diff options
507 files changed, 39898 insertions, 21448 deletions
diff --git a/.github/ISSUE_TEMPLATE/lsp_bug_report.yml b/.github/ISSUE_TEMPLATE/lsp_bug_report.yml index b5b7687bf8..0ed163c9be 100644 --- a/.github/ISSUE_TEMPLATE/lsp_bug_report.yml +++ b/.github/ISSUE_TEMPLATE/lsp_bug_report.yml @@ -29,13 +29,6 @@ body: - type: textarea attributes: - label: ':checkhealth' - description: | - Paste the results from `nvim -c ":checkhealth nvim lspconfig"` - render: markdown - - - type: textarea - attributes: label: 'Steps to reproduce using "nvim -u minimal_init.lua"' description: | - Download the minimal config with `curl -LO https://raw.githubusercontent.com/neovim/nvim-lspconfig/master/test/minimal_init.lua` and modify it to include any specific commands or servers pertaining to your issues. 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/api-docs.yml b/.github/workflows/api-docs.yml index ee72cf5f01..39d85e967d 100644 --- a/.github/workflows/api-docs.yml +++ b/.github/workflows/api-docs.yml @@ -48,6 +48,6 @@ jobs: if: ${{ steps.docs.outputs.UPDATED_DOCS != 0 }} run: | git add -u - git commit -m 'docs: regenerate' + git commit -m 'docs: regenerate [skip ci]' git push --force https://${GITHUB_ACTOR}:${GITHUB_TOKEN}@github.com/${GITHUB_REPOSITORY} ${DOC_BRANCH} - gh pr create --fill --base ${GITHUB_REF#refs/heads/} --head ${DOC_BRANCH} || true + gh pr create --draft --fill --base ${GITHUB_REF#refs/heads/} --head ${DOC_BRANCH} || true diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d2eef13098..3565b20bfc 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: @@ -16,6 +18,88 @@ concurrency: cancel-in-progress: true jobs: + lint: + if: (github.event_name == 'pull_request' && github.base_ref == 'master' && !github.event.pull_request.draft) || (github.event_name == 'push' && github.ref == 'refs/heads/master') + runs-on: ubuntu-20.04 + timeout-minutes: 10 + env: + CC: gcc + steps: + - uses: actions/checkout@v2 + + - name: Setup common environment variables + run: ./.github/workflows/env.sh lint + + - 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 \ + 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: 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 + + - 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 + run: ./ci/before_cache.sh + unixish: name: ${{ matrix.runner }} ${{ matrix.flavor }} (cc=${{ matrix.cc }}) strategy: @@ -23,15 +107,11 @@ jobs: matrix: include: - flavor: asan - cc: clang-12 - runner: ubuntu-20.04 - os: linux - - flavor: lint - cc: gcc + cc: clang-13 runner: ubuntu-20.04 os: linux - flavor: tsan - cc: clang-12 + cc: clang-13 runner: ubuntu-20.04 os: linux - cc: clang @@ -40,6 +120,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 @@ -58,26 +143,40 @@ 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: | wget https://apt.llvm.org/llvm.sh chmod a+x llvm.sh - sudo ./llvm.sh 12 + sudo ./llvm.sh 13 rm llvm.sh - name: Install brew packages if: matrix.os == 'osx' run: | - # Workaround brew issues - rm -f /usr/local/bin/2to3 brew update >/dev/null brew install automake ccache perl cpanminus ninja - name: Setup interpreter packages - run: | - ./ci/before_install.sh - ./ci/install.sh + run: ./ci/install.sh - name: Cache dependencies uses: actions/cache@v2 @@ -90,15 +189,31 @@ 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: - runs-on: windows-2016 + runs-on: windows-2019 + timeout-minutes: 45 if: github.event.pull_request.draft == false env: DEPS_BUILD_DIR: ${{ format('{0}/nvim-deps', github.workspace) }} @@ -121,69 +236,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 commom 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/before_install.sh - ./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/commitlint.yml b/.github/workflows/commitlint.yml index 6c74ec99d3..f190981322 100644 --- a/.github/workflows/commitlint.yml +++ b/.github/workflows/commitlint.yml @@ -1,19 +1,21 @@ name: "Commit Linter" on: - pull_request: + # Only pull_request and push honor [skip ci]. Since this workflow must pass + # to merge a PR, it can't be skipped, so use pull_request_target + pull_request_target: types: [opened, synchronize, reopened, ready_for_review] jobs: lint-commits: runs-on: ubuntu-latest if: github.event.pull_request.draft == false - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} steps: - uses: actions/checkout@v2 with: fetch-depth: 0 ref: ${{ github.event.pull_request.head.sha }} + path: pr_nvim - uses: rhysd/action-setup-vim@v1 with: neovim: true - - run: nvim --clean -es +"lua require('scripts.lintcommit').main({trace=true})" + - run: wget https://raw.githubusercontent.com/neovim/neovim/master/scripts/lintcommit.lua + - run: nvim --clean -es +"cd pr_nvim" +"lua dofile('../lintcommit.lua').main({trace=true})" diff --git a/.github/workflows/env.sh b/.github/workflows/env.sh index a30e06ae26..bd170f92fb 100755 --- a/.github/workflows/env.sh +++ b/.github/workflows/env.sh @@ -34,18 +34,23 @@ case "$FLAVOR" in BUILD_FLAGS="$BUILD_FLAGS -DPREFER_LUA=ON" cat <<EOF >> "$GITHUB_ENV" CLANG_SANITIZER=ASAN_UBSAN -SYMBOLIZER=asan_symbolize-12 -ASAN_OPTIONS=detect_leaks=1:check_initialization_order=1:log_path=$GITHUB_WORKSPACE/build/log/asan +SYMBOLIZER=asan_symbolize-13 +ASAN_OPTIONS=detect_leaks=1:check_initialization_order=1:log_path=$GITHUB_WORKSPACE/build/log/asan:intercept_tls_get_addr=0 UBSAN_OPTIONS=print_stacktrace=1 log_path=$GITHUB_WORKSPACE/build/log/ubsan EOF ;; tsan) cat <<EOF >> "$GITHUB_ENV" TSAN_OPTIONS=log_path=$GITHUB_WORKSPACE/build/log/tsan +CLANG_SANITIZER=TSAN EOF ;; lint) +# Re-enable once system deps are available +# 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" + DEPS_CMAKE_FLAGS="$DEPS_CMAKE_FLAGS -DUSE_BUNDLED_LUV=ON" cat <<EOF >> "$GITHUB_ENV" +USE_BUNDLED=OFF CI_TARGET=lint EOF ;; diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml index 76fc8793fa..d50e0c1f92 100644 --- a/.github/workflows/labeler.yml +++ b/.github/workflows/labeler.yml @@ -1,8 +1,9 @@ name: "Pull Request Labeler" on: pull_request_target: - types: opened + types: [opened] jobs: + triage: runs-on: ubuntu-latest permissions: @@ -12,6 +13,8 @@ jobs: - uses: actions/labeler@main with: repo-token: "${{ secrets.GITHUB_TOKEN }}" + sync-labels: "" + type-scope: runs-on: ubuntu-latest permissions: @@ -23,8 +26,22 @@ jobs: PR_NUMBER: ${{ github.event.pull_request.number }} PR_TITLE: ${{ github.event.pull_request.title }} steps: - # Extract type and try to add it as a label - - run: gh pr edit "$PR_NUMBER" --add-label "$(echo "$PR_TITLE" | sed -E 's|([[:alpha:]]+)(\(.*\))?!?:.*|\1|')" || true + - name: "Extract commit type and add as label" + 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" + run: gh pr edit "$PR_NUMBER" --add-label "$(echo "$PR_TITLE" | sed -E 's|[[:alpha:]]+\((.+)\)!?:.*|\1|')" || true - # Extract scope and try to add it as a label - - 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/notes.md b/.github/workflows/notes.md index 9928d472b3..7181972696 100644 --- a/.github/workflows/notes.md +++ b/.github/workflows/notes.md @@ -6,8 +6,26 @@ ${NVIM_VERSION} ### Windows -1. Extract **nvim-win64.zip** -2. Run `nvim-qt.exe` +#### Zip + +1. Download **nvim-win64.zip** +2. Extract the zip. +3. Run `nvim-qt.exe` + +#### MSI + +1. Download **nvim-win64.msi** +2. Run the MSI +3. Add the Neovim location to your path. + - Default location is `C:\Program Files\Neovim` +4. Search and run `nvim-qt.exe` or run `nvim.exe` on your CLI of choice. + +#### NSIS + +1. Download **nvim-win64.exe** +2. Run the installer. + - Ensure that the option to add the installation location to your path is checked if it's your first installation. +3. Search and run `nvim-qt.exe` or run `nvim.exe` on your CLI of choice. ### macOS @@ -17,6 +35,19 @@ ${NVIM_VERSION} ### Linux (x64) +#### Tarball + +1. Download **nvim-linux64.tar.gz** +2. Extract: `tar xzvf nvim-linux64.tar.gz` +3. Run `./nvim-linux64/bin/nvim` + +#### Debian Package + +1. Download **nvim-linux64.deb** +2. Install the package using `sudo apt install ./nvim-linux64.deb` +3. Run `nvim` + +#### AppImage 1. Download **nvim.appimage** 2. Run `chmod u+x nvim.appimage && ./nvim.appimage` - If your system does not have FUSE you can [extract the appimage](https://github.com/AppImage/AppImageKit/wiki/FUSE#type-2-appimage): @@ -32,9 +63,12 @@ ${NVIM_VERSION} ## SHA256 Checksums ``` -${SHA_LINUX_64} +${SHA_LINUX_64_TAR} +${SHA_LINUX_64_DEB} ${SHA_APP_IMAGE} ${SHA_APP_IMAGE_ZSYNC} ${SHA_MACOS} -${SHA_WIN_64} +${SHA_WIN_64_ZIP} +${SHA_WIN_64_MSI} +${SHA_WIN_64_EXE} ``` diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5839be2944..e954b57175 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -39,12 +39,17 @@ jobs: printf '::set-output name=version::%s\n' "$(./build/bin/nvim --version | head -n 3 | sed -z 's/\n/%0A/g')" printf '::set-output name=release::%s\n' "$(./build/bin/nvim --version | head -n 1)" make DESTDIR="$GITHUB_WORKSPACE/build/release/nvim-linux64" install - cd "$GITHUB_WORKSPACE/build/release" - tar cfz nvim-linux64.tar.gz nvim-linux64 + cd "$GITHUB_WORKSPACE/build/" + cpack -C $NVIM_BUILD_TYPE - uses: actions/upload-artifact@v2 with: name: nvim-linux64 - path: build/release/nvim-linux64.tar.gz + path: build/nvim-linux64.tar.gz + retention-days: 1 + - uses: actions/upload-artifact@v2 + with: + name: nvim-linux64 + path: build/nvim-linux64.deb retention-days: 1 appimage: @@ -80,7 +85,6 @@ jobs: fetch-depth: 0 - name: Install brew packages run: | - rm -f /usr/local/bin/2to3 brew update >/dev/null brew install automake ninja - if: github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && github.event.inputs.tag_name != 'nightly') @@ -115,7 +119,7 @@ jobs: retention-days: 1 windows: - runs-on: windows-2016 + runs-on: windows-2019 env: DEPS_BUILD_DIR: ${{ format('{0}/nvim-deps', github.workspace) }} DEPS_PREFIX: ${{ format('{0}/nvim-deps/usr', github.workspace) }} @@ -132,12 +136,21 @@ jobs: - run: powershell ci\build.ps1 -NoTests env: CONFIGURATION: ${{ matrix.config }} - - run: move build\Neovim.zip build\${{ matrix.archive }}.zip - uses: actions/upload-artifact@v2 with: name: ${{ matrix.archive }} path: build/${{ matrix.archive }}.zip retention-days: 1 + - uses: actions/upload-artifact@v2 + with: + name: ${{ matrix.archive }} + path: build/${{ matrix.archive }}.msi + retention-days: 1 + - uses: actions/upload-artifact@v2 + with: + name: ${{ matrix.archive }} + path: build/${{ matrix.archive }}.exe + retention-days: 1 publish: needs: [linux, appimage, macOS, windows] @@ -188,7 +201,9 @@ jobs: run: | cd ./nvim-linux64 sha256sum nvim-linux64.tar.gz > nvim-linux64.tar.gz.sha256sum - echo "SHA_LINUX_64=$(cat nvim-linux64.tar.gz.sha256sum)" >> $GITHUB_ENV + echo "SHA_LINUX_64_TAR=$(cat nvim-linux64.tar.gz.sha256sum)" >> $GITHUB_ENV + sha256sum nvim-linux64.deb > nvim-linux64.deb.sha256sum + echo "SHA_LINUX_64_DEB=$(cat nvim-linux64.deb.sha256sum)" >> $GITHUB_ENV - name: Generate App Image SHA256 checksums run: | cd ./appimage @@ -208,11 +223,16 @@ jobs: run: | cd ./nvim-win64 sha256sum nvim-win64.zip > nvim-win64.zip.sha256sum - echo "SHA_WIN_64=$(cat nvim-win64.zip.sha256sum)" >> $GITHUB_ENV + echo "SHA_WIN_64_ZIP=$(cat nvim-win64.zip.sha256sum)" >> $GITHUB_ENV + sha256sum nvim-win64.msi > nvim-win64.msi.sha256sum + echo "SHA_WIN_64_MSI=$(cat nvim-win64.msi.sha256sum)" >> $GITHUB_ENV + sha256sum nvim-win64.exe > nvim-win64.exe.sha256sum + echo "SHA_WIN_64_EXE=$(cat nvim-win64.exe.sha256sum)" >> $GITHUB_ENV - name: Publish release env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} NVIM_VERSION: ${{ needs.linux.outputs.version }} + DEBUG: api run: | envsubst < "$GITHUB_WORKSPACE/.github/workflows/notes.md" > "$RUNNER_TEMP/notes.md" gh release create $TAG_NAME $PRERELEASE --notes-file "$RUNNER_TEMP/notes.md" --title "$SUBJECT" --target $GITHUB_SHA nvim-macos/* nvim-linux64/* appimage/* nvim-win64/* 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/.github/workflows/vim-patches.yml b/.github/workflows/vim-patches.yml index 5742b51158..453d293b0e 100644 --- a/.github/workflows/vim-patches.yml +++ b/.github/workflows/vim-patches.yml @@ -49,4 +49,4 @@ jobs: git add -u git commit -m 'version.c: update [skip ci]' git push --force https://${GITHUB_ACTOR}:${GITHUB_TOKEN}@github.com/${GITHUB_REPOSITORY} ${VERSION_BRANCH} - gh pr create --fill --label vim-patch --base ${GITHUB_REF#refs/heads/} --head ${VERSION_BRANCH} || true + gh pr create --draft --fill --label vim-patch --base ${GITHUB_REF#refs/heads/} --head ${VERSION_BRANCH} || true diff --git a/.gitignore b/.gitignore index 0988a51cd9..11479346c7 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,7 @@ compile_commands.json /.clangd/ /.cache/clangd/ /.ccls-cache/ +/.clang-tidy .DS_Store *.mo diff --git a/.luacheckrc b/.luacheckrc index 487f5ab552..9bbd323e84 100644 --- a/.luacheckrc +++ b/.luacheckrc @@ -26,6 +26,10 @@ read_globals = { "vim", } +globals = { + "vim.g", +} + exclude_files = { 'test/functional/fixtures/lua/syntax_error.lua', } diff --git a/CMakeLists.txt b/CMakeLists.txt index 9f1829cf55..eae2d75e3f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -387,7 +387,7 @@ include_directories(SYSTEM ${LIBUV_INCLUDE_DIRS}) find_package(Msgpack 1.0.0 REQUIRED) include_directories(SYSTEM ${MSGPACK_INCLUDE_DIRS}) -find_package(LibLUV 1.30.0 REQUIRED) +find_package(LibLUV 1.43.0 REQUIRED) include_directories(SYSTEM ${LIBLUV_INCLUDE_DIRS}) find_package(TreeSitter REQUIRED) @@ -408,6 +408,19 @@ main(void) if(TS_HAS_SET_MATCH_LIMIT) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DNVIM_TS_HAS_SET_MATCH_LIMIT") endif() +check_c_source_compiles(" +#include <stdlib.h> +#include <tree_sitter/api.h> +int +main(void) +{ + ts_set_allocator(malloc, calloc, realloc, free); + return 0; +} +" TS_HAS_SET_ALLOCATOR) +if(TS_HAS_SET_ALLOCATOR) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DNVIM_TS_HAS_SET_ALLOCATOR") +endif() # Note: The test lib requires LuaJIT; it will be skipped if LuaJIT is missing. option(PREFER_LUA "Prefer Lua over LuaJIT in the nvim executable." OFF) @@ -543,7 +556,11 @@ endif() message(STATUS "Using Lua interpreter: ${LUA_PRG}") -option(COMPILE_LUA "Pre-compile Lua sources into bytecode (for sources that are included in the binary)" ON) +if(DEBUG) + option(COMPILE_LUA "Pre-compile Lua sources into bytecode (for sources that are included in the binary)" OFF) +else() + option(COMPILE_LUA "Pre-compile Lua sources into bytecode (for sources that are included in the binary)" ON) +endif() if(COMPILE_LUA AND NOT WIN32) if(PREFER_LUA) @@ -719,17 +736,6 @@ else() COMMENT "lualint: LUACHECK_PRG not defined") endif() -set(CPACK_PACKAGE_NAME "Neovim") -set(CPACK_PACKAGE_VENDOR "neovim.io") -set(CPACK_PACKAGE_VERSION ${NVIM_VERSION_MEDIUM}) -set(CPACK_PACKAGE_INSTALL_DIRECTORY "Neovim") -# Set toplevel directory/installer name as Neovim -set(CPACK_PACKAGE_FILE_NAME "Neovim") -set(CPACK_TOPLEVEL_TAG "Neovim") -set(CPACK_RESOURCE_FILE_LICENSE "${PROJECT_SOURCE_DIR}/LICENSE") -set(CPACK_NSIS_MODIFY_PATH ON) -set(CPACK_NSIS_ENABLE_UNINSTALL_BEFORE_INSTALL ON) -include(CPack) #add uninstall target if(NOT TARGET uninstall) @@ -741,3 +747,8 @@ if(NOT TARGET uninstall) add_custom_target(uninstall COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_BINARY_DIR}/UninstallHelper.cmake) endif() + + +if(${CMAKE_SOURCE_DIR} STREQUAL ${CMAKE_CURRENT_SOURCE_DIR}) + add_subdirectory(packaging) +endif() diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b02aeb1ed1..5e04f33b15 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -7,11 +7,11 @@ Getting started If you want to help but don't know where to start, here are some low-risk/isolated tasks: -- [Merge a Vim patch]. - Try a [complexity:low] issue. - Fix bugs found by [Clang](#clang-scan-build), [PVS](#pvs-studio) or [Coverity](#coverity). - [Improve documentation][wiki-contribute-help] +- [Merge a Vim patch] (Familiarity with Vim is *strongly* recommended) Reporting problems ------------------ @@ -119,7 +119,7 @@ Apache 2.0 license, except for contributions copied from Vim (identified by the [Managed packages]: https://github.com/neovim/neovim/wiki/Installing-Neovim#install-from-package [Debian]: https://packages.debian.org/testing/neovim [Ubuntu]: http://packages.ubuntu.com/search?keywords=neovim -[Fedora]: https://apps.fedoraproject.org/packages/neovim +[Fedora]: https://packages.fedoraproject.org/pkgs/neovim/neovim/ [Arch Linux]: https://www.archlinux.org/packages/?q=neovim [Void Linux]: https://voidlinux.org/packages/?arch=x86_64&q=neovim [Gentoo]: https://packages.gentoo.org/packages/app-editors/neovim 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/before_install.sh b/ci/before_install.sh deleted file mode 100755 index f12f972fe0..0000000000 --- a/ci/before_install.sh +++ /dev/null @@ -1,46 +0,0 @@ -#!/usr/bin/env bash - -set -e -set -o pipefail - -echo 'Python info:' -( - set -x - python3 --version - python2 --version - python --version - pip3 --version - pip2 --version - pip --version - - pyenv --version - pyenv versions -) 2>&1 | sed 's/^/ /' || true - -# Use pyenv, but not for OSX on Travis, where it only has the "system" version. -if [[ "${TRAVIS_OS_NAME}" != osx ]] && command -v pyenv; then - echo 'Setting Python versions via pyenv' - - # Prefer Python 2 over 3 (more conservative). - pyenv global 2.7:3.8 - - echo 'Updated Python info:' - ( - set -x - python3 --version - python2 --version - python --version - - python3 -m pip --version - python2 -m pip --version - ) 2>&1 | sed 's/^/ /' -fi - -echo "Install node (LTS)" - -if [[ "${TRAVIS_OS_NAME}" == osx ]] || [ ! -f ~/.nvm/nvm.sh ]; then - curl -o ~/.nvm/nvm.sh https://raw.githubusercontent.com/creationix/nvm/master/nvm.sh -fi - -source ~/.nvm/nvm.sh -nvm install 10 diff --git a/ci/before_script.sh b/ci/before_script.sh index 701fe1d9eb..f7216338d4 100755 --- a/ci/before_script.sh +++ b/ci/before_script.sh @@ -6,12 +6,6 @@ set -o pipefail CI_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" source "${CI_DIR}/common/build.sh" -# Enable ipv6 on Travis. ref: a39c8b7ce30d -if test -n "${TRAVIS_OS_NAME}" && ! test "${TRAVIS_OS_NAME}" = osx ; then - echo "before_script.sh: enable ipv6" - sudo sysctl -w net.ipv6.conf.lo.disable_ipv6=0 -fi - # Test some of the configuration variables. if [[ -n "${GCOV}" ]] && [[ ! $(type -P "${GCOV}") ]]; then echo "\$GCOV: '${GCOV}' is not executable." @@ -27,13 +21,6 @@ ccache -s # Reset ccache stats for real results in before_cache. ccache --zero-stats -if [[ "${TRAVIS_OS_NAME}" == osx ]]; then - # Adds user to a dummy group. - # That allows to test changing the group of the file by `os_fchown`. - sudo dscl . -create /Groups/chown_test - sudo dscl . -append /Groups/chown_test GroupMembership "${USER}" -fi - # Compile dependencies. build_deps diff --git a/ci/build.ps1 b/ci/build.ps1 index a81d351bc6..01579a96fe 100644 --- a/ci/build.ps1 +++ b/ci/build.ps1 @@ -77,28 +77,24 @@ if ($compiler -eq 'MINGW') { } elseif ($compiler -eq 'MSVC') { $cmakeGeneratorArgs = '/verbosity:normal' - if ($bits -eq 32) { - $cmakeGenerator = 'Visual Studio 15 2017' - } - elseif ($bits -eq 64) { - $cmakeGenerator = 'Visual Studio 15 2017 Win64' - } + $cmakeGenerator = 'Visual Studio 16 2019' } -if (-not $NoTests) { - # Setup python (use AppVeyor system python) - - # Disambiguate python3, if needed - if (-not (Test-Path -Path C:\hostedtoolcache\windows\Python\3.5.4\x64\python3.exe) ) { - move C:\hostedtoolcache\windows\Python\3.5.4\x64\python.exe C:\hostedtoolcache\windows\Python\3.5.4\x64\python3.exe +if ($compiler -eq 'MSVC') { + $installationPath = vswhere.exe -latest -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath + if ($installationPath -and (test-path "$installationPath\Common7\Tools\vsdevcmd.bat")) { + & "${env:COMSPEC}" /s /c "`"$installationPath\Common7\Tools\vsdevcmd.bat`" -arch=x${bits} -no_logo && set" | foreach-object { + $name, $value = $_ -split '=', 2 + set-content env:\"$name" $value + } } - $env:PATH = "C:\hostedtoolcache\windows\Python\2.7.18\x64;C:\hostedtoolcache\windows\Python\3.5.4\x64;$env:PATH" +} +if (-not $NoTests) { + python -m ensurepip python -m pip install pynvim ; exitIfFailed - python3 -m pip install pynvim ; exitIfFailed # Sanity check python -c "import pynvim; print(str(pynvim))" ; exitIfFailed - python3 -c "import pynvim; print(str(pynvim))" ; exitIfFailed gem.cmd install --pre neovim Get-Command -CommandType Application neovim-ruby-host.bat @@ -108,24 +104,35 @@ if (-not $NoTests) { npm.cmd link neovim } -if ($compiler -eq 'MSVC') { - # Required for LuaRocks (https://github.com/luarocks/luarocks/issues/1039#issuecomment-507296940). - $env:VCINSTALLDIR = "C:/Program Files (x86)/Microsoft Visual Studio/2017/Community/VC/Tools/MSVC/14.16.27023/" -} - function convertToCmakeArgs($vars) { return $vars.GetEnumerator() | foreach { "-D$($_.Key)=$($_.Value)" } } cd $env:DEPS_BUILD_DIR -cmake -G $cmakeGenerator $(convertToCmakeArgs($depsCmakeVars)) "$buildDir/third-party/" ; exitIfFailed +if ($compiler -eq 'MSVC') { + if ($bits -eq 32) { + cmake -G $cmakeGenerator -A Win32 $(convertToCmakeArgs($depsCmakeVars)) "$buildDir/third-party/" ; exitIfFailed + } else { + cmake -G $cmakeGenerator -A x64 $(convertToCmakeArgs($depsCmakeVars)) "$buildDir/third-party/" ; exitIfFailed + } +} else { + cmake -G $cmakeGenerator $(convertToCmakeArgs($depsCmakeVars)) "$buildDir/third-party/" ; exitIfFailed +} cmake --build . --config $cmakeBuildType -- $cmakeGeneratorArgs ; exitIfFailed cd $buildDir # Build Neovim mkdir build cd build -cmake -G $cmakeGenerator $(convertToCmakeArgs($nvimCmakeVars)) .. ; exitIfFailed +if ($compiler -eq 'MSVC') { + if ($bits -eq 32) { + cmake -G $cmakeGenerator -A Win32 $(convertToCmakeArgs($nvimCmakeVars)) .. ; exitIfFailed + } else { + cmake -G $cmakeGenerator -A x64 $(convertToCmakeArgs($nvimCmakeVars)) .. ; exitIfFailed + } +} else { + cmake -G $cmakeGenerator $(convertToCmakeArgs($nvimCmakeVars)) .. ; exitIfFailed +} cmake --build . --config $cmakeBuildType -- $cmakeGeneratorArgs ; exitIfFailed .\bin\nvim --version ; exitIfFailed @@ -157,17 +164,21 @@ if (-not $NoTests) { exit $LastExitCode } + # FIXME: These tests freezes on github CI and causes all jobs to fail. + # Comment out until this is fixed. + # Old tests # Add MSYS to path, required for e.g. `find` used in test scripts. # But would break functionaltests, where its `more` would be used then. - $OldPath = $env:PATH - $env:PATH = "C:\msys64\usr\bin;$env:PATH" - & "C:\msys64\mingw$bits\bin\mingw32-make.exe" -C $(Convert-Path ..\src\nvim\testdir) VERBOSE=1 ; exitIfFailed - $env:PATH = $OldPath - - if ($uploadToCodecov) { - bash -l /c/projects/neovim/ci/common/submit_coverage.sh oldtest - } + + # $OldPath = $env:PATH + # $env:PATH = "C:\msys64\usr\bin;$env:PATH" + # & "C:\msys64\mingw$bits\bin\mingw32-make.exe" -C $(Convert-Path ..\src\nvim\testdir) VERBOSE=1 ; exitIfFailed + # $env:PATH = $OldPath + + # if ($uploadToCodecov) { + # bash -l /c/projects/neovim/ci/common/submit_coverage.sh oldtest + # } } # Ensure choco's cpack is not in PATH otherwise, it conflicts with CMake's @@ -176,7 +187,4 @@ if (Test-Path -Path $env:ChocolateyInstall\bin\cpack.exe) { } # Build artifacts -cpack -G ZIP -C RelWithDebInfo -if ($env:APPVEYOR_REPO_TAG_NAME -ne $null) { - cpack -G NSIS -C RelWithDebInfo -} +cpack -C $cmakeBuildType diff --git a/ci/common/build.sh b/ci/common/build.sh index 0ee4b7493f..b8bbff0b16 100644 --- a/ci/common/build.sh +++ b/ci/common/build.sh @@ -8,8 +8,6 @@ _stat() { top_make() { printf '%78s\n' | tr ' ' '=' - # Travis has 1.5 virtual cores according to: - # http://docs.travis-ci.com/user/speeding-up-the-build/#Paralellizing-your-build-on-one-VM ninja "$@" } @@ -46,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 @@ -55,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 9c7887de0b..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 @@ -18,12 +18,12 @@ if ! [ -f "$codecov_sh" ]; then curl --retry 5 --silent --fail -o "$codecov_sh" https://codecov.io/bash chmod +x "$codecov_sh" - python3 -m pip install --quiet --user gcovr + python -m pip install --quiet --user gcovr fi ( cd build - python3 -m gcovr --branches --exclude-unreachable-branches --print-summary -j 2 --exclude '.*/auto/.*' --root .. --delete -o ../coverage.xml --xml + python -m gcovr --branches --exclude-unreachable-branches --print-summary -j 2 --exclude '.*/auto/.*' --root .. --delete -o ../coverage.xml --xml ) # Upload to codecov. diff --git a/ci/common/suite.sh b/ci/common/suite.sh index 038b116c5a..5110e22ec2 100644 --- a/ci/common/suite.sh +++ b/ci/common/suite.sh @@ -11,65 +11,20 @@ FAIL_SUMMARY="" END_MARKER="$BUILD_DIR/.tests_finished" FAIL_SUMMARY_FILE="$BUILD_DIR/.test_errors" -ANSI_CLEAR="\033[0K" - -if test "$TRAVIS" = "true"; then - ci_fold() { - local action="$1" - local name="$2" - name="$(echo -n "$name" | tr '\n\0' '--' | sed 's/[^A-Za-z0-9]\{1,\}/-/g')" - name="$(echo -n "$name" | sed 's/-$//')" - echo -en "travis_fold:${action}:${name}\r${ANSI_CLEAR}" - } -elif test "$GITHUB_ACTIONS" = "true"; then - ci_fold() { - local action="$1" - local name="$2" - name="$(echo -n "$name" | tr '\n\0' '--' | sed 's/[^A-Za-z0-9]\{1,\}/-/g')" - name="$(echo -n "$name" | sed 's/-$//')" - case "$action" in - start) - echo "::group::${name}" - ;; - end) - echo "::endgroup::" - ;; - *) - :;; - esac - } -else - ci_fold() { - return 0 - } -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 "${NVIM_TEST_CURRENT_SUITE}" - 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 "${NVIM_TEST_CURRENT_SUITE}" 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() { @@ -87,114 +42,15 @@ fail() { FAILED=1 } -run_test() { - local cmd="$1" - test $# -gt 0 && shift - local test_name="$1" - : ${test_name:=$cmd} - test $# -gt 0 && shift - if ! eval "$cmd" ; then - fail "${test_name}" "$@" - fi -} - -run_test_wd() { - local hang_ok= - if test "$1" = "--allow-hang" ; then - hang_ok=1 - shift - fi - - local timeout="$1" - test $# -gt 0 && shift - - local cmd="$1" - test $# -gt 0 && shift - - local restart_cmd="$1" - : ${restart_cmd:=true} - test $# -gt 0 && shift - - local test_name="$1" - : ${test_name:=$cmd} - test $# -gt 0 && shift - - local output_file="$(mktemp)" - local status_file="$(mktemp)" - local sid_file="$(mktemp)" - - local restarts=5 - local prev_tmpsize=-1 - while test $restarts -gt 0 ; do - : > "$status_file" - : > "$sid_file" - setsid \ - env \ - output_file="$output_file" \ - status_file="$status_file" \ - sid_file="$sid_file" \ - cmd="$cmd" \ - CI_DIR="$CI_DIR" \ - sh -c ' - . "${CI_DIR}/common/test.sh" - ps -o sid= > "$sid_file" - ( - ret=0 - if ! eval "$cmd" 2>&1 ; then - ret=1 - fi - echo "$ret" > "$status_file" - ) | tee -a "$output_file" - ' - while test "$(stat -c "%s" "$status_file")" -eq 0 ; do - prev_tmpsize=$tmpsize - sleep $timeout - tmpsize="$(stat -c "%s" "$output_file")" - if test $tempsize -eq $prev_temsize ; then - # no output, assuming either hang or exit - break - fi - done - restarts=$(( restarts - 1 )) - if test "$(stat -c "%s" "$status_file")" -eq 0 ; then - # Status file not updated, assuming hang - - # SID not known, this should not ever happen - if test "$(stat -c "%s" "$sid_file")" -eq 0 ; then - fail "$test_name" E "Shell did not run" - break - fi - - # Kill all processes which belong to one session: should get rid of test - # processes as well as sh itself. - pkill -KILL -s$(cat "$sid_file") - - if test $restarts -eq 0 ; then - if test -z "$hang_ok" ; then - fail "$test_name" E "Test hang up" - fi - else - echo "Test ${test_name} hang up, restarting" - eval "$restart_cmd" - fi - else - local new_failed="$(cat "$status_file")" - if test "$new_failed" != "0" ; then - fail "$test_name" F "Test failed in run_test_wd" - fi - break - fi - done - - rm -f "$output_file" - rm -f "$status_file" - rm -f "$sid_file" -} - 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 @@ -208,3 +64,13 @@ end_tests() { touch "${END_MARKER}" ended_successfully } + +run_suite() { + local command="$1" + local suite_name="$2" + + enter_suite "$suite_name" + eval "$command" || fail "$suite_name" + exit_suite +} + diff --git a/ci/common/test.sh b/ci/common/test.sh index 92c15c8ba1..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,11 +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 )} - -csi_clean() { - find "${BUILD_DIR}/bin" -name 'test-includes-*' -delete - find "${BUILD_DIR}" -name '*test-include*.o' -delete -} diff --git a/ci/install.sh b/ci/install.sh index 1edc1138ee..894e090de2 100755 --- a/ci/install.sh +++ b/ci/install.sh @@ -3,29 +3,14 @@ set -e set -o pipefail -if [[ "${CI_TARGET}" == lint ]]; then - python3 -m pip -q install --user --upgrade flake8 - exit -fi - -if [[ "${TRAVIS_OS_NAME}" == osx ]]; then - export PATH="/usr/local/opt/ccache/libexec:$PATH" -fi - # Use default CC to avoid compilation problems when installing Python modules. -echo "Install neovim module for Python 3." -CC=cc python3 -m pip -q install --user --upgrade pynvim -if python2 -m pip -c True 2>&1; then - echo "Install neovim module for Python 2." - CC=cc python2 -m pip -q install --user --upgrade pynvim -fi +echo "Install neovim module for Python." +CC=cc python -m pip -q install --user --upgrade pynvim echo "Install neovim RubyGem." gem install --no-document --bindir "$HOME/.local/bin" --user-install --pre neovim echo "Install neovim npm package" -source ~/.nvm/nvm.sh -nvm use 10 npm install -g neovim npm link neovim diff --git a/ci/run_lint.sh b/ci/run_lint.sh index 8373a3cb36..2fea7a40c0 100755 --- a/ci/run_lint.sh +++ b/ci/run_lint.sh @@ -8,29 +8,34 @@ CI_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" source "${CI_DIR}/common/build.sh" source "${CI_DIR}/common/suite.sh" -enter_suite 'clint' -run_test 'make clint-full' clint -exit_suite --continue - -enter_suite 'lualint' -run_test 'make lualint' lualint -exit_suite --continue - -enter_suite 'pylint' -run_test 'make pylint' pylint -exit_suite --continue - -enter_suite 'shlint' -run_test 'make shlint' shlint -exit_suite --continue - -enter_suite single-includes -CLICOLOR_FORCE=1 run_test_wd \ - --allow-hang \ - 10s \ - 'make check-single-includes' \ - 'csi_clean' \ - single-includes -exit_suite --continue - -end_tests +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 +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 d91ac5589e..ae85246ab6 100755 --- a/ci/run_tests.sh +++ b/ci/run_tests.sh @@ -8,33 +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 - -source ~/.nvm/nvm.sh -nvm use 10 - - -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 + 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 + 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 - -run_test install_nvim - -exit_suite --continue - -end_tests diff --git a/ci/script.sh b/ci/script.sh index c8025ce34d..74fc4eda6c 100755 --- a/ci/script.sh +++ b/ci/script.sh @@ -3,14 +3,7 @@ set -e set -o pipefail -# This will pass the environment variables down to a bash process which runs -# as $USER, while retaining the environment variables defined and belonging -# to secondary groups given above in usermod. -if [[ "${TRAVIS_OS_NAME}" == osx ]]; then - sudo -E su "${USER}" -c "ci/run_${CI_TARGET}.sh" -else - ci/run_${CI_TARGET}.sh -fi +ci/run_${CI_TARGET}.sh if [[ -s "${GCOV_ERROR_FILE}" ]]; then echo '=== Unexpected gcov errors: ===' diff --git a/cmake/RunTests.cmake b/cmake/RunTests.cmake index 3adbcbbfc5..789131c26c 100644 --- a/cmake/RunTests.cmake +++ b/cmake/RunTests.cmake @@ -95,5 +95,7 @@ if(NOT res EQUAL 0) endif() endif() - message(FATAL_ERROR "${TEST_TYPE} tests failed with error: ${res}") + IF (NOT WIN32) + message(FATAL_ERROR "${TEST_TYPE} tests failed with error: ${res}") + ENDIF() endif() diff --git a/packaging/CMakeLists.txt b/packaging/CMakeLists.txt new file mode 100644 index 0000000000..6ae744c2cd --- /dev/null +++ b/packaging/CMakeLists.txt @@ -0,0 +1,78 @@ +set(CPACK_PACKAGE_NAME "Neovim") +set(CPACK_PACKAGE_VENDOR "neovim.io") +set(CPACK_PACKAGE_FILE_NAME "nvim") + +# From the GitHub About section +set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Vim-fork focused on extensibility and usability.") + +set(CPACK_PACKAGE_INSTALL_DIRECTORY ${CPACK_PACKAGE_NAME}) + +# Pull the versions defined with the top level CMakeLists.txt +set(CPACK_PACKAGE_VERSION_MAJOR ${NVIM_VERSION_MAJOR}) +set(CPACK_PACKAGE_VERSION_MINOR ${NVIM_VERSION_MINOR}) +set(CPACK_PACKAGE_VERSION_PATCH ${NVIM_VERSION_PATCH}) + +# CPACK_VERBATIM_VARIABLES ensures that the variables prefixed with *CPACK_* +# are correctly passed to the cpack program. +# This should always be set to true. +set(CPACK_VERBATIM_VARIABLES TRUE) + +set(CPACK_RESOURCE_FILE_LICENSE "${PROJECT_SOURCE_DIR}/LICENSE.txt") +set(CPACK_RESOURCE_FILE_README ${PROJECT_SOURCE_DIR}/README.md) + + +if(WIN32) + set(CPACK_PACKAGE_FILE_NAME "nvim-win64") + set(CPACK_GENERATOR ZIP WIX NSIS) + + # WIX + # CPACK_WIX_UPGRADE_GUID should be set, but should never change. + # CPACK_WIX_PRODUCT_GUID should not be set (leave as default to auto-generate). + + # The following guid is just a randomly generated guid that's been pasted here. + # It has no special meaning other than to supply it to WIX. + set(CPACK_WIX_UPGRADE_GUID "207A1A70-7B0C-418A-A153-CA6883E38F4D") + set(CPACK_WIX_PRODUCT_ICON ${CMAKE_CURRENT_LIST_DIR}/logo.ico) + + # NSIS + # install icon + set(CPACK_NSIS_MUI_ICON ${CMAKE_CURRENT_LIST_DIR}/logo.ico) + + # uninstall icon + set(CPACK_NSIS_MUI_UNIICON ${CMAKE_CURRENT_LIST_DIR}/logo.ico) + + # icon that appears when you search in Add/Remove programs + set(CPACK_NSIS_INSTALLED_ICON_NAME ${CMAKE_CURRENT_LIST_DIR}/logo.ico) + + # name that appears in the list in Add/Remove programs + set(CPACK_NSIS_DISPLAY_NAME "Neovim") + + # name used in various installer UI locations, such as the title + set(CPACK_NSIS_PACKAGE_NAME "Neovim") + + # Allow the user to modify their path to include neovim during + # the installation process. + set(CPACK_NSIS_MODIFY_PATH TRUE) +elseif(APPLE) + set(CPACK_PACKAGE_FILE_NAME "nvim-macos") + set(CPACK_GENERATOR TGZ) + set(CPACK_PACKAGE_ICON ${CMAKE_CURRENT_LIST_DIR}/logo.icns) +elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux") + set(CPACK_PACKAGE_FILE_NAME "nvim-linux64") + set(CPACK_GENERATOR TGZ DEB) + set(CPACK_DEBIAN_PACKAGE_NAME "Neovim") # required + set(CPACK_DEBIAN_PACKAGE_MAINTAINER "Neovim.io") # required + + # Automatically compute required shared lib dependencies. + # Unfortunately, you "just need to know" that this has a hidden + # dependency on dpkg-shlibdeps whilst using a debian based host. + set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS TRUE) +else() + set(CPACK_GENERATOR TGZ) +endif() + +# CPack variables are loaded in on the call to include(CPack). If you set +# variables *after* the inclusion, they don't get updated within the CPack +# config. Note that some CPack commands should still be run after it, such +# as cpack_add_component(). +include(CPack) diff --git a/packaging/logo.icns b/packaging/logo.icns Binary files differnew file mode 100644 index 0000000000..a6377d9cdb --- /dev/null +++ b/packaging/logo.icns diff --git a/packaging/logo.ico b/packaging/logo.ico Binary files differnew file mode 100644 index 0000000000..a4523501b9 --- /dev/null +++ b/packaging/logo.ico diff --git a/packaging/logo.svg b/packaging/logo.svg new file mode 100644 index 0000000000..e8aa8bd33e --- /dev/null +++ b/packaging/logo.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 54 65" fill="#fff" fill-rule="evenodd" stroke="#000" stroke-linecap="round" stroke-linejoin="round"><use xlink:href="#D" x=".5" y=".5"/><defs><linearGradient x1="50.00%" y1="0.00%" x2="50.00%" y2="100.00%" id="A"><stop stop-color="#16b0ed" stop-opacity=".8" offset="0%"/><stop stop-color="#0f59b2" stop-opacity=".837" offset="100%"/></linearGradient><linearGradient x1="50.00%" y1="0.00%" x2="50.00%" y2="100.00%" id="B"><stop stop-color="#7db643" offset="0%"/><stop stop-color="#367533" offset="100%"/></linearGradient><linearGradient x1="50.00%" y1="-0.00%" x2="50.00%" y2="100.01%" id="C"><stop stop-color="#88c649" stop-opacity=".8" offset="0%"/><stop stop-color="#439240" stop-opacity=".84" offset="100%"/></linearGradient></defs><symbol id="D" overflow="visible"><g stroke="none"><path d="M0 13.761L13.63 0v63.983L0 50.381z" fill="url(#A)"/><path d="M52.664 13.894L38.848.008l.281 63.976 13.63-13.602z" fill="url(#B)"/><path d="M13.621.011l35.435 54.07-9.916 9.915L3.686 10.046z" fill="url(#C)"/><path d="M13.633 25.092l-.019 2.13L2.676 11.069l1.013-1.032z" fill="#000" fill-opacity=".13"/></g></symbol></svg>
\ No newline at end of file diff --git a/runtime/autoload/dist/ft.vim b/runtime/autoload/dist/ft.vim index 9933ace8c2..54cb6ce70f 100644 --- a/runtime/autoload/dist/ft.vim +++ b/runtime/autoload/dist/ft.vim @@ -1,7 +1,7 @@ " Vim functions for file type detection " " Maintainer: Bram Moolenaar <Bram@vim.org> -" Last Change: 2021 Dec 17 +" Last Change: 2022 Feb 22 " These functions are moved here from runtime/filetype.vim to make startup " faster. @@ -67,13 +67,32 @@ func dist#ft#FTasmsyntax() endif endfunc -" Check if one of the first five lines contains "VB_Name". In that case it is -" probably a Visual Basic file. Otherwise it's assumed to be "alt" filetype. -func dist#ft#FTVB(alt) - if getline(1).getline(2).getline(3).getline(4).getline(5) =~? 'VB_Name\|Begin VB\.\(Form\|MDIForm\|UserControl\)' +let s:ft_visual_basic_content = '\cVB_Name\|Begin VB\.\(Form\|MDIForm\|UserControl\)' + +" See FTfrm() for Visual Basic form file detection +func dist#ft#FTbas() + if exists("g:filetype_bas") + exe "setf " . g:filetype_bas + return + endif + + " most frequent FreeBASIC-specific keywords in distro files + let fb_keywords = '\c^\s*\%(extern\|var\|enum\|private\|scope\|union\|byref\|operator\|constructor\|delete\|namespace\|public\|property\|with\|destructor\|using\)\>\%(\s*[:=(]\)\@!' + let fb_preproc = '\c^\s*\%(#\a\+\|option\s\+\%(byval\|dynamic\|escape\|\%(no\)\=gosub\|nokeyword\|private\|static\)\>\)' + let fb_comment = "^\\s*/'" + " OPTION EXPLICIT, without the leading underscore, is common to many dialects + let qb64_preproc = '\c^\s*\%($\a\+\|option\s\+\%(_explicit\|_\=explicitarray\)\>\)' + + let lines = getline(1, min([line("$"), 100])) + + if match(lines, fb_preproc) > -1 || match(lines, fb_comment) > -1 || match(lines, fb_keywords) > -1 + setf freebasic + elseif match(lines, qb64_preproc) > -1 + setf qb64 + elseif match(lines, s:ft_visual_basic_content) > -1 setf vb else - exe "setf " . a:alt + setf basic endif endfunc @@ -192,6 +211,10 @@ func dist#ft#EuphoriaCheck() endfunc func dist#ft#DtraceCheck() + if did_filetype() + " Filetype was already detected + return + endif let lines = getline(1, min([line("$"), 100])) if match(lines, '^module\>\|^import\>') > -1 " D files often start with a module and/or import statement. @@ -219,6 +242,21 @@ func dist#ft#FTe() endif endfunc +func dist#ft#FTfrm() + if exists("g:filetype_frm") + exe "setf " . g:filetype_frm + return + endif + + let lines = getline(1, min([line("$"), 5])) + + if match(lines, s:ft_visual_basic_content) > -1 + setf vb + else + setf form + endif +endfunc + " Distinguish between Forth and F#. " Provided by Doug Kearns. func dist#ft#FTfs() @@ -692,7 +730,7 @@ func dist#ft#FTperl() endif let save_cursor = getpos('.') call cursor(1,1) - let has_use = search('^use\s\s*\k', 'c', 30) + let has_use = search('^use\s\s*\k', 'c', 30) > 0 call setpos('.', save_cursor) if has_use setf perl @@ -724,7 +762,8 @@ func dist#ft#FTtex() let save_cursor = getpos('.') call cursor(1,1) let firstNC = search('^\s*[^[:space:]%]', 'c', 1000) - if firstNC " Check the next thousand lines for a LaTeX or ConTeXt keyword. + if firstNC > 0 + " Check the next thousand lines for a LaTeX or ConTeXt keyword. let lpat = 'documentclass\>\|usepackage\>\|begin{\|newcommand\>\|renewcommand\>' let cpat = 'start\a\+\|setup\a\+\|usemodule\|enablemode\|enableregime\|setvariables\|useencoding\|usesymbols\|stelle\a\+\|verwende\a\+\|stel\a\+\|gebruik\a\+\|usa\a\+\|imposta\a\+\|regle\a\+\|utilisemodule\>' let kwline = search('^\s*\\\%(' . lpat . '\)\|^\s*\\\(' . cpat . '\)', @@ -829,6 +868,38 @@ func dist#ft#Dep3patch() endfor endfunc +" This function checks the first 15 lines for appearance of 'FoamFile' +" and then 'object' in a following line. +" In that case, it's probably an OpenFOAM file +func dist#ft#FTfoam() + let ffile = 0 + let lnum = 1 + while lnum <= 15 + if getline(lnum) =~# '^FoamFile' + let ffile = 1 + elseif ffile == 1 && getline(lnum) =~# '^\s*object' + setf foam + return + endif + let lnum = lnum + 1 + endwhile +endfunc + +" Determine if a *.tf file is TF mud client or terraform +func dist#ft#FTtf() + let numberOfLines = line('$') + for i in range(1, numberOfLines) + let currentLine = trim(getline(i)) + let firstCharacter = currentLine[0] + if firstCharacter !=? ";" && firstCharacter !=? "/" && firstCharacter !=? "" + setf terraform + return + endif + endfor + setf tf +endfunc + + " Restore 'cpoptions' let &cpo = s:cpo_save unlet s:cpo_save diff --git a/runtime/autoload/freebasic.vim b/runtime/autoload/freebasic.vim new file mode 100644 index 0000000000..428cf1382b --- /dev/null +++ b/runtime/autoload/freebasic.vim @@ -0,0 +1,41 @@ +" Vim filetype plugin file +" Language: FreeBASIC +" Maintainer: Doug Kearns <dougkearns@gmail.com> +" Last Change: 2021 Mar 16 + +" Dialects can be one of fb, qb, fblite, or deprecated +" Precedence is forcelang > #lang > lang +function! freebasic#GetDialect() abort + if exists("g:freebasic_forcelang") + return g:freebasic_forcelang + endif + + if exists("g:freebasic_lang") + let dialect = g:freebasic_lang + else + let dialect = "fb" + endif + + " override with #lang directive or metacommand + + let skip = "has('syntax_items') && synIDattr(synID(line('.'), col('.'), 1), 'name') =~ 'Comment$'" + let pat = '\c^\s*\%(#\s*lang\s\+\|''\s*$lang\s*:\s*\)"\([^"]*\)"' + + let save_cursor = getcurpos() + call cursor(1, 1) + let lnum = search(pat, 'n', '', '', skip) + call setpos('.', save_cursor) + + if lnum + let word = matchlist(getline(lnum), pat)[1] + if word =~? '\%(fb\|deprecated\|fblite\|qb\)' + let dialect = word + else + echomsg "freebasic#GetDialect: Invalid lang, found '" .. word .. "' at line " .. lnum .. " " .. getline(lnum) + endif + endif + + return dialect +endfunction + +" vim: nowrap sw=2 sts=2 ts=8 noet fdm=marker: 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/autoload/health/nvim.vim b/runtime/autoload/health/nvim.vim index 0bb343e198..961f83d926 100644 --- a/runtime/autoload/health/nvim.vim +++ b/runtime/autoload/health/nvim.vim @@ -104,8 +104,8 @@ function! s:check_rplugin_manifest() abort if !has_key(existing_rplugins, script) let msg = printf('"%s" is not registered.', fnamemodify(path, ':t')) if python_version ==# 'pythonx' - if !has('python2') && !has('python3') - let msg .= ' (python2 and python3 not available)' + if !has('python3') + let msg .= ' (python3 not available)' endif elseif !has(python_version) let msg .= printf(' (%s not available)', python_version) @@ -148,14 +148,14 @@ endfunction function! s:get_tmux_option(option) abort let cmd = 'tmux show-option -qvg '.a:option " try global scope - let out = system(cmd) + let out = system(split(cmd)) let val = substitute(out, '\v(\s|\r|\n)', '', 'g') if v:shell_error call health#report_error('command failed: '.cmd."\n".out) return 'error' elseif empty(val) let cmd = 'tmux show-option -qvgs '.a:option " try session scope - let out = system(cmd) + let out = system(split(cmd)) let val = substitute(out, '\v(\s|\r|\n)', '', 'g') if v:shell_error call health#report_error('command failed: '.cmd."\n".out) @@ -202,11 +202,11 @@ function! s:check_tmux() abort " check default-terminal and $TERM call health#report_info('$TERM: '.$TERM) let cmd = 'tmux show-option -qvg default-terminal' - let out = system(cmd) + let out = system(split(cmd)) let tmux_default_term = substitute(out, '\v(\s|\r|\n)', '', 'g') if empty(tmux_default_term) let cmd = 'tmux show-option -qvgs default-terminal' - let out = system(cmd) + let out = system(split(cmd)) let tmux_default_term = substitute(out, '\v(\s|\r|\n)', '', 'g') endif @@ -225,7 +225,7 @@ function! s:check_tmux() abort endif " check for RGB capabilities - let info = system('tmux server-info') + let info = system(['tmux', 'server-info']) let has_tc = stridx(info, " Tc: (flag) true") != -1 let has_rgb = stridx(info, " RGB: (flag) true") != -1 if !has_tc && !has_rgb @@ -242,7 +242,7 @@ function! s:check_terminal() abort endif call health#report_start('terminal') let cmd = 'infocmp -L' - let out = system(cmd) + let out = system(split(cmd)) let kbs_entry = matchstr(out, 'key_backspace=[^,[:space:]]*') let kdch1_entry = matchstr(out, 'key_dc=[^,[:space:]]*') diff --git a/runtime/autoload/health/provider.vim b/runtime/autoload/health/provider.vim index 7b4dce3441..2f35179338 100644 --- a/runtime/autoload/health/provider.vim +++ b/runtime/autoload/health/provider.vim @@ -282,10 +282,10 @@ function! s:disabled_via_loaded_var(provider) abort return 0 endfunction -function! s:check_python(version) abort - call health#report_start('Python ' . a:version . ' provider (optional)') +function! s:check_python() abort + call health#report_start('Python 3 provider (optional)') - let pyname = 'python'.(a:version == 2 ? '' : '3') + let pyname = 'python3' let python_exe = '' let venv = exists('$VIRTUAL_ENV') ? resolve($VIRTUAL_ENV) : '' let host_prog_var = pyname.'_host_prog' @@ -301,7 +301,7 @@ function! s:check_python(version) abort call health#report_info(printf('Using: g:%s = "%s"', host_prog_var, get(g:, host_prog_var))) endif - let [pyname, pythonx_errors] = provider#pythonx#Detect(a:version) + let [pyname, pythonx_errors] = provider#pythonx#Detect(3) if empty(pyname) call health#report_warn('No Python executable found that can `import neovim`. ' @@ -405,7 +405,7 @@ function! s:check_python(version) abort " can import 'pynvim'. If so, that Python failed to import 'neovim' as " well, which is most probably due to a failed pip upgrade: " https://github.com/neovim/neovim/wiki/Following-HEAD#20181118 - let [pynvim_exe, errors] = provider#pythonx#DetectByModule('pynvim', a:version) + let [pynvim_exe, errors] = provider#pythonx#DetectByModule('pynvim', 3) if !empty(pynvim_exe) call health#report_error( \ 'Detected pip upgrade failure: Python executable can import "pynvim" but ' @@ -416,14 +416,14 @@ function! s:check_python(version) abort \ . pynvim_exe ." -m pip install neovim # only if needed by third-party software") endif else - let [pyversion, current, latest, status] = s:version_info(python_exe) + let [majorpyversion, current, latest, status] = s:version_info(python_exe) - if a:version != str2nr(pyversion) + if 3 != str2nr(majorpyversion) call health#report_warn('Unexpected Python version.' . \ ' This could lead to confusing error messages.') endif - call health#report_info('Python version: ' . pyversion) + call health#report_info('Python version: ' . majorpyversion) if s:is_bad_response(status) call health#report_info(printf('pynvim version: %s (%s)', current, status)) @@ -523,7 +523,7 @@ function! s:check_virtualenv() abort let hint = '$PATH ambiguities in subshells typically are ' \.'caused by your shell config overriding the $PATH previously set by the ' \.'virtualenv. Either prevent them from doing so, or use this workaround: ' - \.'https://vi.stackexchange.com/a/7654' + \.'https://vi.stackexchange.com/a/34996' let hints[hint] = v:true endif endfor @@ -565,7 +565,7 @@ function! s:check_ruby() abort \ ['Install Ruby and verify that `ruby` and `gem` commands work.']) return endif - call health#report_info('Ruby: '. s:system('ruby -v')) + call health#report_info('Ruby: '. s:system(['ruby', '-v'])) let [host, err] = provider#ruby#Detect() if empty(host) @@ -588,11 +588,11 @@ function! s:check_ruby() abort endif let latest_gem = get(split(latest_gem, 'neovim (\|, \|)$' ), 0, 'not found') - let current_gem_cmd = host .' --version' + let current_gem_cmd = [host, '--version'] let current_gem = s:system(current_gem_cmd) if s:shell_error - call health#report_error('Failed to run: '. current_gem_cmd, - \ ['Report this issue with the output of: ', current_gem_cmd]) + call health#report_error('Failed to run: '. join(current_gem_cmd), + \ ['Report this issue with the output of: ', join(current_gem_cmd)]) return endif @@ -619,7 +619,7 @@ function! s:check_node() abort \ ['Install Node.js and verify that `node` and `npm` (or `yarn`) commands work.']) return endif - let node_v = get(split(s:system('node -v'), "\n"), 0, '') + let node_v = get(split(s:system(['node', '-v']), "\n"), 0, '') call health#report_info('Node.js: '. node_v) if s:shell_error || s:version_cmp(node_v[1:], '6.0.0') < 0 call health#report_warn('Nvim node.js host does not support '.node_v) @@ -660,8 +660,8 @@ function! s:check_node() abort let current_npm_cmd = ['node', host, '--version'] let current_npm = s:system(current_npm_cmd) if s:shell_error - call health#report_error('Failed to run: '. string(current_npm_cmd), - \ ['Report this issue with the output of: ', string(current_npm_cmd)]) + call health#report_error('Failed to run: '. join(current_npm_cmd), + \ ['Report this issue with the output of: ', join(current_npm_cmd)]) return endif @@ -734,8 +734,8 @@ function! s:check_perl() abort let current_cpan_cmd = [perl_exec, '-W', '-MNeovim::Ext', '-e', 'print $Neovim::Ext::VERSION'] let current_cpan = s:system(current_cpan_cmd) if s:shell_error - call health#report_error('Failed to run: '. string(current_cpan_cmd), - \ ['Report this issue with the output of: ', string(current_cpan_cmd)]) + call health#report_error('Failed to run: '. join(current_cpan_cmd), + \ ['Report this issue with the output of: ', join(current_cpan_cmd)]) return endif @@ -751,8 +751,7 @@ endfunction function! health#provider#check() abort call s:check_clipboard() - call s:check_python(2) - call s:check_python(3) + call s:check_python() call s:check_virtualenv() call s:check_ruby() call s:check_node() diff --git a/runtime/autoload/man.vim b/runtime/autoload/man.vim index 90d353f9de..b28170b7a1 100644 --- a/runtime/autoload/man.vim +++ b/runtime/autoload/man.vim @@ -7,7 +7,6 @@ let s:loaded_man = 1 let s:find_arg = '-w' let s:localfile_arg = v:true " Always use -l if possible. #6683 -let s:section_arg = '-S' function! man#init() abort try @@ -216,16 +215,42 @@ endfunction function! s:get_path(sect, name) abort " Some man implementations (OpenBSD) return all available paths from the - " search command, so we get() the first one. #8341 + " search command. Previously, this function would simply select the first one. + " + " However, some searches will report matches that are incorrect: + " man -w strlen may return string.3 followed by strlen.3, and therefore + " selecting the first would get us the wrong page. Thus, we must find the + " first matching one. + " + " There's yet another special case here. Consider the following: + " If you run man -w strlen and string.3 comes up first, this is a problem. We + " should search for a matching named one in the results list. + " However, if you search for man -w clock_gettime, you will *only* get + " clock_getres.2, which is the right page. Searching the resuls for + " clock_gettime will no longer work. In this case, we should just use the + " first one that was found in the correct section. + " + " Finally, we can avoid relying on -S or -s here since they are very + " inconsistently supported. Instead, call -w with a section and a name. if empty(a:sect) - return substitute(get(split(s:system(['man', s:find_arg, a:name])), 0, ''), '\n\+$', '', '') + let results = split(s:system(['man', s:find_arg, a:name])) + else + let results = split(s:system(['man', s:find_arg, a:sect, a:name])) + endif + + if empty(results) + return '' endif - " '-s' flag handles: - " - tokens like 'printf(echo)' - " - sections starting with '-' - " - 3pcap section (found on macOS) - " - commas between sections (for section priority) - return substitute(get(split(s:system(['man', s:find_arg, s:section_arg, a:sect, a:name])), 0, ''), '\n\+$', '', '') + + " find any that match the specified name + let namematches = filter(copy(results), 'fnamemodify(v:val, ":t") =~ a:name') + let sectmatches = [] + + if !empty(namematches) && !empty(a:sect) + let sectmatches = filter(copy(namematches), 'fnamemodify(v:val, ":e") == a:sect') + endif + + return substitute(get(sectmatches, 0, get(namematches, 0, results[0])), '\n\+$', '', '') endfunction " s:verify_exists attempts to find the path to a manpage @@ -243,40 +268,72 @@ endfunction " then we don't do it again in step 2. function! s:verify_exists(sect, name) abort let sect = a:sect - if empty(sect) - let sect = get(b:, 'man_default_sects', '') - endif - try - return s:get_path(sect, a:name) - catch /^command error (/ - endtry - - if !empty(get(b:, 'man_default_sects', '')) && sect !=# b:man_default_sects + if empty(sect) + " no section specified, so search with b:man_default_sects + if exists('b:man_default_sects') + let sects = split(b:man_default_sects, ',') + for sec in sects + try + let res = s:get_path(sec, a:name) + if !empty(res) + return res + endif + catch /^command error (/ + endtry + endfor + endif + else + " try with specified section try - return s:get_path(b:man_default_sects, a:name) + let res = s:get_path(sect, a:name) + if !empty(res) + return res + endif catch /^command error (/ endtry - endif - if !empty(sect) - try - return s:get_path('', a:name) - catch /^command error (/ - endtry + " try again with b:man_default_sects + if exists('b:man_default_sects') + let sects = split(b:man_default_sects, ',') + for sec in sects + try + let res = s:get_path(sec, a:name) + if !empty(res) + return res + endif + catch /^command error (/ + endtry + endfor + endif endif + " if none of the above worked, we will try with no section + try + let res = s:get_path('', a:name) + if !empty(res) + return res + endif + catch /^command error (/ + endtry + + " if that still didn't work, we will check for $MANSECT and try again with it + " unset if !empty($MANSECT) try let MANSECT = $MANSECT call setenv('MANSECT', v:null) - return s:get_path('', a:name) + let res = s:get_path('', a:name) + if !empty(res) + return res + endif catch /^command error (/ finally call setenv('MANSECT', MANSECT) endtry endif + " finally, if that didn't work, there is no hope throw 'no manual entry for ' . a:name endfunction diff --git a/runtime/autoload/provider/python.vim b/runtime/autoload/provider/python.vim deleted file mode 100644 index 8a1d162784..0000000000 --- a/runtime/autoload/provider/python.vim +++ /dev/null @@ -1,45 +0,0 @@ -" The Python provider uses a Python host to emulate an environment for running -" python-vim plugins. :help provider -" -" Associating the plugin with the Python host is the first step because plugins -" will be passed as command-line arguments - -if exists('g:loaded_python_provider') - finish -endif -let [s:prog, s:err] = provider#pythonx#Detect(2) -let g:loaded_python_provider = empty(s:prog) ? 1 : 2 - -function! provider#python#Prog() abort - return s:prog -endfunction - -function! provider#python#Error() abort - return s:err -endfunction - -" The Python provider plugin will run in a separate instance of the Python -" host. -call remote#host#RegisterClone('legacy-python-provider', 'python') -call remote#host#RegisterPlugin('legacy-python-provider', 'script_host.py', []) - -function! provider#python#Call(method, args) abort - if s:err != '' - return - endif - if !exists('s:host') - let s:rpcrequest = function('rpcrequest') - - " Ensure that we can load the Python host before bootstrapping - try - let s:host = remote#host#Require('legacy-python-provider') - catch - let s:err = v:exception - echohl WarningMsg - echomsg v:exception - echohl None - return - endtry - endif - return call(s:rpcrequest, insert(insert(a:args, 'python_'.a:method), s:host)) -endfunction diff --git a/runtime/autoload/provider/pythonx.vim b/runtime/autoload/provider/pythonx.vim index 0eeb35cba8..5b299b322c 100644 --- a/runtime/autoload/provider/pythonx.vim +++ b/runtime/autoload/provider/pythonx.vim @@ -6,10 +6,8 @@ endif let s:loaded_pythonx_provider = 1 function! provider#pythonx#Require(host) abort - let ver = (a:host.orig_name ==# 'python') ? 2 : 3 - " Python host arguments - let prog = (ver == '2' ? provider#python#Prog() : provider#python3#Prog()) + let prog = provider#python3#Prog() let args = [prog, '-c', 'import sys; sys.path = list(filter(lambda x: x != "", sys.path)); import neovim; neovim.start_host()'] @@ -23,14 +21,12 @@ function! provider#pythonx#Require(host) abort endfunction function! s:get_python_executable_from_host_var(major_version) abort - return expand(get(g:, 'python'.(a:major_version == 3 ? '3' : '').'_host_prog', ''), v:true) + return expand(get(g:, 'python'.(a:major_version == 3 ? '3' : execute("throw 'unsupported'")).'_host_prog', ''), v:true) endfunction function! s:get_python_candidates(major_version) abort return { - \ 2: ['python2', 'python2.7', 'python2.6', 'python'], - \ 3: ['python3', 'python3.10', 'python3.9', 'python3.8', 'python3.7', - \ 'python3.6', 'python'] + \ 3: ['python3', 'python3.10', 'python3.9', 'python3.8', 'python3.7', 'python'] \ }[a:major_version] endfunction @@ -82,7 +78,7 @@ function! provider#pythonx#CheckForModule(prog, module, major_version) abort return [0, a:prog . ' not found in search path or not executable.'] endif - let min_version = (a:major_version == 2) ? '2.6' : '3.3' + let min_version = '3.7' " Try to load module, and output Python version. " Exit codes: diff --git a/runtime/doc/api.txt b/runtime/doc/api.txt index 8fb6290e50..fe5f9eaf35 100644 --- a/runtime/doc/api.txt +++ b/runtime/doc/api.txt @@ -45,7 +45,7 @@ start with a TCP/IP socket instead, use |--listen| with a TCP-style address: > More endpoints can be started with |serverstart()|. Note that localhost TCP sockets are generally less secure than named pipes, -and can lead to vunerabilities like remote code execution. +and can lead to vulnerabilities like remote code execution. Connecting to the socket is the easiest way a programmer can test the API, which can be done through any msgpack-rpc client library or full-featured @@ -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 @@ -198,7 +198,7 @@ any of these approaches: 2. Start Nvim with |--api-info|. Useful for statically-compiled clients. Example (requires Python "pyyaml" and "msgpack-python" modules): > - nvim --api-info | python -c 'import msgpack, sys, yaml; print yaml.dump(msgpack.unpackb(sys.stdin.read()))' + nvim --api-info | python -c 'import msgpack, sys, yaml; yaml.dump(msgpack.unpackb(sys.stdin.buffer.read()), sys.stdout)' < 3. Use the |api_info()| Vimscript function. > :lua print(vim.inspect(vim.fn.api_info())) @@ -361,7 +361,7 @@ UTF-32 and UTF-16 sizes of the deleted region is also passed as additional arguments {old_utf32_size} and {old_utf16_size}. "on_changedtick" is invoked when |b:changedtick| was incremented but no text -was changed. The parameters recieved are ("changedtick", {buf}, {changedtick}). +was changed. The parameters received are ("changedtick", {buf}, {changedtick}). *api-lua-detach* In-process Lua callbacks can detach by returning `true`. This will detach all @@ -468,7 +468,7 @@ extmark position and enter some text, the extmark migrates forward. > f o o z|b a r line (| = cursor) 4 extmark (after typing "z") -If an extmark is on the last index of a line and you inputsa newline at that +If an extmark is on the last index of a line and you inputs a newline at that point, the extmark will accordingly migrate to the next line: > f o o z b a r| line (| = cursor) @@ -626,6 +626,62 @@ nvim__stats() *nvim__stats()* Return: ~ Map of various internal stats. + *nvim_add_user_command()* +nvim_add_user_command({name}, {command}, {*opts}) + Create a new user command |user-commands| + + {name} is the name of the new command. The name must begin + with an uppercase letter. + + {command} is the replacement text or Lua function to execute. + + Example: > + :call nvim_add_user_command('SayHello', 'echo "Hello world!"', {}) + :SayHello + Hello world! +< + + Parameters: ~ + {name} Name of the new user command. Must begin with + an uppercase letter. + {command} Replacement command to execute when this user + command is executed. When called from Lua, the + command can also be a Lua function. The + function is called with a single table argument + that contains the following keys: + • args: (string) The args passed to the + command, if any |<args>| + • fargs: (table) The args split by unescaped + whitespace (when more than one argument is + allowed), if any |<f-args>| + • bang: (boolean) "true" if the command was + executed with a ! modifier |<bang>| + • line1: (number) The starting line of the + command range |<line1>| + • line2: (number) The final line of the command + range |<line2>| + • range: (number) The number of items in the + command range: 0, 1, or 2 |<range>| + • count: (number) Any count supplied |<count>| + • reg: (string) The optional register, if + specified |<reg>| + • mods: (string) Command modifiers, if any + |<mods>| + {opts} Optional command attributes. See + |command-attributes| for more details. To use + boolean attributes (such as |:command-bang| or + |:command-bar|) set the value to "true". In + addition to the string options listed in + |:command-complete|, the "complete" key also + accepts a Lua function which works like the + "customlist" completion mode + |:command-completion-customlist|. Additional + parameters: + • desc: (string) Used for listing the command + when a Lua function is used for {command}. + • force: (boolean, default true) Override any + previous definition. + nvim_call_atomic({calls}) *nvim_call_atomic()* Calls many API methods atomically. @@ -634,7 +690,7 @@ nvim_call_atomic({calls}) *nvim_call_atomic()* atomically, i.e. without interleaving redraws, RPC requests from other clients, or user interactions (however API methods may trigger autocommands or event processing which - have such side-effects, e.g. |:sleep| may wake timers). + have such side effects, e.g. |:sleep| may wake timers). 2. To minimize RPC overhead (roundtrips) of a sequence of many requests. @@ -656,8 +712,8 @@ nvim_chan_send({chan}, {data}) *nvim_chan_send()* Send data to channel `id` . For a job, it writes it to the stdin of the process. For the stdio channel |channel-stdio|, it writes to Nvim's stdout. For an internal terminal instance - (|nvim_open_term()|) it writes directly to terimal output. See - |channel-bytes| for more information. + (|nvim_open_term()|) it writes directly to terminal output. + See |channel-bytes| for more information. This function writes raw data, not RPC messages. If the channel was created with `rpc=true` then the channel expects @@ -698,7 +754,7 @@ nvim_del_keymap({mode}, {lhs}) *nvim_del_keymap()* |nvim_set_keymap()| nvim_del_mark({name}) *nvim_del_mark()* - Deletes a uppercase/file named mark. See |mark-motions|. + Deletes an uppercase/file named mark. See |mark-motions|. Note: fails with error if a lowercase or buffer local named mark @@ -714,6 +770,12 @@ nvim_del_mark({name}) *nvim_del_mark()* |nvim_buf_del_mark()| |nvim_get_mark()| +nvim_del_user_command({name}) *nvim_del_user_command()* + Delete a user-defined command. + + Parameters: ~ + {name} Name of the command to delete. + nvim_del_var({name}) *nvim_del_var()* Removes a global (g:) variable. @@ -796,7 +858,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()|. @@ -804,23 +866,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. @@ -1061,7 +1125,7 @@ nvim_get_option_value({name}, {*opts}) *nvim_get_option_value()* Parameters: ~ {name} Option name {opts} Optional parameters - • scope: One of 'global' or 'local'. Analagous to + • scope: One of 'global' or 'local'. Analogous to |:setglobal| and |:setlocal|, respectively. Return: ~ @@ -1346,7 +1410,7 @@ nvim_replace_termcodes({str}, {from_part}, {do_lt}, {special}) {from_part} Legacy Vim parameter. Usually true. {do_lt} Also translate <lt>. Ignored if `special` is false. - {special} Replace |keycodes|, e.g. <CR> becomes a "\n" + {special} Replace |keycodes|, e.g. <CR> becomes a "\r" char. See also: ~ @@ -1479,14 +1543,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 @@ -1524,8 +1587,11 @@ 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|. Values are Booleans. Unknown - key is an error. + 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. + Values are Booleans. Unknown key is an error. nvim_set_option({name}, {value}) *nvim_set_option()* Sets the global value of an option. @@ -1545,7 +1611,7 @@ nvim_set_option_value({name}, {value}, {*opts}) {name} Option name {value} New option value {opts} Optional parameters - • scope: One of 'global' or 'local'. Analagous to + • scope: One of 'global' or 'local'. Analogous to |:setglobal| and |:setlocal|, respectively. nvim_set_var({name}, {value}) *nvim_set_var()* @@ -1790,6 +1856,16 @@ nvim__buf_redraw_range({buffer}, {first}, {last}) nvim__buf_stats({buffer}) *nvim__buf_stats()* TODO: Documentation + *nvim_buf_add_user_command()* +nvim_buf_add_user_command({buffer}, {name}, {command}, {*opts}) + Create a new user command |user-commands| in the given buffer. + + Parameters: ~ + {buffer} Buffer handle, or 0 for current buffer. + + See also: ~ + nvim_add_user_command + nvim_buf_attach({buffer}, {send_buffer}, {opts}) *nvim_buf_attach()* Activates buffer-update events on a channel, or as Lua callbacks. @@ -1925,6 +2001,18 @@ nvim_buf_del_mark({buffer}, {name}) *nvim_buf_del_mark()* |nvim_buf_set_mark()| |nvim_del_mark()| + *nvim_buf_del_user_command()* +nvim_buf_del_user_command({buffer}, {name}) + Delete a buffer-local user-defined command. + + Only commands created with |:command-buffer| or + |nvim_buf_add_user_command()| can be deleted with this + function. + + Parameters: ~ + {buffer} Buffer handle, or 0 for current buffer. + {name} Name of the command to delete. + nvim_buf_del_var({buffer}, {name}) *nvim_buf_del_var()* Removes a buffer-scoped (b:) variable @@ -2067,6 +2155,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. @@ -2464,6 +2575,10 @@ nvim_buf_set_extmark({buffer}, {ns_id}, {line}, {col}, {*opts}) • priority: a priority value for the highlight group. For example treesitter highlighting uses a value of 100. + • strict: boolean that indicates extmark should + not be placed if the line or column value is + past the end of the buffer or end of the line + respectively. Defaults to true. Return: ~ Id of the created/updated extmark @@ -2687,7 +2802,7 @@ nvim_win_is_valid({window}) *nvim_win_is_valid()* true if the window is valid, false otherwise nvim_win_set_buf({window}, {buffer}) *nvim_win_set_buf()* - Sets the current buffer in a window, without side-effects + Sets the current buffer in a window, without side effects Attributes: ~ not allowed when |textlock| is active @@ -2994,6 +3109,130 @@ nvim_tabpage_set_var({tabpage}, {name}, {value}) ============================================================================== +Autocmd Functions *api-autocmd* + +nvim_create_augroup({name}, {*opts}) *nvim_create_augroup()* + Create or get an augroup. + + To get an existing augroup ID, do: > + local id = vim.api.nvim_create_augroup(name, { + clear = false + }) +< + + Parameters: ~ + {name} String: The name of the augroup to create + {opts} Parameters + • clear (bool): Whether to clear existing commands + or not. Defaults to true. See |autocmd-groups| + + Return: ~ + opaque value to use with nvim_del_augroup_by_id + +nvim_create_autocmd({event}, {*opts}) *nvim_create_autocmd()* + Create an autocmd. + + Examples: + • event: "pat1,pat2,pat3", + • event: "pat1" + • event: { "pat1" } + • event: { "pat1", "pat2", "pat3" } + + Parameters: ~ + {event} The event or events to register this autocmd + Required keys: event: string | ArrayOf(string) + + Parameters: ~ + {opts} Optional Parameters: + • callback: (string|function) + • (string): The name of the viml function to + execute when triggering this autocmd + • (function): The lua function to execute when + triggering this autocmd + • NOTE: Cannot be used with {command} + + • command: (string) command + • vimscript command + • NOTE: Cannot be used with {callback} Eg. + command = "let g:value_set = v:true" + + • pattern: (string|table) + • pattern or patterns to match against + • defaults to "*". + • NOTE: Cannot be used with {buffer} + + • buffer: (bufnr) + • create a |autocmd-buflocal| autocmd. + • NOTE: Cannot be used with {pattern} + + • group: (string) The augroup name + • once: (boolean) - See |autocmd-once| + • nested: (boolean) - See |autocmd-nested| + • desc: (string) - Description of the autocmd + + Return: ~ + opaque value to use with nvim_del_autocmd + +nvim_del_augroup_by_id({id}) *nvim_del_augroup_by_id()* + Delete an augroup by {id}. {id} can only be returned when + augroup was created with |nvim_create_augroup|. + + NOTE: behavior differs from augroup-delete. + + When deleting an augroup, autocmds contained by this augroup + will also be deleted and cleared. This augroup will no longer + exist + +nvim_del_augroup_by_name({name}) *nvim_del_augroup_by_name()* + Delete an augroup by {name}. + + NOTE: behavior differs from augroup-delete. + + When deleting an augroup, autocmds contained by this augroup + will also be deleted and cleared. This augroup will no longer + exist + +nvim_del_autocmd({id}) *nvim_del_autocmd()* + Delete an autocmd by {id}. Autocmds only return IDs when + created via the API. Will not error if called and no autocmds + match the {id}. + + Parameters: ~ + {id} Integer The ID returned by nvim_create_autocmd + +nvim_do_autocmd({event}, {*opts}) *nvim_do_autocmd()* + Do one autocmd. + + Parameters: ~ + {event} The event or events to execute + {opts} Optional Parameters: + • buffer (number) - buffer number + • NOTE: Cannot be used with {pattern} + + • pattern (string|table) - optional, defaults to + "*". + • NOTE: Cannot be used with {buffer} + + • group (string) - autocmd group name + • modeline (boolean) - Default true, see + |<nomodeline>| + +nvim_get_autocmds({*opts}) *nvim_get_autocmds()* + Get autocmds that match the requirements passed to {opts}. + + Parameters: ~ + {opts} Optional Parameters: + • event : Name or list of name of events to match + against + • group (string): Name of group to match against + • pattern: Pattern or list of patterns to match + against + + Return: ~ + A list of autocmds that match + + +============================================================================== UI Functions *api-ui* nvim_ui_attach({width}, {height}, {options}) *nvim_ui_attach()* diff --git a/runtime/doc/autocmd.txt b/runtime/doc/autocmd.txt index 0cd0d1ce0e..4b8c07fde4 100644 --- a/runtime/doc/autocmd.txt +++ b/runtime/doc/autocmd.txt @@ -40,10 +40,10 @@ effects. Be careful not to destroy your text. 2. Defining autocommands *autocmd-define* *:au* *:autocmd* -:au[tocmd] [group] {event} {pat} [++once] [++nested] {cmd} +:au[tocmd] [group] {event} {aupat} [++once] [++nested] {cmd} Add {cmd} to the list of commands that Vim will execute automatically on {event} for a file matching - {pat} |autocmd-pattern|. + {aupat} |autocmd-pattern|. Note: A quote character is seen as argument to the :autocmd and won't start a comment. Nvim always adds {cmd} after existing autocommands so @@ -119,19 +119,19 @@ prompt. When one command outputs two messages this can happen anyway. ============================================================================== 3. Removing autocommands *autocmd-remove* -:au[tocmd]! [group] {event} {pat} [++once] [++nested] {cmd} +:au[tocmd]! [group] {event} {aupat} [++once] [++nested] {cmd} Remove all autocommands associated with {event} and - {pat}, and add the command {cmd}. + {aupat}, and add the command {cmd}. See |autocmd-once| for [++once]. See |autocmd-nested| for [++nested]. -:au[tocmd]! [group] {event} {pat} +:au[tocmd]! [group] {event} {aupat} Remove all autocommands associated with {event} and - {pat}. + {aupat}. -:au[tocmd]! [group] * {pat} - Remove all autocommands associated with {pat} for all - events. +:au[tocmd]! [group] * {aupat} + Remove all autocommands associated with {aupat} for + all events. :au[tocmd]! [group] {event} Remove ALL autocommands for {event}. @@ -151,12 +151,12 @@ with ":augroup"); otherwise, Vim uses the group defined with [group]. ============================================================================== 4. Listing autocommands *autocmd-list* -:au[tocmd] [group] {event} {pat} +:au[tocmd] [group] {event} {aupat} Show the autocommands associated with {event} and - {pat}. + {aupat}. -:au[tocmd] [group] * {pat} - Show the autocommands associated with {pat} for all +:au[tocmd] [group] * {aupat} + Show the autocommands associated with {aupat} for all events. :au[tocmd] [group] {event} @@ -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. @@ -844,6 +855,9 @@ RecordingLeave When a macro stops recording. register. |reg_recorded()| is only updated after this event. + Sets these |v:event| keys: + regcontents + regname *SessionLoadPost* SessionLoadPost After loading the session file created using the |:mksession| command. @@ -1076,16 +1090,16 @@ WinScrolled After scrolling the viewport of the current ============================================================================== -6. Patterns *autocmd-pattern* *{pat}* +6. Patterns *autocmd-pattern* *{aupat}* -The {pat} argument can be a comma separated list. This works as if the -command was given with each pattern separately. Thus this command: > +The {aupat} argument of `:autocmd` can be a comma separated list. This works +as if the command was given with each pattern separately. Thus this command: > :autocmd BufRead *.txt,*.info set et Is equivalent to: > :autocmd BufRead *.txt set et :autocmd BufRead *.info set et -The file pattern {pat} is tested for a match against the file name in one of +The file pattern {aupat} is tested for a match against the file name in one of two ways: 1. When there is no '/' in the pattern, Vim checks for a match against only the tail part of the file name (without its leading directory path). @@ -1396,7 +1410,7 @@ Examples for reading and writing compressed files: > : autocmd BufReadPre,FileReadPre *.gz set bin : autocmd BufReadPost,FileReadPost *.gz '[,']!gunzip : autocmd BufReadPost,FileReadPost *.gz set nobin - : autocmd BufReadPost,FileReadPost *.gz execute ":doautocmd BufReadPost " . expand("%:r") + : autocmd BufReadPost,FileReadPost *.gz execute ":doautocmd BufReadPost " .. expand("%:r") : autocmd BufWritePost,FileWritePost *.gz !mv <afile> <afile>:r : autocmd BufWritePost,FileWritePost *.gz !gzip <afile>:r @@ -1495,7 +1509,7 @@ To insert the current date and time in a *.html file when writing it: > : else : let l = line("$") : endif - : exe "1," . l . "g/Last modified: /s/Last modified: .*/Last modified: " . + : exe "1," .. l .. "g/Last modified: /s/Last modified: .*/Last modified: " .. : \ strftime("%Y %b %d") :endfun diff --git a/runtime/doc/builtin.txt b/runtime/doc/builtin.txt new file mode 100644 index 0000000000..03a5f98c6d --- /dev/null +++ b/runtime/doc/builtin.txt @@ -0,0 +1,8974 @@ +*builtin.txt* Nvim + + + VIM REFERENCE MANUAL by Bram Moolenaar + + +Builtin functions *builtin-functions* + +1. Overview |builtin-function-list| +2. Details |builtin-function-details| +3. Matching a pattern in a String |string-match| + +============================================================================== +1. Overview *builtin-function-list* + +Use CTRL-] on the function name to jump to the full explanation. + +USAGE RESULT DESCRIPTION ~ + +abs({expr}) Float or Number absolute value of {expr} +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}, {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 +argv({nr} [, {winid}]) String {nr} entry of the argument list +argv([-1, {winid}]) List the argument list +asin({expr}) Float arc sine of {expr} +assert_beeps({cmd}) Number assert {cmd} causes a beep +assert_equal({exp}, {act} [, {msg}]) + Number assert {exp} is equal to {act} +assert_equalfile({fname-one}, {fname-two} [, {msg}]) + Number assert file contents are equal +assert_exception({error} [, {msg}]) + Number assert {error} is in v:exception +assert_fails({cmd} [, {error}]) Number assert {cmd} fails +assert_false({actual} [, {msg}]) + Number assert {actual} is false +assert_inrange({lower}, {upper}, {actual} [, {msg}]) + Number assert {actual} is inside the range +assert_match({pat}, {text} [, {msg}]) + Number assert {pat} matches {text} +assert_nobeep({cmd}) Number assert {cmd} does not cause a beep +assert_notequal({exp}, {act} [, {msg}]) + Number assert {exp} is not equal {act} +assert_notmatch({pat}, {text} [, {msg}]) + Number assert {pat} not matches {text} +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({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 +bufadd({name}) Number add a buffer to the buffer list +bufexists({expr}) Number |TRUE| if buffer {expr} exists +buflisted({expr}) Number |TRUE| if buffer {expr} is listed +bufload({expr}) Number load buffer {expr} if not loaded yet +bufloaded({expr}) Number |TRUE| if buffer {expr} is loaded +bufname([{expr}]) String Name of the buffer {expr} +bufnr([{expr} [, {create}]]) Number Number of the buffer {expr} +bufwinid({expr}) Number |window-ID| of buffer {expr} +bufwinnr({expr}) Number window number of buffer {expr} +byte2line({byte}) Number line number at byte count {byte} +byteidx({expr}, {nr}) Number byte index of {nr}'th char in {expr} +byteidxcomp({expr}, {nr}) Number byte index of {nr}'th char in {expr} +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 +chansend({id}, {data}) Number Writes {data} to channel +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} +chdir({dir}) String change current working directory +cindent({lnum}) Number C indent for line {lnum} +clearmatches([{win}]) none clear all matches +col({expr}) Number column byte index of cursor or mark +complete({startcol}, {matches}) none set Insert mode completion +complete_add({expr}) Number add completion match +complete_check() Number check for key typed during completion +complete_info([{what}]) Dict get current completion information +confirm({msg} [, {choices} [, {default} [, {type}]]]) + Number number of choice picked by user +copy({expr}) any make a shallow copy of {expr} +cos({expr}) Float cosine of {expr} +cosh({expr}) Float hyperbolic cosine of {expr} +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} +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} +ctxsize() Number return |context-stack| size +cursor({lnum}, {col} [, {off}]) + Number move cursor to {lnum}, {col}, {off} +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}]) + Number delete lines from buffer {buf} +dictwatcheradd({dict}, {pattern}, {callback}) + Start watching a dictionary +dictwatcherdel({dict}, {pattern}, {callback}) + Stop watching a dictionary +did_filetype() Number |TRUE| if FileType autocommand event used +diff_filler({lnum}) Number diff filler lines about {lnum} +diff_hlID({lnum}, {col}) Number diff highlighting at {lnum}/{col} +empty({expr}) Number |TRUE| if {expr} is empty +environ() Dict return environment variables +escape({string}, {chars}) String escape {chars} in {string} with '\' +eval({string}) any evaluate {string} into its value +eventhandler() Number |TRUE| if inside an event handler +executable({expr}) Number 1 if executable {expr} exists +execute({command}) String execute and capture output of {command} +exepath({expr}) String full path of the command {expr} +exists({expr}) Number |TRUE| if {expr} exists +extend({expr1}, {expr2} [, {expr3}]) + List/Dict insert items of {expr2} into {expr1} +exp({expr}) Float exponential of {expr} +expand({expr} [, {nosuf} [, {list}]]) + any expand special keywords in {expr} +expandcmd({expr}) String expand {expr} like with `:edit` +feedkeys({string} [, {mode}]) Number add key sequence to typeahead buffer +filereadable({file}) Number |TRUE| if {file} is a readable file +filewritable({file}) Number |TRUE| if {file} is a writable file +filter({expr1}, {expr2}) List/Dict remove items from {expr1} where + {expr2} is 0 +finddir({name} [, {path} [, {count}]]) + String find directory {name} in {path} +findfile({name} [, {path} [, {count}]]) + String find file {name} in {path} +flatten({list} [, {maxdepth}]) List flatten {list} up to {maxdepth} levels +float2nr({expr}) Number convert Float {expr} to a Number +floor({expr}) Float round {expr} down +fmod({expr1}, {expr2}) Float remainder of {expr1} / {expr2} +fnameescape({fname}) String escape special characters in {fname} +fnamemodify({fname}, {mods}) String modify file name +foldclosed({lnum}) Number first line of fold at {lnum} if closed +foldclosedend({lnum}) Number last line of fold at {lnum} if closed +foldlevel({lnum}) Number fold level at {lnum} +foldtext() String line displayed for closed fold +foldtextresult({lnum}) String text for closed fold at {lnum} +foreground() Number bring the Vim window to the foreground +fullcommand({name}) String get full command from {name} +funcref({name} [, {arglist}] [, {dict}]) + Funcref reference to function {name} +function({name} [, {arglist}] [, {dict}]) + Funcref named reference to function {name} +garbagecollect([{atexit}]) none free memory, breaking cyclic references +get({list}, {idx} [, {def}]) any get item {idx} from {list} or {def} +get({dict}, {key} [, {def}]) any get item {key} from {dict} or {def} +get({func}, {what}) any get property of funcref/partial {func} +getbufinfo([{buf}]) List information about buffers +getbufline({buf}, {lnum} [, {end}]) + List lines {lnum} to {end} of buffer {buf} +getbufvar({buf}, {varname} [, {def}]) + any variable {varname} in buffer {buf} +getchangelist([{buf}]) List list of change list items +getchar([expr]) Number or String + get one character from the user +getcharmod() Number modifiers for the last typed character +getcharpos({expr}) List position of cursor, mark, etc. +getcharsearch() Dict last character search +getcharstr([expr]) String get one character from the user +getcmdline() String return the current command-line +getcmdpos() Number return cursor position in command-line +getcmdtype() String return current command-line type +getcmdwintype() String return current command-line window type +getcompletion({pat}, {type} [, {filtered}]) + List list of cmdline completion matches +getcurpos([{winnr}]) List position of the cursor +getcursorcharpos([{winnr}]) List character position of the cursor +getcwd([{winnr} [, {tabnr}]]) String get the current working directory +getenv({name}) String return environment variable +getfontname([{name}]) String name of font being used +getfperm({fname}) String file permissions of file {fname} +getfsize({fname}) Number size in bytes of file {fname} +getftime({fname}) Number last modification time of file +getftype({fname}) String description of type of file {fname} +getjumplist([{winnr} [, {tabnr}]]) + List list of jump list items +getline({lnum}) String line {lnum} of current buffer +getline({lnum}, {end}) List lines {lnum} to {end} of current buffer +getloclist({nr}) List list of location list items +getloclist({nr}, {what}) Dict get specific location list properties +getmarklist([{buf}]) List list of global/local marks +getmatches([{win}]) List list of current matches +getmousepos() Dict last known mouse position +getpid() Number process ID of Vim +getpos({expr}) List position of cursor, mark, etc. +getqflist() List list of quickfix items +getqflist({what}) Dict get specific quickfix list properties +getreg([{regname} [, 1 [, {list}]]]) + String or List contents of a register +getreginfo([{regname}]) Dict information about a register +getregtype([{regname}]) String type of a register +gettabinfo([{expr}]) List list of tab pages +gettabvar({nr}, {varname} [, {def}]) + any variable {varname} in tab {nr} or {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 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 +getwinvar({nr}, {varname} [, {def}]) + any variable {varname} in window {nr} +glob({expr} [, {nosuf} [, {list} [, {alllinks}]]]) + any expand file wildcards in {expr} +glob2regpat({expr}) String convert a glob pat into a search pat +globpath({path}, {expr} [, {nosuf} [, {list} [, {alllinks}]]]) + String do glob({expr}) for all dirs in {path} +has({feature}) Number |TRUE| if feature {feature} supported +has_key({dict}, {key}) Number |TRUE| if {dict} has entry {key} +haslocaldir([{winnr} [, {tabnr}]]) + Number |TRUE| if the window executed |:lcd| or + the tab executed |:tcd| +hasmapto({what} [, {mode} [, {abbr}]]) + Number |TRUE| if mapping to {what} exists +histadd({history}, {item}) String add an item to a history +histdel({history} [, {item}]) String remove an item from a history +histget({history} [, {index}]) String get the item {index} from a history +histnr({history}) Number highest index of a history +hlexists({name}) Number |TRUE| if highlight group {name} exists +hlID({name}) Number syntax ID of highlight group {name} +hostname() String name of the machine Vim is running on +iconv({expr}, {from}, {to}) String convert encoding of {expr} +indent({lnum}) Number indent of line {lnum} +index({object}, {expr} [, {start} [, {ic}]]) + Number index in {object} where {expr} appears +input({prompt} [, {text} [, {completion}]]) + String get input from the user +inputlist({textlist}) Number let the user pick from a choice list +inputrestore() Number restore typeahead +inputsave() Number save and clear typeahead +inputsecret({prompt} [, {text}]) + String like input() but hiding the text +insert({object}, {item} [, {idx}]) + List insert {item} in {object} [before {idx}] +interrupt() none interrupt script execution +invert({expr}) Number bitwise invert +isdirectory({directory}) Number |TRUE| if {directory} is a directory +isinf({expr}) Number determine if {expr} is infinity value + (positive or negative) +islocked({expr}) Number |TRUE| if {expr} is locked +isnan({expr}) Number |TRUE| if {expr} is NaN +id({expr}) String identifier of the container +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 +jobstop({id}) Number Stops a job +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 +keys({dict}) List keys in {dict} +len({expr}) Number the length of {expr} +libcall({lib}, {func}, {arg}) String call {func} in library {lib} with {arg} +libcallnr({lib}, {func}, {arg}) Number idem, but return a Number +line({expr} [, {winid}]) Number line nr of cursor, last line or mark +line2byte({lnum}) Number byte count of line {lnum} +lispindent({lnum}) Number Lisp indent for line {lnum} +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 +map({expr1}, {expr2}) List/Dict change each item in {expr1} to {expr} +maparg({name} [, {mode} [, {abbr} [, {dict}]]]) + String or Dict + rhs of mapping {name} in mode {mode} +mapcheck({name} [, {mode} [, {abbr}]]) + String check for mappings matching {name} +match({expr}, {pat} [, {start} [, {count}]]) + Number position where {pat} matches in {expr} +matchadd({group}, {pattern} [, {priority} [, {id} [, {dict}]]]) + Number highlight {pattern} with {group} +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}]]) + Number position where {pat} ends in {expr} +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}]]) + String {count}'th match of {pat} in {expr} +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} +min({expr}) Number minimum value of items in {expr} +mkdir({name} [, {path} [, {prot}]]) + Number create directory {name} +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} +nvim_...({args}...) any call nvim |api| functions +or({expr}, {expr}) Number bitwise OR +pathshorten({expr} [, {len}]) String shorten directory names in a path +perleval({expr}) any evaluate |perl| expression +pow({x}, {y}) Float {x} to the power of {y} +prevnonblank({lnum}) Number line nr of non-blank line <= {lnum} +printf({fmt}, {expr1}...) String format text +prompt_getprompt({buf}) String get prompt text +prompt_setcallback({buf}, {expr}) none set prompt callback function +prompt_setinterrupt({buf}, {text}) none set prompt interrupt function +prompt_setprompt({buf}, {text}) none set prompt text +pum_getpos() Dict position and size of pum if visible +pumvisible() Number whether popup menu is visible +pyeval({expr}) any evaluate |Python| expression +py3eval({expr}) any evaluate |python3| expression +pyxeval({expr}) any evaluate |python_x| expression +rand([{expr}]) Number get pseudo-random number +range({expr} [, {max} [, {stride}]]) + List items from {expr} to {max} +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 +reltime([{start} [, {end}]]) List get time value +reltimefloat({time}) Float turn the time value into a Float +reltimestr({time}) String turn time value into a String +remote_expr({server}, {string} [, {idvar} [, {timeout}]]) + String send expression +remote_foreground({server}) Number bring Vim server to the foreground +remote_peek({serverid} [, {retvar}]) + Number check for reply string +remote_read({serverid} [, {timeout}]) + String read reply string +remote_send({server}, {string} [, {idvar}]) + String send key sequence +remote_startserver({name}) none become server {name} +remove({list}, {idx} [, {end}]) any/List + remove items {idx}-{end} from {list} +remove({blob}, {idx} [, {end}]) Number/Blob + remove bytes {idx}-{end} from {blob} +remove({dict}, {key}) any remove entry {key} from {dict} +rename({from}, {to}) Number rename (move) file from {from} to {to} +repeat({expr}, {count}) String repeat {expr} {count} times +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}...]) + Sends an |RPC| notification to {channel} +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 +screenchars({row}, {col}) List List of characters at screen position +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} [, {skip}]]]]) + Number search for {pattern} +searchcount([{options}]) Dict Get or update the last search count +searchdecl({name} [, {global} [, {thisblock}]]) + Number search for variable declaration +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} [, {skip}]]]]) + List search for {pattern} +server2client({clientid}, {string}) + Number send reply string +serverlist() String get a list of available servers +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} +setcharsearch({dict}) Dict set character search from {dict} +setcmdpos({pos}) Number set cursor position in command-line +setcursorcharpos({list}) Number move cursor to position in {list} +setenv({name}, {val}) none set environment variable +setfperm({fname}, {mode} Number set {fname} file permissions to {mode} +setline({lnum}, {line}) Number set line {lnum} to {line} +setloclist({nr}, {list} [, {action}]) + Number modify location list using {list} +setloclist({nr}, {list}, {action}, {what}) + Number modify specific location list props +setmatches({list} [, {win}]) Number restore a list of matches +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 +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} +settagstack({nr}, {dict} [, {action}]) + Number modify tag stack using {dict} +setwinvar({nr}, {varname}, {val}) set {varname} in window {nr} to {val} +sha256({string}) String SHA256 checksum of {string} +shellescape({string} [, {special}]) + String escape {string} for use as shell + command argument +shiftwidth([{col}]) Number effective value of 'shiftwidth' +sign_define({name} [, {dict}]) Number define or update a sign +sign_define({list}) List define or update a list of signs +sign_getdefined([{name}]) List get a list of defined signs +sign_getplaced([{buf} [, {dict}]]) + List get a list of placed signs +sign_jump({id}, {group}, {buf}) + Number jump to a sign +sign_place({id}, {group}, {name}, {buf} [, {dict}]) + Number place a sign +sign_placelist({list}) List place a list of signs +sign_undefine([{name}]) Number undefine a sign +sign_undefine({list}) List undefine a list of signs +sign_unplace({group} [, {dict}]) + Number unplace a sign +sign_unplacelist({list}) List unplace a list of signs +simplify({filename}) String simplify filename as much as possible +sin({expr}) Float sine of {expr} +sinh({expr}) Float hyperbolic sine of {expr} +sockconnect({mode}, {address} [, {opts}]) + Number Connects to socket +sort({list} [, {func} [, {dict}]]) + List sort {list}, using {func} to compare +soundfold({word}) String sound-fold {word} +spellbadword() String badly spelled word at cursor +spellsuggest({word} [, {max} [, {capital}]]) + List spelling suggestions +split({expr} [, {pat} [, {keepempty}]]) + List make |List| from {pat} separated {expr} +sqrt({expr}) Float square root of {expr} +srand([{expr}]) List get seed for |rand()| +stdioopen({dict}) Number open stdio in a headless instance. +stdpath({what}) String/List returns the standard path(s) for {what} +str2float({expr} [, {quoted}]) Float convert String to Float +str2list({expr} [, {utf8}]) List convert each character of {expr} to + ASCII/UTF-8 value +str2nr({expr} [, {base} [, {quoted}]]) + Number convert String to Number +strchars({expr} [, {skipcc}]) Number character length of the String {expr} +strcharpart({str}, {start} [, {len}]) + String {len} characters of {str} at + character {start} +strdisplaywidth({expr} [, {col}]) Number display length of the String {expr} +strftime({format} [, {time}]) String format time with a specified format +strgetchar({str}, {index}) Number get char {index} from {str} +stridx({haystack}, {needle} [, {start}]) + Number index of {needle} in {haystack} +string({expr}) String String representation of {expr} value +strlen({expr}) Number length of the String {expr} +strpart({str}, {start} [, {len} [, {chars}]]) + String {len} bytes/chars of {str} at + byte {start} +strptime({format}, {timestring}) + Number Convert {timestring} to unix timestamp +strridx({haystack}, {needle} [, {start}]) + Number last index of {needle} in {haystack} +strtrans({expr}) String translate string to make it printable +strwidth({expr}) Number display cell length of the String {expr} +submatch({nr} [, {list}]) String or List + specific match in ":s" or substitute() +substitute({expr}, {pat}, {sub}, {flags}) + String all {pat} in {expr} replaced with {sub} +swapinfo({fname}) Dict information about swap file {fname} +swapname({buf}) String swap file of buffer {buf} +synID({lnum}, {col}, {trans}) Number syntax ID at {lnum} and {col} +synIDattr({synID}, {what} [, {mode}]) + String attribute {what} of syntax ID {synID} +synIDtrans({synID}) Number translated syntax ID of {synID} +synconcealed({lnum}, {col}) List info about concealing +synstack({lnum}, {col}) List stack of syntax IDs at {lnum} and {col} +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}]) + Number number of current window in tab page +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} +tempname() String name for a temporary file +test_garbagecollect_now() none free memory right now for testing +timer_info([{id}]) List information about timers +timer_pause({id}, {pause}) none pause or unpause a timer +timer_start({time}, {callback} [, {options}]) + Number create a timer +timer_stop({timer}) none stop a timer +timer_stopall() none stop all timers +tolower({expr}) String the String {expr} switched to lowercase +toupper({expr}) String the String {expr} switched to uppercase +tr({src}, {fromstr}, {tostr}) String translate chars of {src} in {fromstr} + to chars in {tostr} +trim({text} [, {mask} [, {dir}]]) + String trim characters in {mask} from {text} +trunc({expr}) Float truncate Float {expr} +type({name}) Number type of variable {name} +undofile({name}) String undo file name for {name} +undotree() List undo file tree +uniq({list} [, {func} [, {dict}]]) + List remove adjacent duplicates from a list +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}]) + Number Wait until {condition} is satisfied +wildmenumode() Number whether 'wildmenu' mode is active +win_execute({id}, {command} [, {silent}]) + String execute {command} in window {id} +win_findbuf({bufnr}) List find windows containing {bufnr} +win_getid([{win} [, {tab}]]) Number get |window-ID| for {win} in {tab} +win_gettype([{nr}]) String type of window {nr} +win_gotoid({expr}) Number go to |window-ID| {expr} +win_id2tabwin({expr}) List get tab and window nr from |window-ID| +win_id2win({expr}) Number get window nr from |window-ID| +win_move_separator({nr}) Number move window vertical separator +win_move_statusline({nr}) Number move window status line +win_screenpos({nr}) List get screen position of window {nr} +win_splitmove({nr}, {target} [, {options}]) + Number move window {nr} to split of {target} +winbufnr({nr}) Number buffer number of window {nr} +wincol() Number window column of the cursor +windowsversion() String MS-Windows OS version +winheight({nr}) Number height of window {nr} +winlayout([{tabnr}]) List layout of windows in tab {tabnr} +winline() Number window line of the cursor +winnr([{expr}]) Number number of current window +winrestcmd() String returns command to restore window sizes +winrestview({dict}) none restore view of current window +winsaveview() Dict save view of current window +winwidth({nr}) Number width of window {nr} +wordcount() Dict get byte/char/word statistics +writefile({object}, {fname} [, {flags}]) + Number write |Blob| or |List| of lines to file +xor({expr}, {expr}) Number bitwise XOR + +============================================================================== +2. Details *builtin-function-details* + +Not all functions are here, some have been moved to a help file covering the +specific functionality. + +abs({expr}) *abs()* + Return the absolute value of {expr}. When {expr} evaluates to + a |Float| abs() returns a |Float|. When {expr} can be + converted to a |Number| abs() returns a |Number|. Otherwise + abs() gives an error message and returns -1. + Examples: > + echo abs(1.456) +< 1.456 > + echo abs(-5.456) +< 5.456 > + echo abs(-4) +< 4 + + Can also be used as a |method|: > + Compute()->abs() + +acos({expr}) *acos()* + Return the arc cosine of {expr} measured in radians, as a + |Float| in the range of [0, pi]. + {expr} must evaluate to a |Float| or a |Number| in the range + [-1, 1]. + Examples: > + :echo acos(0) +< 1.570796 > + :echo acos(-0.5) +< 2.094395 + + Can also be used as a |method|: > + Compute()->acos() + +add({object}, {expr}) *add()* + Append the item {expr} to |List| or |Blob| {object}. Returns + the resulting |List| or |Blob|. Examples: > + :let alist = add([1, 2, 3], item) + :call add(mylist, "woodstock") +< Note that when {expr} is a |List| it is appended as a single + item. Use |extend()| to concatenate |Lists|. + When {object} is a |Blob| then {expr} must be a number. + Use |insert()| to add an item at another position. + + Can also be used as a |method|: > + mylist->add(val1)->add(val2) + +and({expr}, {expr}) *and()* + Bitwise AND on the two arguments. The arguments are converted + to a number. A List, Dict or Float argument causes an error. + Example: > + :let flag = and(bits, 0x80) +< Can also be used as a |method|: > + :let flag = bits->and(0x80) + +api_info() *api_info()* + Returns Dictionary of |api-metadata|. + + View it in a nice human-readable format: > + :lua print(vim.inspect(vim.fn.api_info())) + +append({lnum}, {text}) *append()* + When {text} is a |List|: Append each item of the |List| as a + text line below line {lnum} in the current buffer. + Otherwise append {text} as one text line below line {lnum} in + the current buffer. + {lnum} can be zero to insert a line before the first one. + {lnum} is used like with |getline()|. + Returns 1 for failure ({lnum} out of range or out of memory), + 0 for success. Example: > + :let failed = append(line('$'), "# THE END") + :let failed = append(0, ["Chapter 1", "the beginning"]) + +< Can also be used as a |method| after a List: > + mylist->append(lnum) + +appendbufline({buf}, {lnum}, {text}) *appendbufline()* + Like |append()| but append the text in buffer {expr}. + + This function works only for loaded buffers. First call + |bufload()| if needed. + + For the use of {buf}, see |bufname()|. + + {lnum} is used like with |append()|. Note that using |line()| + would use the current buffer, not the one appending to. + Use "$" to append at the end of the buffer. + + On success 0 is returned, on failure 1 is returned. + + If {buf} is not a valid buffer or {lnum} is not valid, an + error message is given. Example: > + :let failed = appendbufline(13, 0, "# THE START") +< + Can also be used as a |method| after a List: > + mylist->appendbufline(buf, lnum) + +argc([{winid}]) *argc()* + The result is the number of files in the argument list. See + |arglist|. + If {winid} is not supplied, the argument list of the current + window is used. + If {winid} is -1, the global argument list is used. + Otherwise {winid} specifies the window of which the argument + list is used: either the window number or the window ID. + Returns -1 if the {winid} argument is invalid. + + *argidx()* +argidx() The result is the current index in the argument list. 0 is + the first file. argc() - 1 is the last one. See |arglist|. + + *arglistid()* +arglistid([{winnr} [, {tabnr}]]) + Return the argument list ID. This is a number which + identifies the argument list being used. Zero is used for the + global argument list. See |arglist|. + Returns -1 if the arguments are invalid. + + Without arguments use the current window. + With {winnr} only use this window in the current tab page. + With {winnr} and {tabnr} use the window in the specified tab + page. + {winnr} can be the window number or the |window-ID|. + + *argv()* +argv([{nr} [, {winid}]]) + The result is the {nr}th file in the argument list. See + |arglist|. "argv(0)" is the first one. Example: > + :let i = 0 + :while i < argc() + : let f = escape(fnameescape(argv(i)), '.') + : exe 'amenu Arg.' .. f .. ' :e ' .. f .. '<CR>' + : let i = i + 1 + :endwhile +< Without the {nr} argument, or when {nr} is -1, a |List| with + the whole |arglist| is returned. + + The {winid} argument specifies the window ID, see |argc()|. + For the Vim command line arguments see |v:argv|. + +asin({expr}) *asin()* + Return the arc sine of {expr} measured in radians, as a |Float| + in the range of [-pi/2, pi/2]. + {expr} must evaluate to a |Float| or a |Number| in the range + [-1, 1]. + Examples: > + :echo asin(0.8) +< 0.927295 > + :echo asin(-0.5) +< -0.523599 + + Can also be used as a |method|: > + Compute()->asin() + + +assert_ functions are documented here: |assert-functions-details| + + +atan({expr}) *atan()* + Return the principal value of the arc tangent of {expr}, in + the range [-pi/2, +pi/2] radians, as a |Float|. + {expr} must evaluate to a |Float| or a |Number|. + Examples: > + :echo atan(100) +< 1.560797 > + :echo atan(-4.01) +< -1.326405 + + Can also be used as a |method|: > + Compute()->atan() + +atan2({expr1}, {expr2}) *atan2()* + Return the arc tangent of {expr1} / {expr2}, measured in + radians, as a |Float| in the range [-pi, pi]. + {expr1} and {expr2} must evaluate to a |Float| or a |Number|. + Examples: > + :echo atan2(-1, 1) +< -0.785398 > + :echo atan2(1, -1) +< 2.356194 + + Can also be used as a |method|: > + Compute()->atan2(1) + + *browse()* +browse({save}, {title}, {initdir}, {default}) + Put up a file requester. This only works when "has("browse")" + returns |TRUE| (only in some GUI versions). + The input fields are: + {save} when |TRUE|, select file to write + {title} title for the requester + {initdir} directory to start browsing in + {default} default file name + An empty string is returned when the "Cancel" button is hit, + something went wrong, or browsing is not possible. + + *browsedir()* +browsedir({title}, {initdir}) + Put up a directory requester. This only works when + "has("browse")" returns |TRUE| (only in some GUI versions). + On systems where a directory browser is not supported a file + browser is used. In that case: select a file in the directory + to be used. + The input fields are: + {title} title for the requester + {initdir} directory to start browsing in + When the "Cancel" button is hit, something went wrong, or + browsing is not possible, an empty string is returned. + +bufadd({name}) *bufadd()* + Add a buffer to the buffer list with String {name}. + If a buffer for file {name} already exists, return that buffer + number. Otherwise return the buffer number of the newly + created buffer. When {name} is an empty string then a new + buffer is always created. + The buffer will not have 'buflisted' set and not be loaded + yet. To add some text to the buffer use this: > + let bufnr = bufadd('someName') + call bufload(bufnr) + call setbufline(bufnr, 1, ['some', 'text']) +< Can also be used as a |method|: > + let bufnr = 'somename'->bufadd() + +bufexists({buf}) *bufexists()* + The result is a Number, which is |TRUE| if a buffer called + {buf} exists. + If the {buf} argument is a number, buffer numbers are used. + Number zero is the alternate buffer for the current window. + + If the {buf} argument is a string it must match a buffer name + exactly. The name can be: + - Relative to the current directory. + - A full path. + - The name of a buffer with 'buftype' set to "nofile". + - A URL name. + Unlisted buffers will be found. + Note that help files are listed by their short name in the + output of |:buffers|, but bufexists() requires using their + long name to be able to find them. + bufexists() may report a buffer exists, but to use the name + with a |:buffer| command you may need to use |expand()|. Esp + for MS-Windows 8.3 names in the form "c:\DOCUME~1" + Use "bufexists(0)" to test for the existence of an alternate + file name. + + Can also be used as a |method|: > + let exists = 'somename'->bufexists() + +buflisted({buf}) *buflisted()* + The result is a Number, which is |TRUE| if a buffer called + {buf} exists and is listed (has the 'buflisted' option set). + The {buf} argument is used like with |bufexists()|. + + Can also be used as a |method|: > + let listed = 'somename'->buflisted() + +bufload({buf}) *bufload()* + Ensure the buffer {buf} is loaded. When the buffer name + refers to an existing file then the file is read. Otherwise + the buffer will be empty. If the buffer was already loaded + then there is no change. + If there is an existing swap file for the file of the buffer, + there will be no dialog, the buffer will be loaded anyway. + The {buf} argument is used like with |bufexists()|. + + Can also be used as a |method|: > + eval 'somename'->bufload() + +bufloaded({buf}) *bufloaded()* + The result is a Number, which is |TRUE| if a buffer called + {buf} exists and is loaded (shown in a window or hidden). + The {buf} argument is used like with |bufexists()|. + + Can also be used as a |method|: > + let loaded = 'somename'->bufloaded() + +bufname([{buf}]) *bufname()* + The result is the name of a buffer. Mostly as it is displayed + by the `:ls` command, but not using special names such as + "[No Name]". + If {buf} is omitted the current buffer is used. + If {buf} is a Number, that buffer number's name is given. + Number zero is the alternate buffer for the current window. + If {buf} is a String, it is used as a |file-pattern| to match + with the buffer names. This is always done like 'magic' is + set and 'cpoptions' is empty. When there is more than one + match an empty string is returned. + "" or "%" can be used for the current buffer, "#" for the + alternate buffer. + A full match is preferred, otherwise a match at the start, end + or middle of the buffer name is accepted. If you only want a + full match then put "^" at the start and "$" at the end of the + pattern. + Listed buffers are found first. If there is a single match + with a listed buffer, that one is returned. Next unlisted + buffers are searched for. + If the {buf} is a String, but you want to use it as a buffer + number, force it to be a Number by adding zero to it: > + :echo bufname("3" + 0) +< Can also be used as a |method|: > + echo bufnr->bufname() + +< If the buffer doesn't exist, or doesn't have a name, an empty + string is returned. > + bufname("#") alternate buffer name + bufname(3) name of buffer 3 + bufname("%") name of current buffer + bufname("file2") name of buffer where "file2" matches. +< + *bufnr()* +bufnr([{buf} [, {create}]]) + The result is the number of a buffer, as it is displayed by + the `:ls` command. For the use of {buf}, see |bufname()| + above. + If the buffer doesn't exist, -1 is returned. Or, if the + {create} argument is present and TRUE, a new, unlisted, + buffer is created and its number is returned. + bufnr("$") is the last buffer: > + :let last_buffer = bufnr("$") +< The result is a Number, which is the highest buffer number + of existing buffers. Note that not all buffers with a smaller + number necessarily exist, because ":bwipeout" may have removed + them. Use bufexists() to test for the existence of a buffer. + + Can also be used as a |method|: > + echo bufref->bufnr() + +bufwinid({buf}) *bufwinid()* + The result is a Number, which is the |window-ID| of the first + window associated with buffer {buf}. For the use of {buf}, + see |bufname()| above. If buffer {buf} doesn't exist or + there is no such window, -1 is returned. Example: > + + echo "A window containing buffer 1 is " .. (bufwinid(1)) +< + Only deals with the current tab page. + + Can also be used as a |method|: > + FindBuffer()->bufwinid() + +bufwinnr({buf}) *bufwinnr()* + Like |bufwinid()| but return the window number instead of the + |window-ID|. + If buffer {buf} doesn't exist or there is no such window, -1 + is returned. Example: > + + echo "A window containing buffer 1 is " .. (bufwinnr(1)) + +< The number can be used with |CTRL-W_w| and ":wincmd w" + |:wincmd|. + + Can also be used as a |method|: > + FindBuffer()->bufwinnr() + +byte2line({byte}) *byte2line()* + Return the line number that contains the character at byte + count {byte} in the current buffer. This includes the + end-of-line character, depending on the 'fileformat' option + for the current buffer. The first character has byte count + one. + Also see |line2byte()|, |go| and |:goto|. + + Can also be used as a |method|: > + GetOffset()->byte2line() + +byteidx({expr}, {nr}) *byteidx()* + Return byte index of the {nr}'th character in the String + {expr}. Use zero for the first character, it then returns + zero. + If there are no multibyte characters the returned value is + equal to {nr}. + Composing characters are not counted separately, their byte + length is added to the preceding base character. See + |byteidxcomp()| below for counting composing characters + separately. + Example : > + echo matchstr(str, ".", byteidx(str, 3)) +< will display the fourth character. Another way to do the + same: > + let s = strpart(str, byteidx(str, 3)) + echo strpart(s, 0, byteidx(s, 1)) +< Also see |strgetchar()| and |strcharpart()|. + + If there are less than {nr} characters -1 is returned. + If there are exactly {nr} characters the length of the string + in bytes is returned. + + Can also be used as a |method|: > + GetName()->byteidx(idx) + +byteidxcomp({expr}, {nr}) *byteidxcomp()* + Like byteidx(), except that a composing character is counted + as a separate character. Example: > + let s = 'e' .. nr2char(0x301) + echo byteidx(s, 1) + echo byteidxcomp(s, 1) + echo byteidxcomp(s, 2) +< The first and third echo result in 3 ('e' plus composing + character is 3 bytes), the second echo results in 1 ('e' is + one byte). + Only works differently from byteidx() when 'encoding' is set + to a Unicode encoding. + + Can also be used as a |method|: > + GetName()->byteidxcomp(idx) + +call({func}, {arglist} [, {dict}]) *call()* *E699* + Call function {func} with the items in |List| {arglist} as + arguments. + {func} can either be a |Funcref| or the name of a function. + a:firstline and a:lastline are set to the cursor line. + Returns the return value of the called function. + {dict} is for functions with the "dict" attribute. It will be + used to set the local variable "self". |Dictionary-function| + + Can also be used as a |method|: > + GetFunc()->call([arg, arg], dict) + +ceil({expr}) *ceil()* + Return the smallest integral value greater than or equal to + {expr} as a |Float| (round up). + {expr} must evaluate to a |Float| or a |Number|. + Examples: > + echo ceil(1.456) +< 2.0 > + echo ceil(-5.456) +< -5.0 > + echo ceil(4.0) +< 4.0 + + Can also be used as a |method|: > + Compute()->ceil() + +changenr() *changenr()* + Return the number of the most recent change. This is the same + number as what is displayed with |:undolist| and can be used + with the |:undo| command. + When a change was made it is the number of that change. After + 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()* + 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 + with `"rpc":v:true`) If {stream} is omitted, all streams + are closed. If the channel is a pty, this will then close the + pty master, sending SIGHUP to the job process. + For a socket, there is only one stream, and {stream} should be + ommited. + +chansend({id}, {data}) *chansend()* + Send data to channel {id}. For a job, it writes it to the + stdin of the process. For the stdio channel |channel-stdio|, + it writes to Nvim's stdout. Returns the number of bytes + written if the write succeeded, 0 otherwise. + See |channel-bytes| for more information. + + {data} may be a string, string convertible, |Blob|, or a list. + If {data} is a list, the items will be joined by newlines; any + newlines in an item will be sent as NUL. To send a final + newline, include a final empty string. Example: > + :call chansend(id, ["abc", "123\n456", ""]) +< will send "abc<NL>123<NUL>456<NL>". + + chansend() writes raw data, not RPC messages. If the channel + was created with `"rpc":v:true` then the channel expects RPC + messages, use |rpcnotify()| and |rpcrequest()| instead. + + +char2nr({string} [, {utf8}]) *char2nr()* + Return number value of the first char in {string}. + Examples: > + char2nr(" ") returns 32 + char2nr("ABC") returns 65 + char2nr("á") returns 225 + char2nr("á"[0]) returns 195 + char2nr("\<M-x>") returns 128 +< Non-ASCII characters are always treated as UTF-8 characters. + {utf8} is ignored, it exists only for backwards-compatibility. + A combining character is a separate character. + |nr2char()| does the opposite. + + Can also be used as a |method|: > + GetChar()->char2nr() +< + *charcol()* +charcol({expr}) Same as |col()| but returns the character index of the column + position given with {expr} instead of the byte position. + + Example: + With the cursor on '세' in line 5 with text "여보세요": > + charcol('.') returns 3 + col('.') returns 7 + +< Can also be used as a |method|: > + GetPos()->col() +< + *charidx()* +charidx({string}, {idx} [, {countcc}]) + Return the character index of the byte at {idx} in {string}. + The index of the first character is zero. + If there are no multibyte characters the returned value is + equal to {idx}. + When {countcc} is omitted or |FALSE|, then composing characters + are not counted separately, their byte length is + added to the preceding base character. + When {countcc} is |TRUE|, then composing characters are + counted as separate characters. + Returns -1 if the arguments are invalid or if {idx} is greater + than the index of the last byte in {string}. An error is + given if the first argument is not a string, the second + argument is not a number or when the third argument is present + and is not zero or one. + See |byteidx()| and |byteidxcomp()| for getting the byte index + from the character index. + Examples: > + echo charidx('áb́ć', 3) returns 1 + echo charidx('áb́ć', 6, 1) returns 4 + echo charidx('áb́ć', 16) returns -1 +< + Can also be used as a |method|: > + GetName()->charidx(idx) + +chdir({dir}) *chdir()* + Change the current working directory to {dir}. The scope of + the directory change depends on the directory of the current + window: + - If the current window has a window-local directory + (|:lcd|), then changes the window local directory. + - Otherwise, if the current tabpage has a local + directory (|:tcd|) then changes the tabpage local + directory. + - Otherwise, changes the global directory. + If successful, returns the previous working directory. Pass + this to another chdir() to restore the directory. + On failure, returns an empty string. + + Example: > + let save_dir = chdir(newdir) + if save_dir + " ... do some work + call chdir(save_dir) + endif +< +cindent({lnum}) *cindent()* + Get the amount of indent for line {lnum} according the C + indenting rules, as with 'cindent'. + The indent is counted in spaces, the value of 'tabstop' is + relevant. {lnum} is used just like in |getline()|. + When {lnum} is invalid -1 is returned. + See |C-indenting|. + + Can also be used as a |method|: > + GetLnum()->cindent() + +clearmatches([{win}]) *clearmatches()* + Clears all matches previously defined for the current window + by |matchadd()| and the |:match| commands. + If {win} is specified, use the window with this number or + window ID instead of the current window. + + Can also be used as a |method|: > + GetWin()->clearmatches() +< + *col()* +col({expr}) The result is a Number, which is the byte index of the column + position given with {expr}. The accepted positions are: + . the cursor position + $ the end of the cursor line (the result is the + number of bytes in the cursor line plus one) + 'x position of mark x (if the mark is not set, 0 is + returned) + v In Visual mode: the start of the Visual area (the + cursor is the end). When not in Visual mode + returns the cursor position. Differs from |'<| in + that it's updated right away. + Additionally {expr} can be [lnum, col]: a |List| with the line + and column number. Most useful when the column is "$", to get + the last column of a specific line. When "lnum" or "col" is + out of range then col() returns zero. + To get the line number use |line()|. To get both use + |getpos()|. + For the screen column position use |virtcol()|. For the + character position use |charcol()|. + Note that only marks in the current file can be used. + Examples: > + col(".") column of cursor + col("$") length of cursor line plus one + col("'t") column of mark t + col("'" .. markname) column of mark markname +< The first column is 1. 0 is returned for an error. + For an uppercase mark the column may actually be in another + buffer. + For the cursor position, when 'virtualedit' is active, the + column is one higher if the cursor is after the end of the + line. This can be used to obtain the column in Insert mode: > + :imap <F2> <C-O>:let save_ve = &ve<CR> + \<C-O>:set ve=all<CR> + \<C-O>:echo col(".") .. "\n" <Bar> + \let &ve = save_ve<CR> + +< Can also be used as a |method|: > + GetPos()->col() +< + +complete({startcol}, {matches}) *complete()* *E785* + Set the matches for Insert mode completion. + Can only be used in Insert mode. You need to use a mapping + with CTRL-R = (see |i_CTRL-R|). It does not work after CTRL-O + or with an expression mapping. + {startcol} is the byte offset in the line where the completed + text start. The text up to the cursor is the original text + that will be replaced by the matches. Use col('.') for an + empty string. "col('.') - 1" will replace one character by a + match. + {matches} must be a |List|. Each |List| item is one match. + See |complete-items| for the kind of items that are possible. + "longest" in 'completeopt' is ignored. + Note that the after calling this function you need to avoid + inserting anything that would cause completion to stop. + The match can be selected with CTRL-N and CTRL-P as usual with + Insert mode completion. The popup menu will appear if + specified, see |ins-completion-menu|. + Example: > + inoremap <F5> <C-R>=ListMonths()<CR> + + func! ListMonths() + call complete(col('.'), ['January', 'February', 'March', + \ 'April', 'May', 'June', 'July', 'August', 'September', + \ 'October', 'November', 'December']) + return '' + endfunc +< This isn't very useful, but it shows how it works. Note that + an empty string is returned to avoid a zero being inserted. + + Can also be used as a |method|, the base is passed as the + second argument: > + GetMatches()->complete(col('.')) + +complete_add({expr}) *complete_add()* + Add {expr} to the list of matches. Only to be used by the + function specified with the 'completefunc' option. + Returns 0 for failure (empty string or out of memory), + 1 when the match was added, 2 when the match was already in + the list. + See |complete-functions| for an explanation of {expr}. It is + the same as one item in the list that 'omnifunc' would return. + + Can also be used as a |method|: > + GetMoreMatches()->complete_add() + +complete_check() *complete_check()* + Check for a key typed while looking for completion matches. + This is to be used when looking for matches takes some time. + Returns |TRUE| when searching for matches is to be aborted, + zero otherwise. + Only to be used by the function specified with the + 'completefunc' option. + + +complete_info([{what}]) *complete_info()* + Returns a |Dictionary| with information about Insert mode + completion. See |ins-completion|. + The items are: + mode Current completion mode name string. + See |complete_info_mode| for the values. + pum_visible |TRUE| if popup menu is visible. + See |pumvisible()|. + items List of completion matches. Each item is a + dictionary containing the entries "word", + "abbr", "menu", "kind", "info" and "user_data". + See |complete-items|. + selected Selected item index. First index is zero. + Index is -1 if no item is selected (showing + typed text only, or the last completion after + no item is selected when using the <Up> or + <Down> keys) + inserted Inserted string. [NOT IMPLEMENT YET] + + *complete_info_mode* + mode values are: + "" Not in completion mode + "keyword" Keyword completion |i_CTRL-X_CTRL-N| + "ctrl_x" Just pressed CTRL-X |i_CTRL-X| + "scroll" Scrolling with |i_CTRL-X_CTRL-E| or + |i_CTRL-X_CTRL-Y| + "whole_line" Whole lines |i_CTRL-X_CTRL-L| + "files" File names |i_CTRL-X_CTRL-F| + "tags" Tags |i_CTRL-X_CTRL-]| + "path_defines" Definition completion |i_CTRL-X_CTRL-D| + "path_patterns" Include completion |i_CTRL-X_CTRL-I| + "dictionary" Dictionary |i_CTRL-X_CTRL-K| + "thesaurus" Thesaurus |i_CTRL-X_CTRL-T| + "cmdline" Vim Command line |i_CTRL-X_CTRL-V| + "function" User defined completion |i_CTRL-X_CTRL-U| + "omni" Omni completion |i_CTRL-X_CTRL-O| + "spell" Spelling suggestions |i_CTRL-X_s| + "eval" |complete()| completion + "unknown" Other internal modes + + If the optional {what} list argument is supplied, then only + the items listed in {what} are returned. Unsupported items in + {what} are silently ignored. + + To get the position and size of the popup menu, see + |pum_getpos()|. It's also available in |v:event| during the + |CompleteChanged| event. + + Examples: > + " Get all items + call complete_info() + " Get only 'mode' + call complete_info(['mode']) + " Get only 'mode' and 'pum_visible' + call complete_info(['mode', 'pum_visible']) + +< Can also be used as a |method|: > + GetItems()->complete_info() +< + *confirm()* +confirm({msg} [, {choices} [, {default} [, {type}]]]) + Confirm() offers the user a dialog, from which a choice can be + made. It returns the number of the choice. For the first + choice this is 1. + + {msg} is displayed in a dialog with {choices} as the + alternatives. When {choices} is missing or empty, "&OK" is + used (and translated). + {msg} is a String, use '\n' to include a newline. Only on + some systems the string is wrapped when it doesn't fit. + + {choices} is a String, with the individual choices separated + by '\n', e.g. > + confirm("Save changes?", "&Yes\n&No\n&Cancel") +< The letter after the '&' is the shortcut key for that choice. + Thus you can type 'c' to select "Cancel". The shortcut does + not need to be the first letter: > + confirm("file has been modified", "&Save\nSave &All") +< For the console, the first letter of each choice is used as + the default shortcut key. Case is ignored. + + The optional {type} String argument gives the type of dialog. + It can be one of these values: "Error", "Question", "Info", + "Warning" or "Generic". Only the first character is relevant. + When {type} is omitted, "Generic" is used. + + The optional {type} argument gives the type of dialog. This + is only used for the icon of the Win32 GUI. It can be one of + these values: "Error", "Question", "Info", "Warning" or + "Generic". Only the first character is relevant. + When {type} is omitted, "Generic" is used. + + If the user aborts the dialog by pressing <Esc>, CTRL-C, + or another valid interrupt key, confirm() returns 0. + + An example: > + :let choice = confirm("What do you want?", "&Apples\n&Oranges\n&Bananas", 2) + :if choice == 0 + : echo "make up your mind!" + :elseif choice == 3 + : echo "tasteful" + :else + : echo "I prefer bananas myself." + :endif +< In a GUI dialog, buttons are used. The layout of the buttons + depends on the 'v' flag in 'guioptions'. If it is included, + the buttons are always put vertically. Otherwise, confirm() + tries to put the buttons in one horizontal line. If they + don't fit, a vertical layout is used anyway. For some systems + the horizontal layout is always used. + + Can also be used as a |method|in: > + BuildMessage()->confirm("&Yes\n&No") +< + *copy()* +copy({expr}) Make a copy of {expr}. For Numbers and Strings this isn't + different from using {expr} directly. + When {expr} is a |List| a shallow copy is created. This means + that the original |List| can be changed without changing the + copy, and vice versa. But the items are identical, thus + changing an item changes the contents of both |Lists|. + A |Dictionary| is copied in a similar way as a |List|. + Also see |deepcopy()|. + Can also be used as a |method|: > + mylist->copy() + +cos({expr}) *cos()* + Return the cosine of {expr}, measured in radians, as a |Float|. + {expr} must evaluate to a |Float| or a |Number|. + Examples: > + :echo cos(100) +< 0.862319 > + :echo cos(-4.01) +< -0.646043 + + Can also be used as a |method|: > + Compute()->cos() + +cosh({expr}) *cosh()* + Return the hyperbolic cosine of {expr} as a |Float| in the range + [1, inf]. + {expr} must evaluate to a |Float| or a |Number|. + Examples: > + :echo cosh(0.5) +< 1.127626 > + :echo cosh(-0.5) +< -1.127626 + + Can also be used as a |method|: > + Compute()->cosh() + +count({comp}, {expr} [, {ic} [, {start}]]) *count()* + Return the number of times an item with value {expr} appears + in |String|, |List| or |Dictionary| {comp}. + + If {start} is given then start with the item with this index. + {start} can only be used with a |List|. + + When {ic} is given and it's |TRUE| then case is ignored. + + When {comp} is a string then the number of not overlapping + occurrences of {expr} is returned. Zero is returned when + {expr} is an empty string. + + Can also be used as a |method|: > + mylist->count(val) +< + *cscope_connection()* +cscope_connection([{num} , {dbpath} [, {prepend}]]) + Checks for the existence of a |cscope| connection. If no + parameters are specified, then the function returns: + 0, if there are no cscope connections; + 1, if there is at least one cscope connection. + + If parameters are specified, then the value of {num} + determines how existence of a cscope connection is checked: + + {num} Description of existence check + ----- ------------------------------ + 0 Same as no parameters (e.g., "cscope_connection()"). + 1 Ignore {prepend}, and use partial string matches for + {dbpath}. + 2 Ignore {prepend}, and use exact string matches for + {dbpath}. + 3 Use {prepend}, use partial string matches for both + {dbpath} and {prepend}. + 4 Use {prepend}, use exact string matches for both + {dbpath} and {prepend}. + + Note: All string comparisons are case sensitive! + + Examples. Suppose we had the following (from ":cs show"): > + + # pid database name prepend path + 0 27664 cscope.out /usr/local +< + Invocation Return Val ~ + ---------- ---------- > + cscope_connection() 1 + cscope_connection(1, "out") 1 + cscope_connection(2, "out") 0 + cscope_connection(3, "out") 0 + cscope_connection(3, "out", "local") 1 + cscope_connection(4, "out") 0 + cscope_connection(4, "out", "local") 0 + cscope_connection(4, "cscope.out", "/usr/local") 1 +< + +ctxget([{index}]) *ctxget()* + Returns a |Dictionary| representing the |context| at {index} + from the top of the |context-stack| (see |context-dict|). + If {index} is not given, it is assumed to be 0 (i.e.: top). + +ctxpop() *ctxpop()* + Pops and restores the |context| at the top of the + |context-stack|. + +ctxpush([{types}]) *ctxpush()* + Pushes the current editor state (|context|) on the + |context-stack|. + If {types} is given and is a |List| of |String|s, it specifies + which |context-types| to include in the pushed context. + Otherwise, all context types are included. + +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|). + If {index} is not given, it is assumed to be 0 (i.e.: top). + +ctxsize() *ctxsize()* + Returns the size of the |context-stack|. + +cursor({lnum}, {col} [, {off}]) *cursor()* +cursor({list}) + Positions the cursor at the column (byte count) {col} in the + line {lnum}. The first column is one. + + When there is one argument {list} this is used as a |List| + with two, three or four item: + [{lnum}, {col}] + [{lnum}, {col}, {off}] + [{lnum}, {col}, {off}, {curswant}] + This is like the return value of |getpos()| or |getcurpos()|, + but without the first item. + + To position the cursor using the character count, use + |setcursorcharpos()|. + + Does not change the jumplist. + If {lnum} is greater than the number of lines in the buffer, + the cursor will be positioned at the last line in the buffer. + If {lnum} is zero, the cursor will stay in the current line. + If {col} is greater than the number of bytes in the line, + the cursor will be positioned at the last character in the + line. + If {col} is zero, the cursor will stay in the current column. + If {curswant} is given it is used to set the preferred column + for vertical movement. Otherwise {col} is used. + + When 'virtualedit' is used {off} specifies the offset in + screen columns from the start of the character. E.g., a + position within a <Tab> or after the last character. + Returns 0 when the position could be set, -1 otherwise. + + Can also be used as a |method|: > + GetCursorPos()->cursor() + +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 + that the original |List| can be changed without changing the + copy, and vice versa. When an item is a |List|, a copy for it + is made, recursively. Thus changing an item in the copy does + not change the contents of the original |List|. + + When {noref} is omitted or zero a contained |List| or + |Dictionary| is only copied once. All references point to + this single copy. With {noref} set to 1 every occurrence of a + |List| or |Dictionary| results in a new copy. This also means + that a cyclic reference causes deepcopy() to fail. + *E724* + Nesting is possible up to 100 levels. When there is an item + that refers back to a higher level making a deep copy with + {noref} set to 1 will fail. + Also see |copy()|. + + Can also be used as a |method|: > + GetObject()->deepcopy() + +delete({fname} [, {flags}]) *delete()* + Without {flags} or with {flags} empty: Deletes the file by the + name {fname}. This also works when {fname} is a symbolic link. + A symbolic link itself is deleted, not what it points to. + + When {flags} is "d": Deletes the directory by the name + {fname}. This fails when directory {fname} is not empty. + + When {flags} is "rf": Deletes the directory by the name + {fname} and everything in it, recursively. BE CAREFUL! + Note: on MS-Windows it is not possible to delete a directory + that is being used. + + The result is a Number, which is 0/false if the delete + operation was successful and -1/true when the deletion failed + or partly failed. + + Can also be used as a |method|: > + GetName()->delete() + +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. + + This function works only for loaded buffers. First call + |bufload()| if needed. + + For the use of {buf}, see |bufname()| above. + + {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}. + + Can also be used as a |method|: > + GetBuffer()->deletebufline(1) +< +dictwatcheradd({dict}, {pattern}, {callback}) *dictwatcheradd()* + Adds a watcher to a dictionary. A dictionary watcher is + identified by three components: + + - A dictionary({dict}); + - A key pattern({pattern}). + - A function({callback}). + + After this is called, every change on {dict} and on keys + matching {pattern} will result in {callback} being invoked. + + For example, to watch all global variables: > + silent! call dictwatcherdel(g:, '*', 'OnDictChanged') + function! OnDictChanged(d,k,z) + echomsg string(a:k) string(a:z) + endfunction + call dictwatcheradd(g:, '*', 'OnDictChanged') +< + For now {pattern} only accepts very simple patterns that can + contain a '*' at the end of the string, in which case it will + match every key that begins with the substring before the '*'. + That means if '*' is not the last character of {pattern}, only + keys that are exactly equal as {pattern} will be matched. + + The {callback} receives three arguments: + + - The dictionary being watched. + - The key which changed. + - A dictionary containing the new and old values for the key. + + The type of change can be determined by examining the keys + present on the third argument: + + - If contains both `old` and `new`, the key was updated. + - If it contains only `new`, the key was added. + - If it contains only `old`, the key was deleted. + + This function can be used by plugins to implement options with + validation and parsing logic. + +dictwatcherdel({dict}, {pattern}, {callback}) *dictwatcherdel()* + Removes a watcher added with |dictwatcheradd()|. All three + arguments must match the ones passed to |dictwatcheradd()| in + order for the watcher to be successfully deleted. + + *did_filetype()* +did_filetype() Returns |TRUE| when autocommands are being executed and the + FileType event has been triggered at least once. Can be used + to avoid triggering the FileType event again in the scripts + that detect the file type. |FileType| + Returns |FALSE| when `:setf FALLBACK` was used. + When editing another file, the counter is reset, thus this + really checks if the FileType event has been triggered for the + current buffer. This allows an autocommand that starts + editing another buffer to set 'filetype' and load a syntax + file. + +diff_filler({lnum}) *diff_filler()* + Returns the number of filler lines above line {lnum}. + These are the lines that were inserted at this point in + another diff'ed window. These filler lines are shown in the + display but don't exist in the buffer. + {lnum} is used like with |getline()|. Thus "." is the current + line, "'m" mark m, etc. + Returns 0 if the current window is not in diff mode. + + Can also be used as a |method|: > + GetLnum()->diff_filler() + +diff_hlID({lnum}, {col}) *diff_hlID()* + Returns the highlight ID for diff mode at line {lnum} column + {col} (byte index). When the current line does not have a + diff change zero is returned. + {lnum} is used like with |getline()|. Thus "." is the current + line, "'m" mark m, etc. + {col} is 1 for the leftmost column, {lnum} is 1 for the first + line. + The highlight ID can be used with |synIDattr()| to obtain + syntax information about the highlighting. + + Can also be used as a |method|: > + GetLnum()->diff_hlID(col) + +empty({expr}) *empty()* + Return the Number 1 if {expr} is empty, zero otherwise. + - A |List| or |Dictionary| is empty when it does not have any + items. + - A |String| is empty when its length is zero. + - A |Number| and |Float| are empty when their value is zero. + - |v:false| and |v:null| are empty, |v:true| is not. + - A |Blob| is empty when its length is zero. + + Can also be used as a |method|: > + mylist->empty() + +environ() *environ()* + Return all of environment variables as dictionary. You can + check if an environment variable exists like this: > + :echo has_key(environ(), 'HOME') +< Note that the variable name may be CamelCase; to ignore case + use this: > + :echo index(keys(environ()), 'HOME', 0, 1) != -1 + +escape({string}, {chars}) *escape()* + Escape the characters in {chars} that occur in {string} with a + backslash. Example: > + :echo escape('c:\program files\vim', ' \') +< results in: > + c:\\program\ files\\vim +< Also see |shellescape()| and |fnameescape()|. + + Can also be used as a |method|: > + GetText()->escape(' \') +< + *eval()* +eval({string}) Evaluate {string} and return the result. Especially useful to + turn the result of |string()| back into the original value. + This works for Numbers, Floats, Strings, Blobs and composites + of them. Also works for |Funcref|s that refer to existing + functions. + + Can also be used as a |method|: > + argv->join()->eval() + +eventhandler() *eventhandler()* + Returns 1 when inside an event handler. That is that Vim got + interrupted while waiting for the user to type a character, + e.g., when dropping a file on Vim. This means interactive + commands cannot be used. Otherwise zero is returned. + +executable({expr}) *executable()* + This function checks if an executable with the name {expr} + exists. {expr} must be the name of the program without any + arguments. + executable() uses the value of $PATH and/or the normal + searchpath for programs. *PATHEXT* + On MS-Windows the ".exe", ".bat", etc. can optionally be + included. Then the extensions in $PATHEXT are tried. Thus if + "foo.exe" does not exist, "foo.exe.bat" can be found. If + $PATHEXT is not set then ".exe;.com;.bat;.cmd" is used. A dot + by itself can be used in $PATHEXT to try using the name + without an extension. When 'shell' looks like a Unix shell, + then the name is also tried without adding an extension. + On MS-Windows it only checks if the file exists and is not a + directory, not if it's really executable. + On Windows an executable in the same directory as Vim is + always found (it is added to $PATH at |startup|). + The result is a Number: + 1 exists + 0 does not exist + -1 not implemented on this system + |exepath()| can be used to get the full path of an executable. + + Can also be used as a |method|: > + GetCommand()->executable() + +execute({command} [, {silent}]) *execute()* + Execute {command} and capture its output. + If {command} is a |String|, returns {command} output. + If {command} is a |List|, returns concatenated outputs. + Examples: > + echo execute('echon "foo"') +< foo > + echo execute(['echon "foo"', 'echon "bar"']) +< foobar + + The optional {silent} argument can have these values: + "" no `:silent` used + "silent" `:silent` used + "silent!" `:silent!` used + The default is "silent". Note that with "silent!", unlike + `:redir`, error messages are dropped. + + To get a list of lines use |split()| on the result: > + split(execute('args'), "\n") + +< This function is not available in the |sandbox|. + Note: If nested, an outer execute() will not observe output of + the inner calls. + Note: Text attributes (highlights) are not captured. + To execute a command in another window than the current one + use `win_execute()`. + + Can also be used as a |method|: > + GetCommand()->execute() + +exepath({expr}) *exepath()* + Returns the full path of {expr} if it is an executable and + given as a (partial or full) path or is found in $PATH. + Returns empty string otherwise. + If {expr} starts with "./" the |current-directory| is used. + + Can also be used as a |method|: > + GetCommand()->exepath() +< + *exists()* +exists({expr}) The result is a Number, which is |TRUE| if {expr} is + defined, zero otherwise. + + For checking for a supported feature use |has()|. + For checking if a file exists use |filereadable()|. + + The {expr} argument is a string, which contains one of these: + varname internal variable (see + dict.key |internal-variables|). Also works + list[i] for |curly-braces-names|, |Dictionary| + entries, |List| items, etc. + Beware that evaluating an index may + cause an error message for an invalid + expression. E.g.: > + :let l = [1, 2, 3] + :echo exists("l[5]") +< 0 > + :echo exists("l[xx]") +< E121: Undefined variable: xx + 0 + &option-name Vim option (only checks if it exists, + not if it really works) + +option-name Vim option that works. + $ENVNAME environment variable (could also be + done by comparing with an empty + string) + *funcname built-in function (see |functions|) + or user defined function (see + |user-function|). Also works for a + variable that is a Funcref. + :cmdname Ex command: built-in command, user + command or command modifier |:command|. + Returns: + 1 for match with start of a command + 2 full match with a command + 3 matches several user commands + To check for a supported command + always check the return value to be 2. + :2match The |:2match| command. + :3match The |:3match| command. + #event autocommand defined for this event + #event#pattern autocommand defined for this event and + pattern (the pattern is taken + literally and compared to the + autocommand patterns character by + character) + #group autocommand group exists + #group#event autocommand defined for this group and + event. + #group#event#pattern + autocommand defined for this group, + event and pattern. + ##event autocommand for this event is + supported. + + Examples: > + exists("&mouse") + exists("$HOSTNAME") + exists("*strftime") + exists("*s:MyFunc") + exists("*MyFunc") + exists("bufcount") + exists(":Make") + exists("#CursorHold") + exists("#BufReadPre#*.gz") + exists("#filetypeindent") + exists("#filetypeindent#FileType") + exists("#filetypeindent#FileType#*") + exists("##ColorScheme") +< There must be no space between the symbol (&/$/*/#) and the + name. + There must be no extra characters after the name, although in + a few cases this is ignored. That may become more strict in + the future, thus don't count on it! + Working example: > + exists(":make") +< NOT working example: > + exists(":make install") + +< Note that the argument must be a string, not the name of the + variable itself. For example: > + exists(bufcount) +< This doesn't check for existence of the "bufcount" variable, + but gets the value of "bufcount", and checks if that exists. + + Can also be used as a |method|: > + Varname()->exists() + +exp({expr}) *exp()* + Return the exponential of {expr} as a |Float| in the range + [0, inf]. + {expr} must evaluate to a |Float| or a |Number|. + Examples: > + :echo exp(2) +< 7.389056 > + :echo exp(-1) +< 0.367879 + + Can also be used as a |method|: > + Compute()->exp() + +debugbreak({pid}) *debugbreak()* + Specifically used to interrupt a program being debugged. It + will cause process {pid} to get a SIGTRAP. Behavior for other + processes is undefined. See |terminal-debugger|. + {Sends a SIGINT to a process {pid} other than MS-Windows} + + Can also be used as a |method|: > + GetPid()->debugbreak() + +expand({string} [, {nosuf} [, {list}]]) *expand()* + Expand wildcards and the following special keywords in + {string}. 'wildignorecase' applies. + + If {list} is given and it is |TRUE|, a List will be returned. + Otherwise the result is a String and when there are several + matches, they are separated by <NL> characters. + + If the expansion fails, the result is an empty string. A name + for a non-existing file is not included, unless {string} does + not start with '%', '#' or '<', see below. + + When {string} starts with '%', '#' or '<', the expansion is + done like for the |cmdline-special| variables with their + associated modifiers. Here is a short overview: + + % current file name + # alternate file name + #n alternate file name n + <cfile> file name under the cursor + <afile> autocmd file name + <abuf> autocmd buffer number (as a String!) + <amatch> autocmd matched name + <sfile> sourced script file or function name + <slnum> sourced script line number or function + line number + <sflnum> script file line number, also when in + a function + <SID> "<SNR>123_" where "123" is the + current script ID |<SID>| + <cword> word under the cursor + <cWORD> WORD under the cursor + <client> the {clientid} of the last received + message |server2client()| + Modifiers: + :p expand to full path + :h head (last path component removed) + :t tail (last path component only) + :r root (one extension removed) + :e extension only + + Example: > + :let &tags = expand("%:p:h") .. "/tags" +< Note that when expanding a string that starts with '%', '#' or + '<', any following text is ignored. This does NOT work: > + :let doesntwork = expand("%:h.bak") +< Use this: > + :let doeswork = expand("%:h") .. ".bak" +< Also note that expanding "<cfile>" and others only returns the + referenced file name without further expansion. If "<cfile>" + is "~/.cshrc", you need to do another expand() to have the + "~/" expanded into the path of the home directory: > + :echo expand(expand("<cfile>")) +< + There cannot be white space between the variables and the + following modifier. The |fnamemodify()| function can be used + to modify normal file names. + + When using '%' or '#', and the current or alternate file name + is not defined, an empty string is used. Using "%:p" in a + buffer with no name, results in the current directory, with a + '/' added. + + When {string} does not start with '%', '#' or '<', it is + expanded like a file name is expanded on the command line. + 'suffixes' and 'wildignore' are used, unless the optional + {nosuf} argument is given and it is |TRUE|. + Names for non-existing files are included. The "**" item can + be used to search in a directory tree. For example, to find + all "README" files in the current directory and below: > + :echo expand("**/README") +< + expand() can also be used to expand variables and environment + variables that are only known in a shell. But this can be + slow, because a shell may be used to do the expansion. See + |expr-env-expand|. + The expanded variable is still handled like a list of file + names. When an environment variable cannot be expanded, it is + left unchanged. Thus ":echo expand('$FOOBAR')" results in + "$FOOBAR". + + See |glob()| for finding existing files. See |system()| for + getting the raw output of an external command. + + Can also be used as a |method|: > + Getpattern()->expand() + +expandcmd({string}) *expandcmd()* + Expand special items in String {string} like what is done for + an Ex command such as `:edit`. This expands special keywords, + like with |expand()|, and environment variables, anywhere in + {string}. "~user" and "~/path" are only expanded at the + start. + Returns the expanded string. Example: > + :echo expandcmd('make %<.o') + +< Can also be used as a |method|: > + GetCommand()->expandcmd() +< +extend({expr1}, {expr2} [, {expr3}]) *extend()* + {expr1} and {expr2} must be both |Lists| or both + |Dictionaries|. + + If they are |Lists|: Append {expr2} to {expr1}. + If {expr3} is given insert the items of {expr2} before the + item with index {expr3} in {expr1}. When {expr3} is zero + insert before the first item. When {expr3} is equal to + len({expr1}) then {expr2} is appended. + Examples: > + :echo sort(extend(mylist, [7, 5])) + :call extend(mylist, [2, 3], 1) +< When {expr1} is the same List as {expr2} then the number of + items copied is equal to the original length of the List. + E.g., when {expr3} is 1 you get N new copies of the first item + (where N is the original length of the List). + Use |add()| to concatenate one item to a list. To concatenate + two lists into a new list use the + operator: > + :let newlist = [1, 2, 3] + [4, 5] +< + If they are |Dictionaries|: + Add all entries from {expr2} to {expr1}. + If a key exists in both {expr1} and {expr2} then {expr3} is + used to decide what to do: + {expr3} = "keep": keep the value of {expr1} + {expr3} = "force": use the value of {expr2} + {expr3} = "error": give an error message *E737* + When {expr3} is omitted then "force" is assumed. + + {expr1} is changed when {expr2} is not empty. If necessary + make a copy of {expr1} first. + {expr2} remains unchanged. + When {expr1} is locked and {expr2} is not empty the operation + fails. + Returns {expr1}. + + Can also be used as a |method|: > + mylist->extend(otherlist) + +feedkeys({string} [, {mode}]) *feedkeys()* + Characters in {string} are queued for processing as if they + come from a mapping or were typed by the user. + + By default the string is added to the end of the typeahead + buffer, thus if a mapping is still being executed the + characters come after them. Use the 'i' flag to insert before + other characters, they will be executed next, before any + characters from a mapping. + + The function does not wait for processing of keys contained in + {string}. + + To include special keys into {string}, use double-quotes + and "\..." notation |expr-quote|. For example, + feedkeys("\<CR>") simulates pressing of the <Enter> key. But + feedkeys('\<CR>') pushes 5 characters. + The |<Ignore>| keycode may be used to exit the + wait-for-character without doing anything. + + {mode} is a String, which can contain these character flags: + 'm' Remap keys. This is default. If {mode} is absent, + keys are remapped. + 'n' Do not remap keys. + 't' Handle keys as if typed; otherwise they are handled as + if coming from a mapping. This matters for undo, + opening folds, etc. + 'i' Insert the string instead of appending (see above). + 'x' Execute commands until typeahead is empty. This is + similar to using ":normal!". You can call feedkeys() + several times without 'x' and then one time with 'x' + (possibly with an empty {string}) to execute all the + typeahead. Note that when Vim ends in Insert mode it + will behave as if <Esc> is typed, to avoid getting + stuck, waiting for a character to be typed before the + script continues. + Note that if you manage to call feedkeys() while + executing commands, thus calling it recursively, then + all typehead will be consumed by the last call. + '!' When used with 'x' will not end Insert mode. Can be + used in a test when a timer is set to exit Insert mode + a little later. Useful for testing CursorHoldI. + + Return value is always 0. + + Can also be used as a |method|: > + GetInput()->feedkeys() + +filereadable({file}) *filereadable()* + The result is a Number, which is |TRUE| when a file with the + name {file} exists, and can be read. If {file} doesn't exist, + or is a directory, the result is |FALSE|. {file} is any + expression, which is used as a String. + If you don't care about the file being readable you can use + |glob()|. + {file} is used as-is, you may want to expand wildcards first: > + echo filereadable('~/.vimrc') + 0 + echo filereadable(expand('~/.vimrc')) + 1 + +< Can also be used as a |method|: > + GetName()->filereadable() + +filewritable({file}) *filewritable()* + The result is a Number, which is 1 when a file with the + name {file} exists, and can be written. If {file} doesn't + exist, or is not writable, the result is 0. If {file} is a + directory, and we can write to it, the result is 2. + + Can also be used as a |method|: > + GetName()->filewriteable() + +filter({expr1}, {expr2}) *filter()* + {expr1} must be a |List|, |Blob|, or a |Dictionary|. + For each item in {expr1} evaluate {expr2} and when the result + is zero remove the item from the |List| or |Dictionary|. For a + |Blob| each byte is removed. + + {expr2} must be a |string| or |Funcref|. + + If {expr2} is a |string|, inside {expr2} |v:val| has the value + of the current item. For a |Dictionary| |v:key| has the key + of the current item and for a |List| |v:key| has the index of + the current item. For a |Blob| |v:key| has the index of the + current byte. + + Examples: > + call filter(mylist, 'v:val !~ "OLD"') +< Removes the items where "OLD" appears. > + call filter(mydict, 'v:key >= 8') +< Removes the items with a key below 8. > + call filter(var, 0) +< Removes all the items, thus clears the |List| or |Dictionary|. + + Note that {expr2} is the result of expression and is then + used as an expression again. Often it is good to use a + |literal-string| to avoid having to double backslashes. + + If {expr2} is a |Funcref| it must take two arguments: + 1. the key or the index of the current item. + 2. the value of the current item. + The function must return |TRUE| if the item should be kept. + Example that keeps the odd items of a list: > + func Odd(idx, val) + return a:idx % 2 == 1 + endfunc + call filter(mylist, function('Odd')) +< It is shorter when using a |lambda|: > + call filter(myList, {idx, val -> idx * val <= 42}) +< If you do not use "val" you can leave it out: > + call filter(myList, {idx -> idx % 2 == 1}) +< + The operation is done in-place. If you want a |List| or + |Dictionary| to remain unmodified make a copy first: > + :let l = filter(copy(mylist), 'v:val =~ "KEEP"') + +< Returns {expr1}, the |List|, |Blob| or |Dictionary| that was + filtered. When an error is encountered while evaluating + {expr2} no further items in {expr1} are processed. When + {expr2} is a Funcref errors inside a function are ignored, + unless it was defined with the "abort" flag. + + Can also be used as a |method|: > + mylist->filter(expr2) + +finddir({name} [, {path} [, {count}]]) *finddir()* + Find directory {name} in {path}. Supports both downwards and + upwards recursive directory searches. See |file-searching| + for the syntax of {path}. + + Returns the path of the first found match. When the found + directory is below the current directory a relative path is + returned. Otherwise a full path is returned. + If {path} is omitted or empty then 'path' is used. + + If the optional {count} is given, find {count}'s occurrence of + {name} in {path} instead of the first one. + When {count} is negative return all the matches in a |List|. + + This is quite similar to the ex-command `:find`. + + Can also be used as a |method|: > + GetName()->finddir() + +findfile({name} [, {path} [, {count}]]) *findfile()* + Just like |finddir()|, but find a file instead of a directory. + Uses 'suffixesadd'. + Example: > + :echo findfile("tags.vim", ".;") +< Searches from the directory of the current file upwards until + it finds the file "tags.vim". + + Can also be used as a |method|: > + GetName()->findfile() + +flatten({list} [, {maxdepth}]) *flatten()* + Flatten {list} up to {maxdepth} levels. Without {maxdepth} + the result is a |List| without nesting, as if {maxdepth} is + a very large number. + The {list} is changed in place, make a copy first if you do + not want that. + *E900* + {maxdepth} means how deep in nested lists changes are made. + {list} is not modified when {maxdepth} is 0. + {maxdepth} must be positive number. + + If there is an error the number zero is returned. + + Example: > + :echo flatten([1, [2, [3, 4]], 5]) +< [1, 2, 3, 4, 5] > + :echo flatten([1, [2, [3, 4]], 5], 1) +< [1, 2, [3, 4], 5] + + Can also be used as a |method|: > + mylist->flatten() +< +float2nr({expr}) *float2nr()* + Convert {expr} to a Number by omitting the part after the + decimal point. + {expr} must evaluate to a |Float| or a Number. + When the value of {expr} is out of range for a |Number| the + result is truncated to 0x7fffffff or -0x7fffffff (or when + 64-bit Number support is enabled, 0x7fffffffffffffff or + -0x7fffffffffffffff). NaN results in -0x80000000 (or when + 64-bit Number support is enabled, -0x8000000000000000). + Examples: > + echo float2nr(3.95) +< 3 > + echo float2nr(-23.45) +< -23 > + echo float2nr(1.0e100) +< 2147483647 (or 9223372036854775807) > + echo float2nr(-1.0e150) +< -2147483647 (or -9223372036854775807) > + echo float2nr(1.0e-100) +< 0 + + Can also be used as a |method|: > + Compute()->float2nr() + +floor({expr}) *floor()* + Return the largest integral value less than or equal to + {expr} as a |Float| (round down). + {expr} must evaluate to a |Float| or a |Number|. + Examples: > + echo floor(1.856) +< 1.0 > + echo floor(-5.456) +< -6.0 > + echo floor(4.0) +< 4.0 + + Can also be used as a |method|: > + Compute()->floor() + +fmod({expr1}, {expr2}) *fmod()* + Return the remainder of {expr1} / {expr2}, even if the + division is not representable. Returns {expr1} - i * {expr2} + for some integer i such that if {expr2} is non-zero, the + result has the same sign as {expr1} and magnitude less than + the magnitude of {expr2}. If {expr2} is zero, the value + returned is zero. The value returned is a |Float|. + {expr1} and {expr2} must evaluate to a |Float| or a |Number|. + Examples: > + :echo fmod(12.33, 1.22) +< 0.13 > + :echo fmod(-12.33, 1.22) +< -0.13 + + Can also be used as a |method|: > + Compute()->fmod(1.22) + +fnameescape({string}) *fnameescape()* + Escape {string} for use as file name command argument. All + characters that have a special meaning, such as '%' and '|' + are escaped with a backslash. + For most systems the characters escaped are + " \t\n*?[{`$\\%#'\"|!<". For systems where a backslash + appears in a filename, it depends on the value of 'isfname'. + A leading '+' and '>' is also escaped (special after |:edit| + and |:write|). And a "-" by itself (special after |:cd|). + Example: > + :let fname = '+some str%nge|name' + :exe "edit " .. fnameescape(fname) +< results in executing: > + edit \+some\ str\%nge\|name +< + Can also be used as a |method|: > + GetName()->fnameescape() + +fnamemodify({fname}, {mods}) *fnamemodify()* + Modify file name {fname} according to {mods}. {mods} is a + string of characters like it is used for file names on the + command line. See |filename-modifiers|. + Example: > + :echo fnamemodify("main.c", ":p:h") +< results in: > + /home/mool/vim/vim/src +< If {mods} is empty then {fname} is returned. + Note: Environment variables don't work in {fname}, use + |expand()| first then. + + Can also be used as a |method|: > + GetName()->fnamemodify(':p:h') + +foldclosed({lnum}) *foldclosed()* + The result is a Number. If the line {lnum} is in a closed + fold, the result is the number of the first line in that fold. + If the line {lnum} is not in a closed fold, -1 is returned. + {lnum} is used like with |getline()|. Thus "." is the current + line, "'m" mark m, etc. + + Can also be used as a |method|: > + GetLnum()->foldclosed() + +foldclosedend({lnum}) *foldclosedend()* + The result is a Number. If the line {lnum} is in a closed + fold, the result is the number of the last line in that fold. + If the line {lnum} is not in a closed fold, -1 is returned. + {lnum} is used like with |getline()|. Thus "." is the current + line, "'m" mark m, etc. + + Can also be used as a |method|: > + GetLnum()->foldclosedend() + +foldlevel({lnum}) *foldlevel()* + The result is a Number, which is the foldlevel of line {lnum} + in the current buffer. For nested folds the deepest level is + returned. If there is no fold at line {lnum}, zero is + returned. It doesn't matter if the folds are open or closed. + When used while updating folds (from 'foldexpr') -1 is + returned for lines where folds are still to be updated and the + foldlevel is unknown. As a special case the level of the + previous line is usually available. + {lnum} is used like with |getline()|. Thus "." is the current + line, "'m" mark m, etc. + + Can also be used as a |method|: > + GetLnum()->foldlevel() +< + *foldtext()* +foldtext() Returns a String, to be displayed for a closed fold. This is + the default function used for the 'foldtext' option and should + only be called from evaluating 'foldtext'. It uses the + |v:foldstart|, |v:foldend| and |v:folddashes| variables. + The returned string looks like this: > + +-- 45 lines: abcdef +< The number of leading dashes depends on the foldlevel. The + "45" is the number of lines in the fold. "abcdef" is the text + in the first non-blank line of the fold. Leading white space, + "//" or "/*" and the text from the 'foldmarker' and + 'commentstring' options is removed. + When used to draw the actual foldtext, the rest of the line + will be filled with the fold char from the 'fillchars' + setting. + +foldtextresult({lnum}) *foldtextresult()* + Returns the text that is displayed for the closed fold at line + {lnum}. Evaluates 'foldtext' in the appropriate context. + When there is no closed fold at {lnum} an empty string is + returned. + {lnum} is used like with |getline()|. Thus "." is the current + line, "'m" mark m, etc. + Useful when exporting folded text, e.g., to HTML. + + Can also be used as a |method|: > + GetLnum()->foldtextresult() +< + *foreground()* +foreground() Move the Vim window to the foreground. Useful when sent from + a client to a Vim server. |remote_send()| + On Win32 systems this might not work, the OS does not always + allow a window to bring itself to the foreground. Use + |remote_foreground()| instead. + {only in the Win32 GUI and console version} + +fullcommand({name}) *fullcommand()* + Get the full command name from a short abbreviated command + name; see |20.2| for details on command abbreviations. + + The string argument {name} may start with a `:` and can + include a [range], these are skipped and not returned. + Returns an empty string if a command doesn't exist or if it's + ambiguous (for user-defined commands). + + For example `fullcommand('s')`, `fullcommand('sub')`, + `fullcommand(':%substitute')` all return "substitute". + + Can also be used as a |method|: > + GetName()->fullcommand() +< + *funcref()* +funcref({name} [, {arglist}] [, {dict}]) + Just like |function()|, but the returned Funcref will lookup + the function by reference, not by name. This matters when the + function {name} is redefined later. + + Unlike |function()|, {name} must be an existing user function. + It only works for an autoloaded function if it has already + been loaded (to avoid mistakenly loading the autoload script + when only intending to use the function name, use |function()| + instead). {name} cannot be a builtin function. + + Can also be used as a |method|: > + GetFuncname()->funcref([arg]) +< + *function()* *E700* *E922* *E923* +function({name} [, {arglist}] [, {dict}]) + Return a |Funcref| variable that refers to function {name}. + {name} can be a user defined function or an internal function. + + {name} can also be a Funcref or a partial. When it is a + partial the dict stored in it will be used and the {dict} + argument is not allowed. E.g.: > + let FuncWithArg = function(dict.Func, [arg]) + let Broken = function(dict.Func, [arg], dict) +< + When using the Funcref the function will be found by {name}, + also when it was redefined later. Use |funcref()| to keep the + same function. + + When {arglist} or {dict} is present this creates a partial. + That means the argument list and/or the dictionary is stored in + the Funcref and will be used when the Funcref is called. + + The arguments are passed to the function in front of other + arguments, but after any argument from |method|. Example: > + func Callback(arg1, arg2, name) + ... + let Partial = function('Callback', ['one', 'two']) + ... + call Partial('name') +< Invokes the function as with: > + call Callback('one', 'two', 'name') + +< The Dictionary is only useful when calling a "dict" function. + In that case the {dict} is passed in as "self". Example: > + function Callback() dict + echo "called for " .. self.name + endfunction + ... + let context = {"name": "example"} + let Func = function('Callback', context) + ... + call Func() " will echo: called for example + +< The argument list and the Dictionary can be combined: > + function Callback(arg1, count) dict + ... + let context = {"name": "example"} + let Func = function('Callback', ['one'], context) + ... + call Func(500) +< Invokes the function as with: > + call context.Callback('one', 500) +< + Can also be used as a |method|: > + GetFuncname()->function([arg]) + +garbagecollect([{atexit}]) *garbagecollect()* + Cleanup unused |Lists| and |Dictionaries| that have circular + references. + + There is hardly ever a need to invoke this function, as it is + automatically done when Vim runs out of memory or is waiting + for the user to press a key after 'updatetime'. Items without + circular references are always freed when they become unused. + This is useful if you have deleted a very big |List| and/or + |Dictionary| with circular references in a script that runs + for a long time. + + When the optional {atexit} argument is one, garbage + collection will also be done when exiting Vim, if it wasn't + done before. This is useful when checking for memory leaks. + + The garbage collection is not done immediately but only when + it's safe to perform. This is when waiting for the user to + type a character. + +get({list}, {idx} [, {default}]) *get()* + Get item {idx} from |List| {list}. When this item is not + available return {default}. Return zero when {default} is + omitted. + Can also be used as a |method|: > + mylist->get(idx) +get({blob}, {idx} [, {default}]) + Get byte {idx} from |Blob| {blob}. When this byte is not + available return {default}. Return -1 when {default} is + omitted. +get({dict}, {key} [, {default}]) + Get item with key {key} from |Dictionary| {dict}. When this + item is not available return {default}. Return zero when + {default} is omitted. Useful example: > + let val = get(g:, 'var_name', 'default') +< This gets the value of g:var_name if it exists, and uses + 'default' when it does not exist. +get({func}, {what}) + Get item {what} from Funcref {func}. Possible values for + {what} are: + "name" The function name + "func" The function + "dict" The dictionary + "args" The list with arguments + + *getbufinfo()* +getbufinfo([{buf}]) +getbufinfo([{dict}]) + Get information about buffers as a List of Dictionaries. + + Without an argument information about all the buffers is + returned. + + When the argument is a |Dictionary| only the buffers matching + the specified criteria are returned. The following keys can + be specified in {dict}: + buflisted include only listed buffers. + bufloaded include only loaded buffers. + bufmodified include only modified buffers. + + Otherwise, {buf} specifies a particular buffer to return + information for. For the use of {buf}, see |bufname()| + above. If the buffer is found the returned List has one item. + Otherwise the result is an empty list. + + Each returned List item is a dictionary with the following + entries: + bufnr Buffer number. + changed TRUE if the buffer is modified. + changedtick Number of changes made to the buffer. + hidden TRUE if the buffer is hidden. + lastused Timestamp in seconds, like + |localtime()|, when the buffer was + last used. + listed TRUE if the buffer is listed. + lnum Line number used for the buffer when + opened in the current window. + Only valid if the buffer has been + displayed in the window in the past. + If you want the line number of the + last known cursor position in a given + window, use |line()|: > + :echo line('.', {winid}) +< + linecount Number of lines in the buffer (only + valid when loaded) + loaded TRUE if the buffer is loaded. + name Full path to the file in the buffer. + signs List of signs placed in the buffer. + Each list item is a dictionary with + the following fields: + id sign identifier + lnum line number + name sign name + variables A reference to the dictionary with + buffer-local variables. + windows List of |window-ID|s that display this + buffer + + Examples: > + for buf in getbufinfo() + echo buf.name + endfor + for buf in getbufinfo({'buflisted':1}) + if buf.changed + .... + endif + endfor +< + To get buffer-local options use: > + getbufvar({bufnr}, '&option_name') +< + Can also be used as a |method|: > + GetBufnr()->getbufinfo() +< + *getbufline()* +getbufline({buf}, {lnum} [, {end}]) + Return a |List| with the lines starting from {lnum} to {end} + (inclusive) in the buffer {buf}. If {end} is omitted, a + |List| with only the line {lnum} is returned. + + For the use of {buf}, see |bufname()| above. + + For {lnum} and {end} "$" can be used for the last line of the + buffer. Otherwise a number must be used. + + When {lnum} is smaller than 1 or bigger than the number of + lines in the buffer, an empty |List| is returned. + + When {end} is greater than the number of lines in the buffer, + it is treated as {end} is set to the number of lines in the + buffer. When {end} is before {lnum} an empty |List| is + returned. + + This function works only for loaded buffers. For unloaded and + non-existing buffers, an empty |List| is returned. + + Example: > + :let lines = getbufline(bufnr("myfile"), 1, "$") + +< Can also be used as a |method|: > + GetBufnr()->getbufline(lnum) + +getbufvar({buf}, {varname} [, {def}]) *getbufvar()* + The result is the value of option or local buffer variable + {varname} in buffer {buf}. Note that the name without "b:" + must be used. + The {varname} argument is a string. + When {varname} is empty returns a |Dictionary| with all the + buffer-local variables. + When {varname} is equal to "&" returns a |Dictionary| with all + the buffer-local options. + Otherwise, when {varname} starts with "&" returns the value of + a buffer-local option. + This also works for a global or buffer-local option, but it + doesn't work for a global variable, window-local variable or + window-local option. + For the use of {buf}, see |bufname()| above. + When the buffer or variable doesn't exist {def} or an empty + string is returned, there is no error message. + Examples: > + :let bufmodified = getbufvar(1, "&mod") + :echo "todo myvar = " .. getbufvar("todo", "myvar") + +< Can also be used as a |method|: > + GetBufnr()->getbufvar(varname) +< +getchangelist([{buf}]) *getchangelist()* + Returns the |changelist| for the buffer {buf}. For the use + of {buf}, see |bufname()| above. If buffer {buf} doesn't + exist, an empty list is returned. + + The returned list contains two entries: a list with the change + locations and the current position in the list. Each + entry in the change list is a dictionary with the following + entries: + col column number + coladd column offset for 'virtualedit' + lnum line number + If buffer {buf} is the current buffer, then the current + position refers to the position in the list. For other + buffers, it is set to the length of the list. + + Can also be used as a |method|: > + GetBufnr()->getchangelist() + +getchar([expr]) *getchar()* + Get a single character from the user or input stream. + If [expr] is omitted, wait until a character is available. + If [expr] is 0, only get a character when one is available. + Return zero otherwise. + If [expr] is 1, only check if a character is available, it is + not consumed. Return zero if no character available. + If you prefer always getting a string use |getcharstr()|. + + Without [expr] and when [expr] is 0 a whole character or + special key is returned. If it is a single character, the + result is a number. Use nr2char() to convert it to a String. + Otherwise a String is returned with the encoded character. + For a special key it's a String with a sequence of bytes + starting with 0x80 (decimal: 128). This is the same value as + the String "\<Key>", e.g., "\<Left>". The returned value is + also a String when a modifier (shift, control, alt) was used + that is not included in the character. + + When [expr] is 0 and Esc is typed, there will be a short delay + while Vim waits to see if this is the start of an escape + sequence. + + When [expr] is 1 only the first byte is returned. For a + one-byte character it is the character itself as a number. + Use nr2char() to convert it to a String. + + Use getcharmod() to obtain any additional modifiers. + + When the user clicks a mouse button, the mouse event will be + returned. The position can then be found in |v:mouse_col|, + |v:mouse_lnum|, |v:mouse_winid| and |v:mouse_win|. + |getmousepos()| can also be used. Mouse move events will be + ignored. + This example positions the mouse as it would normally happen: > + let c = getchar() + if c == "\<LeftMouse>" && v:mouse_win > 0 + exe v:mouse_win .. "wincmd w" + exe v:mouse_lnum + exe "normal " .. v:mouse_col .. "|" + endif +< + There is no prompt, you will somehow have to make clear to the + user that a character has to be typed. The screen is not + redrawn, e.g. when resizing the window. + + There is no mapping for the character. + Key codes are replaced, thus when the user presses the <Del> + key you get the code for the <Del> key, not the raw character + sequence. Examples: > + getchar() == "\<Del>" + getchar() == "\<S-Left>" +< This example redefines "f" to ignore case: > + :nmap f :call FindChar()<CR> + :function FindChar() + : let c = nr2char(getchar()) + : while col('.') < col('$') - 1 + : normal l + : if getline('.')[col('.') - 1] ==? c + : break + : endif + : endwhile + :endfunction +< +getcharmod() *getcharmod()* + The result is a Number which is the state of the modifiers for + the last obtained character with getchar() or in another way. + These values are added together: + 2 shift + 4 control + 8 alt (meta) + 16 meta (when it's different from ALT) + 32 mouse double click + 64 mouse triple click + 96 mouse quadruple click (== 32 + 64) + 128 command (Macintosh only) + Only the modifiers that have not been included in the + character itself are obtained. Thus Shift-a results in "A" + without a modifier. + + *getcharpos()* +getcharpos({expr}) + Get the position for String {expr}. Same as |getpos()| but the + column number in the returned List is a character index + instead of a byte index. + + Example: + With the cursor on '세' in line 5 with text "여보세요": > + getcharpos('.') returns [0, 5, 3, 0] + getpos('.') returns [0, 5, 7, 0] +< + Can also be used as a |method|: > + GetMark()->getcharpos() + +getcharsearch() *getcharsearch()* + Return the current character search information as a {dict} + with the following entries: + + char character previously used for a character + search (|t|, |f|, |T|, or |F|); empty string + if no character search has been performed + forward direction of character search; 1 for forward, + 0 for backward + until type of character search; 1 for a |t| or |T| + character search, 0 for an |f| or |F| + character search + + This can be useful to always have |;| and |,| search + forward/backward regardless of the direction of the previous + character search: > + :nnoremap <expr> ; getcharsearch().forward ? ';' : ',' + :nnoremap <expr> , getcharsearch().forward ? ',' : ';' +< Also see |setcharsearch()|. + + +getcharstr([expr]) *getcharstr()* + Get a single character from the user or input stream as a + string. + If [expr] is omitted, wait until a character is available. + If [expr] is 0 or false, only get a character when one is + available. Return an empty string otherwise. + If [expr] is 1 or true, only check if a character is + available, it is not consumed. Return an empty string + if no character is available. + Otherwise this works like |getchar()|, except that a number + result is converted to a string. + + +getcmdline() *getcmdline()* + Return the current command-line. Only works when the command + line is being edited, thus requires use of |c_CTRL-\_e| or + |c_CTRL-R_=|. + Example: > + :cmap <F7> <C-\>eescape(getcmdline(), ' \')<CR> +< Also see |getcmdtype()|, |getcmdpos()| and |setcmdpos()|. + Returns an empty string when entering a password or using + |inputsecret()|. + +getcmdpos() *getcmdpos()* + Return the position of the cursor in the command line as a + byte count. The first column is 1. + Only works when editing the command line, thus requires use of + |c_CTRL-\_e| or |c_CTRL-R_=| or an expression mapping. + Returns 0 otherwise. + Also see |getcmdtype()|, |setcmdpos()| and |getcmdline()|. + +getcmdtype() *getcmdtype()* + Return the current command-line type. Possible return values + are: + : normal Ex command + > debug mode command |debug-mode| + / forward search command + ? backward search command + @ |input()| command + - |:insert| or |:append| command + = |i_CTRL-R_=| + Only works when editing the command line, thus requires use of + |c_CTRL-\_e| or |c_CTRL-R_=| or an expression mapping. + Returns an empty string otherwise. + Also see |getcmdpos()|, |setcmdpos()| and |getcmdline()|. + +getcmdwintype() *getcmdwintype()* + Return the current |command-line-window| type. Possible return + values are the same as |getcmdtype()|. Returns an empty string + when not in the command-line window. + +getcompletion({pat}, {type} [, {filtered}]) *getcompletion()* + Return a list of command-line completion matches. The String + {type} argument specifies what for. The following completion + types are supported: + + arglist file names in argument list + augroup autocmd groups + buffer buffer names + behave :behave suboptions + cmdline |cmdline-completion| result + color color schemes + command Ex command + compiler compilers + cscope |:cscope| suboptions + diff_buffer |:diffget| and |:diffput| completion + dir directory names + environment environment variable names + event autocommand events + expression Vim expression + file file and directory names + file_in_path file and directory names in |'path'| + filetype filetype names |'filetype'| + function function name + help help subjects + highlight highlight groups + history :history suboptions + locale locale names (as output of locale -a) + mapclear buffer argument + mapping mapping name + menu menus + messages |:messages| suboptions + option options + packadd optional package |pack-add| names + shellcmd Shell command + sign |:sign| suboptions + syntax syntax file names |'syntax'| + syntime |:syntime| suboptions + tag tags + tag_listfiles tags, file names + user user names + var user variables + + If {pat} is an empty string, then all the matches are + returned. Otherwise only items matching {pat} are returned. + See |wildcards| for the use of special characters in {pat}. + + If the optional {filtered} flag is set to 1, then 'wildignore' + is applied to filter the results. Otherwise all the matches + are returned. The 'wildignorecase' option always applies. + + If {type} is "cmdline", then the |cmdline-completion| result is + returned. For example, to complete the possible values after + a ":call" command: > + echo getcompletion('call ', 'cmdline') +< + If there are no matches, an empty list is returned. An + invalid value for {type} produces an error. + + Can also be used as a |method|: > + GetPattern()->getcompletion('color') +< + *getcurpos()* +getcurpos([{winid}]) + Get the position of the cursor. This is like getpos('.'), but + includes an extra "curswant" in the list: + [0, lnum, col, off, curswant] ~ + The "curswant" number is the preferred column when moving the + cursor vertically. Also see |getcursorcharpos()| and + |getpos()|. + The first "bufnum" item is always zero. The byte position of + the cursor is returned in 'col'. To get the character + position, use |getcursorcharpos()|. + + The optional {winid} argument can specify the window. It can + be the window number or the |window-ID|. The last known + cursor position is returned, this may be invalid for the + current value of the buffer if it is not the current window. + If {winid} is invalid a list with zeroes is returned. + + This can be used to save and restore the cursor position: > + let save_cursor = getcurpos() + MoveTheCursorAround + call setpos('.', save_cursor) +< Note that this only works within the window. See + |winrestview()| for restoring more state. + + Can also be used as a |method|: > + GetWinid()->getcurpos() +< + *getcursorcharpos()* +getcursorcharpos([{winid}]) + Same as |getcurpos()| but the column number in the returned + List is a character index instead of a byte index. + + Example: + With the cursor on '보' in line 3 with text "여보세요": > + getcursorcharpos() returns [0, 3, 2, 0, 3] + getcurpos() returns [0, 3, 4, 0, 3] +< + Can also be used as a |method|: > + GetWinid()->getcursorcharpos() + +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 + ignored. + Tabs and windows are identified by their respective numbers, + 0 means current tab or window. Missing tab number implies 0. + Thus the following are equivalent: > + getcwd(0) + getcwd(0, 0) +< If {winnr} is -1 it is ignored, only the tab is resolved. + {winnr} can be the window number or the |window-ID|. + If both {winnr} and {tabnr} are -1 the global working + directory is returned. + Throw error if the arguments are invalid. |E5000| |E5001| |E5002| + + Can also be used as a |method|: > + GetWinnr()->getcwd() + +getenv({name}) *getenv()* + Return the value of environment variable {name}. The {name} + argument is a string, without a leading '$'. Example: > + myHome = getenv('HOME') + +< When the variable does not exist |v:null| is returned. That + is different from a variable set to an empty string. + See also |expr-env|. + + Can also be used as a |method|: > + GetVarname()->getenv() + +getfontname([{name}]) *getfontname()* + Without an argument returns the name of the normal font being + used. Like what is used for the Normal highlight group + |hl-Normal|. + With an argument a check is done whether String {name} is a + valid font name. If not then an empty string is returned. + Otherwise the actual font name is returned, or {name} if the + GUI does not support obtaining the real name. + Only works when the GUI is running, thus not in your vimrc or + gvimrc file. Use the |GUIEnter| autocommand to use this + function just after the GUI has started. + +getfperm({fname}) *getfperm()* + The result is a String, which is the read, write, and execute + permissions of the given file {fname}. + If {fname} does not exist or its directory cannot be read, an + empty string is returned. + The result is of the form "rwxrwxrwx", where each group of + "rwx" flags represent, in turn, the permissions of the owner + of the file, the group the file belongs to, and other users. + If a user does not have a given permission the flag for this + is replaced with the string "-". Examples: > + :echo getfperm("/etc/passwd") + :echo getfperm(expand("~/.config/nvim/init.vim")) +< This will hopefully (from a security point of view) display + the string "rw-r--r--" or even "rw-------". + + Can also be used as a |method|: > + GetFilename()->getfperm() +< + For setting permissions use |setfperm()|. + +getfsize({fname}) *getfsize()* + The result is a Number, which is the size in bytes of the + given file {fname}. + If {fname} is a directory, 0 is returned. + If the file {fname} can't be found, -1 is returned. + If the size of {fname} is too big to fit in a Number then -2 + is returned. + + Can also be used as a |method|: > + GetFilename()->getfsize() + +getftime({fname}) *getftime()* + The result is a Number, which is the last modification time of + the given file {fname}. The value is measured as seconds + since 1st Jan 1970, and may be passed to strftime(). See also + |localtime()| and |strftime()|. + If the file {fname} can't be found -1 is returned. + + Can also be used as a |method|: > + GetFilename()->getftime() + +getftype({fname}) *getftype()* + The result is a String, which is a description of the kind of + file of the given file {fname}. + If {fname} does not exist an empty string is returned. + Here is a table over different kinds of files and their + results: + Normal file "file" + Directory "dir" + Symbolic link "link" + Block device "bdev" + Character device "cdev" + Socket "socket" + FIFO "fifo" + All other "other" + Example: > + getftype("/home") +< Note that a type such as "link" will only be returned on + systems that support it. On some systems only "dir" and + "file" are returned. + + Can also be used as a |method|: > + GetFilename()->getftype() + +getjumplist([{winnr} [, {tabnr}]]) *getjumplist()* + Returns the |jumplist| for the specified window. + + Without arguments use the current window. + With {winnr} only use this window in the current tab page. + {winnr} can also be a |window-ID|. + With {winnr} and {tabnr} use the window in the specified tab + page. + + The returned list contains two entries: a list with the jump + locations and the last used jump position number in the list. + Each entry in the jump location list is a dictionary with + the following entries: + bufnr buffer number + col column number + coladd column offset for 'virtualedit' + filename filename if available + lnum line number + + Can also be used as a |method|: > + GetWinnr()->getjumplist() + +< *getline()* +getline({lnum} [, {end}]) + Without {end} the result is a String, which is line {lnum} + from the current buffer. Example: > + getline(1) +< When {lnum} is a String that doesn't start with a + digit, |line()| is called to translate the String into a Number. + To get the line under the cursor: > + getline(".") +< When {lnum} is a number smaller than 1 or bigger than the + number of lines in the buffer, an empty string is returned. + + When {end} is given the result is a |List| where each item is + a line from the current buffer in the range {lnum} to {end}, + including line {end}. + {end} is used in the same way as {lnum}. + Non-existing lines are silently omitted. + When {end} is before {lnum} an empty |List| is returned. + Example: > + :let start = line('.') + :let end = search("^$") - 1 + :let lines = getline(start, end) + +< Can also be used as a |method|: > + ComputeLnum()->getline() + +< To get lines from another buffer see |getbufline()| + +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. + + For a location list window, the displayed location list is + returned. For an invalid window number {nr}, an empty list is + returned. Otherwise, same as |getqflist()|. + + If the optional {what} dictionary argument is supplied, then + returns the items listed in {what} as a dictionary. Refer to + |getqflist()| for the supported items in {what}. + If {what} contains 'filewinid', then returns the id of the + window used to display files from the location list. This + field is applicable only when called from a location list + window. See |location-list-file-window| for more details. + + Returns a |Dictionary| with default values if there is no + location list for the window {nr}. + Returns an empty Dictionary if window {nr} does not exist. + + Examples (See also |getqflist-examples|): > + :echo getloclist(3, {'all': 0}) + :echo getloclist(5, {'filewinid': 0}) + + +getmarklist([{buf}]) *getmarklist()* + Without the {buf} argument returns a |List| with information + about all the global marks. |mark| + + If the optional {buf} argument is specified, returns the + local marks defined in buffer {buf}. For the use of {buf}, + see |bufname()|. + + Each item in the returned List is a |Dict| with the following: + mark name of the mark prefixed by "'" + pos a |List| with the position of the mark: + [bufnum, lnum, col, off] + Refer to |getpos()| for more information. + file file name + + Refer to |getpos()| for getting information about a specific + mark. + + Can also be used as a |method|: > + GetBufnr()->getmarklist() + +getmatches([{win}]) *getmatches()* + Returns a |List| with all matches previously defined for the + current window by |matchadd()| and the |:match| commands. + |getmatches()| is useful in combination with |setmatches()|, + as |setmatches()| can restore a list of matches saved by + |getmatches()|. + If {win} is specified, use the window with this number or + window ID instead of the current window. + Example: > + :echo getmatches() +< [{'group': 'MyGroup1', 'pattern': 'TODO', + 'priority': 10, 'id': 1}, {'group': 'MyGroup2', + 'pattern': 'FIXME', 'priority': 10, 'id': 2}] > + :let m = getmatches() + :call clearmatches() + :echo getmatches() +< [] > + :call setmatches(m) + :echo getmatches() +< [{'group': 'MyGroup1', 'pattern': 'TODO', + 'priority': 10, 'id': 1}, {'group': 'MyGroup2', + 'pattern': 'FIXME', 'priority': 10, 'id': 2}] > + :unlet m +< +getmousepos() *getmousepos()* + Returns a Dictionary with the last known position of the + mouse. This can be used in a mapping for a mouse click. The + items are: + screenrow screen row + screencol screen column + winid Window ID of the click + winrow row inside "winid" + wincol column inside "winid" + line text line inside "winid" + column text column inside "winid" + All numbers are 1-based. + + If not over a window, e.g. when in the command line, then only + "screenrow" and "screencol" are valid, the others are zero. + + When on the status line below a window or the vertical + separater right of a window, the "line" and "column" values + are zero. + + When the position is after the text then "column" is the + length of the text in bytes plus one. + + If the mouse is over a focusable floating window then that + window is used. + + When using |getchar()| the Vim variables |v:mouse_lnum|, + |v:mouse_col| and |v:mouse_winid| also provide these values. + + *getpid()* +getpid() Return a Number which is the process ID of the Vim process. + This is a unique number, until Vim exits. + + *getpos()* +getpos({expr}) Get the position for String {expr}. For possible values of + {expr} see |line()|. For getting the cursor position see + |getcurpos()|. + The result is a |List| with four numbers: + [bufnum, lnum, col, off] + "bufnum" is zero, unless a mark like '0 or 'A is used, then it + is the buffer number of the mark. + "lnum" and "col" are the position in the buffer. The first + column is 1. + The "off" number is zero, unless 'virtualedit' is used. Then + it is the offset in screen columns from the start of the + character. E.g., a position within a <Tab> or after the last + character. + Note that for '< and '> Visual mode matters: when it is "V" + (visual line mode) the column of '< is zero and the column of + '> is a large number. + The column number in the returned List is the byte position + within the line. To get the character position in the line, + use |getcharpos()|. + The column number can be very large, e.g. 2147483647, in which + case it means "after the end of the line". + This can be used to save and restore the position of a mark: > + let save_a_mark = getpos("'a") + ... + call setpos("'a", save_a_mark) +< Also see |getcharpos()|, |getcurpos()| and |setpos()|. + + Can also be used as a |method|: > + GetMark()->getpos() + +getqflist([{what}]) *getqflist()* + Returns a |List| with all the current quickfix errors. Each + list item is a dictionary with these entries: + bufnr number of buffer that has the file name, use + bufname() to get the name + module module name + lnum line number in the buffer (first line is 1) + end_lnum + end of line number if the item is multiline + col column number (first column is 1) + end_col end of column number if the item has range + vcol |TRUE|: "col" is visual column + |FALSE|: "col" is byte index + nr error number + pattern search pattern used to locate the error + text description of the error + type type of the error, 'E', '1', etc. + valid |TRUE|: recognized error message + + When there is no error list or it's empty, an empty list is + returned. Quickfix list entries with a non-existing buffer + number are returned with "bufnr" set to zero (Note: some + functions accept buffer number zero for the alternate buffer, + you may need to explicitly check for zero). + + Useful application: Find pattern matches in multiple files and + do something with them: > + :vimgrep /theword/jg *.c + :for d in getqflist() + : echo bufname(d.bufnr) ':' d.lnum '=' d.text + :endfor +< + If the optional {what} dictionary argument is supplied, then + returns only the items listed in {what} as a dictionary. The + following string items are supported in {what}: + changedtick get the total number of changes made + to the list |quickfix-changedtick| + context get the |quickfix-context| + efm errorformat to use when parsing "lines". If + not present, then the 'errorformat' option + value is used. + id get information for the quickfix list with + |quickfix-ID|; zero means the id for the + current list or the list specified by "nr" + idx get information for the quickfix entry at this + index in the list specified by 'id' or 'nr'. + If set to zero, then uses the current entry. + See |quickfix-index| + items quickfix list entries + lines parse a list of lines using 'efm' and return + the resulting entries. Only a |List| type is + accepted. The current quickfix list is not + modified. See |quickfix-parse|. + nr get information for this quickfix list; zero + means the current quickfix list and "$" means + the last quickfix list + size number of entries in the quickfix list + title get the list title |quickfix-title| + winid get the quickfix |window-ID| + all all of the above quickfix properties + Non-string items in {what} are ignored. To get the value of a + particular item, set it to zero. + If "nr" is not present then the current quickfix list is used. + If both "nr" and a non-zero "id" are specified, then the list + specified by "id" is used. + To get the number of lists in the quickfix stack, set "nr" to + "$" in {what}. The "nr" value in the returned dictionary + contains the quickfix stack size. + When "lines" is specified, all the other items except "efm" + are ignored. The returned dictionary contains the entry + "items" with the list of entries. + + The returned dictionary contains the following entries: + changedtick total number of changes made to the + list |quickfix-changedtick| + context quickfix list context. See |quickfix-context| + If not present, set to "". + id quickfix list ID |quickfix-ID|. If not + present, set to 0. + idx index of the quickfix entry in the list. If not + present, set to 0. + items quickfix list entries. If not present, set to + an empty list. + nr quickfix list number. If not present, set to 0 + size number of entries in the quickfix list. If not + present, set to 0. + title quickfix list title text. If not present, set + to "". + winid quickfix |window-ID|. If not present, set to 0 + + Examples (See also |getqflist-examples|): > + :echo getqflist({'all': 1}) + :echo getqflist({'nr': 2, 'title': 1}) + :echo getqflist({'lines' : ["F1:10:L10"]}) +< +getreg([{regname} [, 1 [, {list}]]]) *getreg()* + The result is a String, which is the contents of register + {regname}. Example: > + :let cliptext = getreg('*') +< When register {regname} was not set the result is an empty + string. + The {regname} argument must be a string. + + getreg('=') returns the last evaluated value of the expression + register. (For use in maps.) + getreg('=', 1) returns the expression itself, so that it can + be restored with |setreg()|. For other registers the extra + argument is ignored, thus you can always give it. + + If {list} is present and |TRUE|, the result type is changed + to |List|. Each list item is one text line. Use it if you care + about zero bytes possibly present inside register: without + third argument both NLs and zero bytes are represented as NLs + (see |NL-used-for-Nul|). + When the register was not set an empty list is returned. + + If {regname} is not specified, |v:register| is used. + + Can also be used as a |method|: > + GetRegname()->getreg() + +getreginfo([{regname}]) *getreginfo()* + Returns detailed information about register {regname} as a + Dictionary with the following entries: + regcontents List of lines contained in register + {regname}, like + |getreg|({regname}, 1, 1). + regtype the type of register {regname}, as in + |getregtype()|. + isunnamed Boolean flag, v:true if this register + is currently pointed to by the unnamed + register. + points_to for the unnamed register, gives the + single letter name of the register + currently pointed to (see |quotequote|). + For example, after deleting a line + with `dd`, this field will be "1", + which is the register that got the + deleted text. + + The {regname} argument is a string. If {regname} is invalid + or not set, an empty Dictionary will be returned. + If {regname} is not specified, |v:register| is used. + The returned Dictionary can be passed to |setreg()|. + + Can also be used as a |method|: > + GetRegname()->getreginfo() + +getregtype([{regname}]) *getregtype()* + The result is a String, which is type of register {regname}. + The value will be one of: + "v" for |charwise| text + "V" for |linewise| text + "<CTRL-V>{width}" for |blockwise-visual| text + "" for an empty or unknown register + <CTRL-V> is one character with value 0x16. + The {regname} argument is a string. If {regname} is not + specified, |v:register| is used. + + Can also be used as a |method|: > + GetRegname()->getregtype() + +gettabinfo([{tabnr}]) *gettabinfo()* + If {tabnr} is not specified, then information about all the + tab pages is returned as a |List|. Each List item is a + |Dictionary|. Otherwise, {tabnr} specifies the tab page + number and information about that one is returned. If the tab + page does not exist an empty List is returned. + + Each List item is a |Dictionary| with the following entries: + tabnr tab page number. + variables a reference to the dictionary with + tabpage-local variables + windows List of |window-ID|s in the tab page. + + Can also be used as a |method|: > + GetTabnr()->gettabinfo() + +gettabvar({tabnr}, {varname} [, {def}]) *gettabvar()* + Get the value of a tab-local variable {varname} in tab page + {tabnr}. |t:var| + Tabs are numbered starting with one. + The {varname} argument is a string. When {varname} is empty a + dictionary with all tab-local variables is returned. + Note that the name without "t:" must be used. + When the tab or variable doesn't exist {def} or an empty + string is returned, there is no error message. + + Can also be used as a |method|: > + GetTabnr()->gettabvar(varname) + +gettabwinvar({tabnr}, {winnr}, {varname} [, {def}]) *gettabwinvar()* + Get the value of window-local variable {varname} in window + {winnr} in tab page {tabnr}. + The {varname} argument is a string. When {varname} is empty a + dictionary with all window-local variables is returned. + When {varname} is equal to "&" get the values of all + window-local options in a |Dictionary|. + Otherwise, when {varname} starts with "&" get the value of a + window-local option. + Note that {varname} must be the name without "w:". + Tabs are numbered starting with one. For the current tabpage + use |getwinvar()|. + {winnr} can be the window number or the |window-ID|. + When {winnr} is zero the current window is used. + This also works for a global option, buffer-local option and + window-local option, but it doesn't work for a global variable + or buffer-local variable. + When the tab, window or variable doesn't exist {def} or an + empty string is returned, there is no error message. + Examples: > + :let list_is_on = gettabwinvar(1, 2, '&list') + :echo "myvar = " .. gettabwinvar(3, 1, 'myvar') +< + To obtain all window-local variables use: > + gettabwinvar({tabnr}, {winnr}, '&') + +< Can also be used as a |method|: > + GetTabnr()->gettabwinvar(winnr, varname) + +gettagstack([{winnr}]) *gettagstack()* + The result is a Dict, which is the tag stack of window {winnr}. + {winnr} can be the window number or the |window-ID|. + When {winnr} is not specified, the current window is used. + When window {winnr} doesn't exist, an empty Dict is returned. + + The returned dictionary contains the following entries: + curidx Current index in the stack. When at + top of the stack, set to (length + 1). + Index of bottom of the stack is 1. + items List of items in the stack. Each item + is a dictionary containing the + entries described below. + length Number of entries in the stack. + + Each item in the stack is a dictionary with the following + entries: + bufnr buffer number of the current jump + from cursor position before the tag jump. + See |getpos()| for the format of the + returned list. + matchnr current matching tag number. Used when + multiple matching tags are found for a + name. + tagname name of the tag + + See |tagstack| for more information about the tag stack. + + Can also be used as a |method|: > + GetWinnr()->gettagstack() + +getwininfo([{winid}]) *getwininfo()* + Returns information about windows as a |List| with Dictionaries. + + If {winid} is given Information about the window with that ID + is returned, as a |List| with one item. If the window does not + exist the result is an empty list. + + Without {winid} information about all the windows in all the + tab pages is returned. + + Each List item is a |Dictionary| with the following entries: + botline last complete displayed buffer line + bufnr number of buffer in the window + height window height (excluding winbar) + loclist 1 if showing a location list + quickfix 1 if quickfix or location list window + terminal 1 if a terminal window + tabnr tab page number + topline first displayed buffer line + variables a reference to the dictionary with + window-local variables + width window width + winbar 1 if the window has a toolbar, 0 + otherwise + wincol leftmost screen column of the window; + "col" from |win_screenpos()| + textoff number of columns occupied by any + 'foldcolumn', 'signcolumn' and line + number in front of the text + winid |window-ID| + winnr window number + winrow topmost screen line of the window; + "row" from |win_screenpos()| + + Can also be used as a |method|: > + GetWinnr()->getwininfo() + +getwinpos([{timeout}]) *getwinpos()* + The result is a |List| with two numbers, the result of + |getwinposx()| and |getwinposy()| combined: + [x-pos, y-pos] + {timeout} can be used to specify how long to wait in msec for + a response from the terminal. When omitted 100 msec is used. + + Use a longer time for a remote terminal. + When using a value less than 10 and no response is received + within that time, a previously reported position is returned, + if available. This can be used to poll for the position and + do some work in the meantime: > + while 1 + let res = getwinpos(1) + if res[0] >= 0 + break + endif + " Do some work here + endwhile +< + Can also be used as a |method|: > + GetTimeout()->getwinpos() +< + *getwinposx()* +getwinposx() The result is a Number, which is the X coordinate in pixels of + the left hand side of the GUI Vim window. The result will be + -1 if the information is not available. + The value can be used with `:winpos`. + + *getwinposy()* +getwinposy() The result is a Number, which is the Y coordinate in pixels of + the top of the GUI Vim window. The result will be -1 if the + information is not available. + The value can be used with `:winpos`. + +getwinvar({winnr}, {varname} [, {def}]) *getwinvar()* + Like |gettabwinvar()| for the current tabpage. + Examples: > + :let list_is_on = getwinvar(2, '&list') + :echo "myvar = " .. getwinvar(1, 'myvar') + +< Can also be used as a |method|: > + GetWinnr()->getwinvar(varname) +< +glob({expr} [, {nosuf} [, {list} [, {alllinks}]]]) *glob()* + Expand the file wildcards in {expr}. See |wildcards| for the + use of special characters. + + Unless the optional {nosuf} argument is given and is |TRUE|, + the 'suffixes' and 'wildignore' options apply: Names matching + one of the patterns in 'wildignore' will be skipped and + 'suffixes' affect the ordering of matches. + 'wildignorecase' always applies. + + When {list} is present and it is |TRUE| the result is a |List| + with all matching files. The advantage of using a List is, + you also get filenames containing newlines correctly. + Otherwise the result is a String and when there are several + matches, they are separated by <NL> characters. + + If the expansion fails, the result is an empty String or List. + + You can also use |readdir()| if you need to do complicated + things, such as limiting the number of matches. + + A name for a non-existing file is not included. A symbolic + link is only included if it points to an existing file. + However, when the {alllinks} argument is present and it is + |TRUE| then all symbolic links are included. + + For most systems backticks can be used to get files names from + any external command. Example: > + :let tagfiles = glob("`find . -name tags -print`") + :let &tags = substitute(tagfiles, "\n", ",", "g") +< The result of the program inside the backticks should be one + item per line. Spaces inside an item are allowed. + + See |expand()| for expanding special Vim variables. See + |system()| for getting the raw output of an external command. + + Can also be used as a |method|: > + GetExpr()->glob() + +glob2regpat({string}) *glob2regpat()* + Convert a file pattern, as used by glob(), into a search + pattern. The result can be used to match with a string that + is a file name. E.g. > + if filename =~ glob2regpat('Make*.mak') +< This is equivalent to: > + if filename =~ '^Make.*\.mak$' +< When {string} is an empty string the result is "^$", match an + empty string. + Note that the result depends on the system. On MS-Windows + a backslash usually means a path separator. + + Can also be used as a |method|: > + GetExpr()->glob2regpat() +< *globpath()* +globpath({path}, {expr} [, {nosuf} [, {list} [, {allinks}]]]) + Perform glob() for String {expr} on all directories in {path} + and concatenate the results. Example: > + :echo globpath(&rtp, "syntax/c.vim") +< + {path} is a comma-separated list of directory names. Each + directory name is prepended to {expr} and expanded like with + |glob()|. A path separator is inserted when needed. + To add a comma inside a directory name escape it with a + backslash. Note that on MS-Windows a directory may have a + trailing backslash, remove it if you put a comma after it. + If the expansion fails for one of the directories, there is no + error message. + + Unless the optional {nosuf} argument is given and is |TRUE|, + the 'suffixes' and 'wildignore' options apply: Names matching + one of the patterns in 'wildignore' will be skipped and + 'suffixes' affect the ordering of matches. + + When {list} is present and it is |TRUE| the result is a |List| + with all matching files. The advantage of using a List is, you + also get filenames containing newlines correctly. Otherwise + the result is a String and when there are several matches, + they are separated by <NL> characters. Example: > + :echo globpath(&rtp, "syntax/c.vim", 0, 1) +< + {allinks} is used as with |glob()|. + + The "**" item can be used to search in a directory tree. + For example, to find all "README.txt" files in the directories + in 'runtimepath' and below: > + :echo globpath(&rtp, "**/README.txt") +< Upwards search and limiting the depth of "**" is not + supported, thus using 'path' will not always work properly. + + Can also be used as a |method|, the base is passed as the + second argument: > + GetExpr()->globpath(&rtp) +< + *has()* +has({feature}) Returns 1 if {feature} is supported, 0 otherwise. The + {feature} argument is a feature name like "nvim-0.2.1" or + "win32", see below. See also |exists()|. + + If the code has a syntax error, then Nvim may skip the rest + of the line and miss |:endif|. > + if has('feature') | let x = this->breaks->without->the->feature | endif +< + Put |:if| and |:endif| on separate lines to avoid the + syntax error. > + if has('feature') + let x = this->breaks->without->the->feature + endif +< + Vim's compile-time feature-names (prefixed with "+") are not + recognized because Nvim is always compiled with all possible + features. |feature-compile| + + Feature names can be: + 1. Nvim version. For example the "nvim-0.2.1" feature means + that Nvim is version 0.2.1 or later: > + :if has("nvim-0.2.1") + +< 2. Runtime condition or other pseudo-feature. For example the + "win32" feature checks if the current system is Windows: > + :if has("win32") +< *feature-list* + List of supported pseudo-feature names: + acl |ACL| support. + bsd BSD system (not macOS, use "mac" for that). + clipboard |clipboard| provider is available. + fname_case Case in file names matters (for Darwin and MS-Windows + this is not present). + iconv Can use |iconv()| for conversion. + linux Linux system. + mac MacOS system. + nvim This is Nvim. + python3 Legacy Vim |python3| interface. |has-python| + pythonx Legacy Vim |python_x| interface. |has-pythonx| + sun SunOS system. + ttyin input is a terminal (tty). + ttyout output is a terminal (tty). + unix Unix system. + *vim_starting* True during |startup|. + win32 Windows system (32 or 64 bit). + win64 Windows system (64 bit). + wsl WSL (Windows Subsystem for Linux) system. + + *has-patch* + 3. Vim patch. For example the "patch123" feature means that + Vim patch 123 at the current |v:version| was included: > + :if v:version > 602 || v:version == 602 && has("patch148") + +< 4. Vim version. For example the "patch-7.4.237" feature means + that Nvim is Vim-compatible to version 7.4.237 or later. > + :if has("patch-7.4.237") + + +has_key({dict}, {key}) *has_key()* + The result is a Number, which is TRUE if |Dictionary| {dict} + has an entry with key {key}. FALSE otherwise. The {key} + argument is a string. + + Can also be used as a |method|: > + mydict->has_key(key) + +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. + + Tabs and windows are identified by their respective numbers, + 0 means current tab or window. Missing argument implies 0. + Thus the following are equivalent: > + haslocaldir() + haslocaldir(0) + haslocaldir(0, 0) +< With {winnr} use that window in the current tabpage. + With {winnr} and {tabnr} use the window in that tabpage. + {winnr} can be the window number or the |window-ID|. + If {winnr} is -1 it is ignored, only the tab is resolved. + Throw error if the arguments are invalid. |E5000| |E5001| |E5002| + + Can also be used as a |method|: > + GetWinnr()->haslocaldir() + +hasmapto({what} [, {mode} [, {abbr}]]) *hasmapto()* + The result is a Number, which is TRUE if there is a mapping + that contains {what} in somewhere in the rhs (what it is + mapped to) and this mapping exists in one of the modes + indicated by {mode}. + The arguments {what} and {mode} are strings. + When {abbr} is there and it is |TRUE| use abbreviations + instead of mappings. Don't forget to specify Insert and/or + Command-line mode. + Both the global mappings and the mappings local to the current + buffer are checked for a match. + If no matching mapping is found FALSE is returned. + The following characters are recognized in {mode}: + n Normal mode + v Visual and Select mode + x Visual mode + s Select mode + o Operator-pending mode + i Insert mode + l Language-Argument ("r", "f", "t", etc.) + c Command-line mode + When {mode} is omitted, "nvo" is used. + + This function is useful to check if a mapping already exists + to a function in a Vim script. Example: > + :if !hasmapto('\ABCdoit') + : map <Leader>d \ABCdoit + :endif +< This installs the mapping to "\ABCdoit" only if there isn't + already a mapping to "\ABCdoit". + + Can also be used as a |method|: > + GetRHS()->hasmapto() + +histadd({history}, {item}) *histadd()* + Add the String {item} to the history {history} which can be + one of: *hist-names* + "cmd" or ":" command line history + "search" or "/" search pattern history + "expr" or "=" typed expression history + "input" or "@" input line history + "debug" or ">" debug command history + empty the current or last used history + The {history} string does not need to be the whole name, one + character is sufficient. + If {item} does already exist in the history, it will be + shifted to become the newest entry. + The result is a Number: TRUE if the operation was successful, + otherwise FALSE is returned. + + Example: > + :call histadd("input", strftime("%Y %b %d")) + :let date=input("Enter date: ") +< This function is not available in the |sandbox|. + + Can also be used as a |method|, the base is passed as the + second argument: > + GetHistory()->histadd('search') + +histdel({history} [, {item}]) *histdel()* + Clear {history}, i.e. delete all its entries. See |hist-names| + for the possible values of {history}. + + If the parameter {item} evaluates to a String, it is used as a + regular expression. All entries matching that expression will + be removed from the history (if there are any). + Upper/lowercase must match, unless "\c" is used |/\c|. + If {item} evaluates to a Number, it will be interpreted as + an index, see |:history-indexing|. The respective entry will + be removed if it exists. + + The result is TRUE for a successful operation, otherwise FALSE + is returned. + + Examples: + Clear expression register history: > + :call histdel("expr") +< + Remove all entries starting with "*" from the search history: > + :call histdel("/", '^\*') +< + The following three are equivalent: > + :call histdel("search", histnr("search")) + :call histdel("search", -1) + :call histdel("search", '^' .. histget("search", -1) .. '$') +< + To delete the last search pattern and use the last-but-one for + the "n" command and 'hlsearch': > + :call histdel("search", -1) + :let @/ = histget("search", -1) +< + Can also be used as a |method|: > + GetHistory()->histdel() + +histget({history} [, {index}]) *histget()* + The result is a String, the entry with Number {index} from + {history}. See |hist-names| for the possible values of + {history}, and |:history-indexing| for {index}. If there is + no such entry, an empty String is returned. When {index} is + omitted, the most recent item from the history is used. + + Examples: + Redo the second last search from history. > + :execute '/' .. histget("search", -2) + +< Define an Ex command ":H {num}" that supports re-execution of + the {num}th entry from the output of |:history|. > + :command -nargs=1 H execute histget("cmd", 0+<args>) +< + Can also be used as a |method|: > + GetHistory()->histget() + +histnr({history}) *histnr()* + The result is the Number of the current entry in {history}. + See |hist-names| for the possible values of {history}. + If an error occurred, -1 is returned. + + Example: > + :let inp_index = histnr("expr") + +< Can also be used as a |method|: > + GetHistory()->histnr() +< +hlexists({name}) *hlexists()* + The result is a Number, which is TRUE if a highlight group + called {name} exists. This is when the group has been + defined in some way. Not necessarily when highlighting has + been defined for it, it may also have been used for a syntax + item. + + Can also be used as a |method|: > + GetName()->hlexists() +< + *hlID()* +hlID({name}) The result is a Number, which is the ID of the highlight group + with name {name}. When the highlight group doesn't exist, + zero is returned. + This can be used to retrieve information about the highlight + group. For example, to get the background color of the + "Comment" group: > + :echo synIDattr(synIDtrans(hlID("Comment")), "bg") +< + Can also be used as a |method|: > + GetName()->hlID() + +hostname() *hostname()* + The result is a String, which is the name of the machine on + which Vim is currently running. Machine names greater than + 256 characters long are truncated. + +iconv({string}, {from}, {to}) *iconv()* + The result is a String, which is the text {string} converted + from encoding {from} to encoding {to}. + When the conversion completely fails an empty string is + returned. When some characters could not be converted they + are replaced with "?". + The encoding names are whatever the iconv() library function + can accept, see ":!man 3 iconv". + Note that Vim uses UTF-8 for all Unicode encodings, conversion + from/to UCS-2 is automatically changed to use UTF-8. You + cannot use UCS-2 in a string anyway, because of the NUL bytes. + + Can also be used as a |method|: > + GetText()->iconv('latin1', 'utf-8') +< + *indent()* +indent({lnum}) The result is a Number, which is indent of line {lnum} in the + current buffer. The indent is counted in spaces, the value + of 'tabstop' is relevant. {lnum} is used just like in + |getline()|. + When {lnum} is invalid -1 is returned. + + Can also be used as a |method|: > + GetLnum()->indent() + +index({object}, {expr} [, {start} [, {ic}]]) *index()* + If {object} is a |List| return the lowest index where the item + has a value equal to {expr}. There is no automatic + conversion, so the String "4" is different from the Number 4. + And the Number 4 is different from the Float 4.0. The value + of 'ignorecase' is not used here, case always matters. + + If {object} is a |Blob| return the lowest index where the byte + value is equal to {expr}. + + If {start} is given then start looking at the item with index + {start} (may be negative for an item relative to the end). + When {ic} is given and it is |TRUE|, ignore case. Otherwise + case must match. + -1 is returned when {expr} is not found in {object}. + Example: > + :let idx = index(words, "the") + :if index(numbers, 123) >= 0 + +< Can also be used as a |method|: > + GetObject()->index(what) + +input({prompt} [, {text} [, {completion}]]) *input()* +input({opts}) + The result is a String, which is whatever the user typed on + the command-line. The {prompt} argument is either a prompt + string, or a blank string (for no prompt). A '\n' can be used + in the prompt to start a new line. + + In the second form it accepts a single dictionary with the + following keys, any of which may be omitted: + + Key Default Description ~ + prompt "" Same as {prompt} in the first form. + default "" Same as {text} in the first form. + completion nothing Same as {completion} in the first form. + cancelreturn "" The value returned when the dialog is + cancelled. + highlight nothing Highlight handler: |Funcref|. + + The highlighting set with |:echohl| is used for the prompt. + The input is entered just like a command-line, with the same + editing commands and mappings. There is a separate history + for lines typed for input(). + Example: > + :if input("Coffee or beer? ") == "beer" + : echo "Cheers!" + :endif +< + If the optional {text} argument is present and not empty, this + is used for the default reply, as if the user typed this. + Example: > + :let color = input("Color? ", "white") + +< The optional {completion} argument specifies the type of + completion supported for the input. Without it completion is + not performed. The supported completion types are the same as + that can be supplied to a user-defined command using the + "-complete=" argument. Refer to |:command-completion| for + more information. Example: > + let fname = input("File: ", "", "file") + +< *input()-highlight* *E5400* *E5402* + The optional `highlight` key allows specifying function which + will be used for highlighting user input. This function + receives user input as its only argument and must return + a list of 3-tuples [hl_start_col, hl_end_col + 1, hl_group] + where + hl_start_col is the first highlighted column, + hl_end_col is the last highlighted column (+ 1!), + hl_group is |:hi| group used for highlighting. + *E5403* *E5404* *E5405* *E5406* + Both hl_start_col and hl_end_col + 1 must point to the start + of the multibyte character (highlighting must not break + multibyte characters), hl_end_col + 1 may be equal to the + input length. Start column must be in range [0, len(input)), + end column must be in range (hl_start_col, len(input)], + sections must be ordered so that next hl_start_col is greater + then or equal to previous hl_end_col. + + Example (try some input with parentheses): > + highlight RBP1 guibg=Red ctermbg=red + highlight RBP2 guibg=Yellow ctermbg=yellow + highlight RBP3 guibg=Green ctermbg=green + highlight RBP4 guibg=Blue ctermbg=blue + let g:rainbow_levels = 4 + function! RainbowParens(cmdline) + let ret = [] + let i = 0 + let lvl = 0 + while i < len(a:cmdline) + if a:cmdline[i] is# '(' + call add(ret, [i, i + 1, 'RBP' .. ((lvl % g:rainbow_levels) + 1)]) + let lvl += 1 + elseif a:cmdline[i] is# ')' + let lvl -= 1 + call add(ret, [i, i + 1, 'RBP' .. ((lvl % g:rainbow_levels) + 1)]) + endif + let i += 1 + endwhile + return ret + endfunction + call input({'prompt':'>','highlight':'RainbowParens'}) +< + Highlight function is called at least once for each new + displayed input string, before command-line is redrawn. It is + expected that function is pure for the duration of one input() + call, i.e. it produces the same output for the same input, so + output may be memoized. Function is run like under |:silent| + modifier. If the function causes any errors, it will be + skipped for the duration of the current input() call. + + Highlighting is disabled if command-line contains arabic + characters. + + NOTE: This function must not be used in a startup file, for + the versions that only run in GUI mode (e.g., the Win32 GUI). + Note: When input() is called from within a mapping it will + consume remaining characters from that mapping, because a + mapping is handled like the characters were typed. + Use |inputsave()| before input() and |inputrestore()| + after input() to avoid that. Another solution is to avoid + that further characters follow in the mapping, e.g., by using + |:execute| or |:normal|. + + Example with a mapping: > + :nmap \x :call GetFoo()<CR>:exe "/" .. Foo<CR> + :function GetFoo() + : call inputsave() + : let g:Foo = input("enter search pattern: ") + : call inputrestore() + :endfunction + +< Can also be used as a |method|: > + GetPrompt()->input() + +inputlist({textlist}) *inputlist()* + {textlist} must be a |List| of strings. This |List| is + displayed, one string per line. The user will be prompted to + enter a number, which is returned. + The user can also select an item by clicking on it with the + mouse, if the mouse is enabled in the command line ('mouse' is + "a" or includes "c"). For the first string 0 is returned. + When clicking above the first item a negative number is + returned. When clicking on the prompt one more than the + length of {textlist} is returned. + Make sure {textlist} has less than 'lines' entries, otherwise + it won't work. It's a good idea to put the entry number at + the start of the string. And put a prompt in the first item. + Example: > + let color = inputlist(['Select color:', '1. red', + \ '2. green', '3. blue']) + +< Can also be used as a |method|: > + GetChoices()->inputlist() + +inputrestore() *inputrestore()* + Restore typeahead that was saved with a previous |inputsave()|. + Should be called the same number of times inputsave() is + called. Calling it more often is harmless though. + Returns TRUE when there is nothing to restore, FALSE otherwise. + +inputsave() *inputsave()* + Preserve typeahead (also from mappings) and clear it, so that + a following prompt gets input from the user. Should be + followed by a matching inputrestore() after the prompt. Can + be used several times, in which case there must be just as + many inputrestore() calls. + Returns TRUE when out of memory, FALSE otherwise. + +inputsecret({prompt} [, {text}]) *inputsecret()* + This function acts much like the |input()| function with but + two exceptions: + a) the user's response will be displayed as a sequence of + asterisks ("*") thereby keeping the entry secret, and + b) the user's response will not be recorded on the input + |history| stack. + The result is a String, which is whatever the user actually + typed on the command-line in response to the issued prompt. + NOTE: Command-line completion is not supported. + + Can also be used as a |method|: > + GetPrompt()->inputsecret() + +insert({object}, {item} [, {idx}]) *insert()* + When {object} is a |List| or a |Blob| insert {item} at the start + of it. + + If {idx} is specified insert {item} before the item with index + {idx}. If {idx} is zero it goes before the first item, just + like omitting {idx}. A negative {idx} is also possible, see + |list-index|. -1 inserts just before the last item. + + Returns the resulting |List| or |Blob|. Examples: > + :let mylist = insert([2, 3, 5], 1) + :call insert(mylist, 4, -1) + :call insert(mylist, 6, len(mylist)) +< The last example can be done simpler with |add()|. + Note that when {item} is a |List| it is inserted as a single + item. Use |extend()| to concatenate |Lists|. + + Can also be used as a |method|: > + mylist->insert(item) + +interrupt() *interrupt()* + Interrupt script execution. It works more or less like the + user typing CTRL-C, most commands won't execute and control + returns to the user. This is useful to abort execution + from lower down, e.g. in an autocommand. Example: > + :function s:check_typoname(file) + : if fnamemodify(a:file, ':t') == '[' + : echomsg 'Maybe typo' + : call interrupt() + : endif + :endfunction + :au BufWritePre * call s:check_typoname(expand('<amatch>')) + +invert({expr}) *invert()* + Bitwise invert. The argument is converted to a number. A + List, Dict or Float argument causes an error. Example: > + :let bits = invert(bits) +< Can also be used as a |method|: > + :let bits = bits->invert() + +isdirectory({directory}) *isdirectory()* + The result is a Number, which is |TRUE| when a directory + with the name {directory} exists. If {directory} doesn't + exist, or isn't a directory, the result is |FALSE|. {directory} + is any expression, which is used as a String. + + Can also be used as a |method|: > + GetName()->isdirectory() + +isinf({expr}) *isinf()* + Return 1 if {expr} is a positive infinity, or -1 a negative + infinity, otherwise 0. > + :echo isinf(1.0 / 0.0) +< 1 > + :echo isinf(-1.0 / 0.0) +< -1 + + Can also be used as a |method|: > + Compute()->isinf() + +islocked({expr}) *islocked()* *E786* + The result is a Number, which is |TRUE| when {expr} is the + name of a locked variable. + The string argument {expr} must be the name of a variable, + |List| item or |Dictionary| entry, not the variable itself! + Example: > + :let alist = [0, ['a', 'b'], 2, 3] + :lockvar 1 alist + :echo islocked('alist') " 1 + :echo islocked('alist[1]') " 0 + +< When {expr} is a variable that does not exist you get an error + message. Use |exists()| to check for existence. + + Can also be used as a |method|: > + GetName()->islocked() + +id({expr}) *id()* + Returns a |String| which is a unique identifier of the + container type (|List|, |Dict|, |Blob| and |Partial|). It is + guaranteed that for the mentioned types `id(v1) ==# id(v2)` + returns true iff `type(v1) == type(v2) && v1 is v2`. + Note that |v:_null_string|, |v:_null_list|, |v:_null_dict| and + |v:_null_blob| have the same `id()` with different types + because they are internally represented as NULL pointers. + `id()` returns a hexadecimal representanion of the pointers to + the containers (i.e. like `0x994a40`), same as `printf("%p", + {expr})`, but it is advised against counting on the exact + format of the return value. + + It is not guaranteed that `id(no_longer_existing_container)` + will not be equal to some other `id()`: new containers may + reuse identifiers of the garbage-collected ones. + +items({dict}) *items()* + Return a |List| with all the key-value pairs of {dict}. Each + |List| item is a list with two items: the key of a {dict} + entry and the value of this entry. The |List| is in arbitrary + order. Also see |keys()| and |values()|. + Example: > + for [key, value] in items(mydict) + echo key .. ': ' .. value + endfor + +< Can also be used as a |method|: > + mydict->items() + +isnan({expr}) *isnan()* + Return |TRUE| if {expr} is a float with value NaN. > + echo isnan(0.0 / 0.0) +< 1 + + Can also be used as a |method|: > + Compute()->isnan() + +jobpid({job}) *jobpid()* + Return the PID (process id) of |job-id| {job}. + +jobresize({job}, {width}, {height}) *jobresize()* + Resize the pseudo terminal window of |job-id| {job} to {width} + columns and {height} rows. + Fails if the job was not started with `"pty":v:true`. + +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: > + :call jobstart(split(&shell) + split(&shellcmdflag) + ['{cmd}']) +< (See |shell-unquoting| for details.) + + Example: > + :call jobstart('nvim -h', {'on_stdout':{j,d,e->append(line('.'),d)}}) +< + Returns |job-id| on success, 0 on invalid arguments (or job + table is full), -1 if {cmd}[0] or 'shell' is not executable. + The returned job-id is a valid |channel-id| representing the + job's stdio streams. Use |chansend()| (or |rpcnotify()| and + |rpcrequest()| if "rpc" was enabled) to send data to stdin and + |chanclose()| to close the streams without stopping the job. + + See |job-control| and |RPC|. + + NOTE: on Windows if {cmd} is a List: + - cmd[0] must be an executable (not a "built-in"). If it is + in $PATH it can be called by name, without an extension: > + :call jobstart(['ping', 'neovim.io']) +< If it is a full or partial path, extension is required: > + :call jobstart(['System32\ping.exe', 'neovim.io']) +< - {cmd} is collapsed to a string of quoted args as expected + by CommandLineToArgvW https://msdn.microsoft.com/bb776391 + unless cmd[0] is some form of "cmd.exe". + + *jobstart-options* + {opts} is a dictionary with these keys: + clear_env: (boolean) `env` defines the job environment + exactly, instead of merging current environment. + cwd: (string, default=|current-directory|) Working + directory of the job. + detach: (boolean) Detach the job process: it will not be + killed when Nvim exits. If the process exits + before Nvim, `on_exit` will be invoked. + env: (dict) Map of environment variable name:value + pairs extending (or replacing if |clear_env|) + the current environment. + height: (number) Height of the `pty` terminal. + |on_exit|: (function) Callback invoked when the job exits. + |on_stdout|: (function) Callback invoked when the job emits + stdout data. + |on_stderr|: (function) Callback invoked when the job emits + stderr data. + overlapped: (boolean) Set FILE_FLAG_OVERLAPPED for the + standard input/output passed to the child process. + Normally you do not need to set this. + (Only available on MS-Windows, On other + platforms, this option is silently ignored.) + pty: (boolean) Connect the job to a new pseudo + terminal, and its streams to the master file + descriptor. Then `on_stderr` is ignored, + `on_stdout` receives all output. + rpc: (boolean) Use |msgpack-rpc| to communicate with + the job over stdio. Then `on_stdout` is ignored, + but `on_stderr` can still be used. + stderr_buffered: (boolean) Collect data until EOF (stream closed) + before invoking `on_stderr`. |channel-buffered| + stdout_buffered: (boolean) Collect data until EOF (stream + closed) before invoking `on_stdout`. |channel-buffered| + stdin: (string) Either "pipe" (default) to connect the + job's stdin to a channel or "null" to disconnect + stdin. + width: (number) Width of the `pty` terminal. + + {opts} is passed as |self| dictionary to the callback; the + caller may set other keys to pass application-specific data. + + Returns: + - |channel-id| on success + - 0 on invalid arguments + - -1 if {cmd}[0] is not executable. + See also |job-control|, |channel|, |msgpack-rpc|. + +jobstop({id}) *jobstop()* + Stop |job-id| {id} by sending SIGTERM to the job process. If + the process does not terminate after a timeout then SIGKILL + will be sent. When the job terminates its |on_exit| handler + (if any) will be invoked. + See |job-control|. + + Returns 1 for valid job id, 0 for invalid id, including jobs have + exited or stopped. + +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. + {timeout} is the maximum waiting time in milliseconds. If + omitted or -1, wait forever. + + Timeout of 0 can be used to check the status of a job: > + let running = jobwait([{job-id}], 0)[0] == -1 +< + During jobwait() callbacks for jobs not in the {jobs} list may + be invoked. The screen will not redraw unless |:redraw| is + invoked by a callback. + + Returns a list of len({jobs}) integers, where each integer is + the status of the corresponding job: + Exit-code, if the job exited + -1 if the timeout was exceeded + -2 if the job was interrupted (by |CTRL-C|) + -3 if the job-id is invalid + +join({list} [, {sep}]) *join()* + Join the items in {list} together into one String. + When {sep} is specified it is put in between the items. If + {sep} is omitted a single space is used. + Note that {sep} is not added at the end. You might want to + add it there too: > + let lines = join(mylist, "\n") .. "\n" +< String items are used as-is. |Lists| and |Dictionaries| are + converted into a string like with |string()|. + The opposite function is |split()|. + + Can also be used as a |method|: > + mylist->join() + +json_decode({expr}) *json_decode()* + Convert {expr} from JSON object. Accepts |readfile()|-style + list as the input, as well as regular string. May output any + Vim value. In the following cases it will output + |msgpack-special-dict|: + 1. Dictionary contains duplicate key. + 2. Dictionary contains empty key. + 3. String contains NUL byte. Two special dictionaries: for + dictionary and for string will be emitted in case string + with NUL byte was a dictionary key. + + Note: function treats its input as UTF-8 always. The JSON + standard allows only a few encodings, of which UTF-8 is + recommended and the only one required to be supported. + Non-UTF-8 characters are an error. + + Can also be used as a |method|: > + ReadObject()->json_decode() + +json_encode({expr}) *json_encode()* + Convert {expr} into a JSON string. Accepts + |msgpack-special-dict| as the input. Will not convert + |Funcref|s, mappings with non-string keys (can be created as + |msgpack-special-dict|), values with self-referencing + containers, strings which contain non-UTF-8 characters, + pseudo-UTF-8 strings which contain codepoints reserved for + surrogate pairs (such strings are not valid UTF-8 strings). + Non-printable characters are converted into "\u1234" escapes + or special escapes like "\t", other are dumped as-is. + |Blob|s are converted to arrays of the individual bytes. + + Can also be used as a |method|: > + GetObject()->json_encode() + +keys({dict}) *keys()* + Return a |List| with all the keys of {dict}. The |List| is in + arbitrary order. Also see |items()| and |values()|. + + Can also be used as a |method|: > + mydict->keys() + +< *len()* *E701* +len({expr}) The result is a Number, which is the length of the argument. + When {expr} is a String or a Number the length in bytes is + used, as with |strlen()|. + When {expr} is a |List| the number of items in the |List| is + returned. + When {expr} is a |Blob| the number of bytes is returned. + When {expr} is a |Dictionary| the number of entries in the + |Dictionary| is returned. + Otherwise an error is given. + + Can also be used as a |method|: > + mylist->len() + +< *libcall()* *E364* *E368* +libcall({libname}, {funcname}, {argument}) + Call function {funcname} in the run-time library {libname} + with single argument {argument}. + This is useful to call functions in a library that you + especially made to be used with Vim. Since only one argument + is possible, calling standard library functions is rather + limited. + The result is the String returned by the function. If the + function returns NULL, this will appear as an empty string "" + to Vim. + If the function returns a number, use libcallnr()! + If {argument} is a number, it is passed to the function as an + int; if {argument} is a string, it is passed as a + null-terminated string. + + libcall() allows you to write your own 'plug-in' extensions to + Vim without having to recompile the program. It is NOT a + means to call system functions! If you try to do so Vim will + very probably crash. + + For Win32, the functions you write must be placed in a DLL + and use the normal C calling convention (NOT Pascal which is + used in Windows System DLLs). The function must take exactly + one parameter, either a character pointer or a long integer, + and must return a character pointer or NULL. The character + pointer returned must point to memory that will remain valid + after the function has returned (e.g. in static data in the + DLL). If it points to allocated memory, that memory will + leak away. Using a static buffer in the function should work, + it's then freed when the DLL is unloaded. + + WARNING: If the function returns a non-valid pointer, Vim may + crash! This also happens if the function returns a number, + because Vim thinks it's a pointer. + For Win32 systems, {libname} should be the filename of the DLL + without the ".DLL" suffix. A full path is only required if + the DLL is not in the usual places. + For Unix: When compiling your own plugins, remember that the + object code must be compiled as position-independent ('PIC'). + Examples: > + :echo libcall("libc.so", "getenv", "HOME") + +< Can also be used as a |method|, the base is passed as the + third argument: > + GetValue()->libcall("libc.so", "getenv") +< + *libcallnr()* +libcallnr({libname}, {funcname}, {argument}) + Just like |libcall()|, but used for a function that returns an + int instead of a string. + Examples: > + :echo libcallnr("/usr/lib/libc.so", "getpid", "") + :call libcallnr("libc.so", "printf", "Hello World!\n") + :call libcallnr("libc.so", "sleep", 10) +< + Can also be used as a |method|, the base is passed as the + third argument: > + GetValue()->libcallnr("libc.so", "printf") +< +line({expr} [, {winid}]) *line()* + The result is a Number, which is the line number of the file + position given with {expr}. The {expr} argument is a string. + The accepted positions are: + . the cursor position + $ the last line in the current buffer + 'x position of mark x (if the mark is not set, 0 is + returned) + w0 first line visible in current window (one if the + display isn't updated, e.g. in silent Ex mode) + w$ last line visible in current window (this is one + less than "w0" if no lines are visible) + v In Visual mode: the start of the Visual area (the + cursor is the end). When not in Visual mode + returns the cursor position. Differs from |'<| in + that it's updated right away. + Note that a mark in another file can be used. The line number + then applies to another buffer. + To get the column number use |col()|. To get both use + |getpos()|. + With the optional {winid} argument the values are obtained for + that window instead of the current window. + Examples: > + line(".") line number of the cursor + line(".", winid) idem, in window "winid" + line("'t") line number of mark t + line("'" .. marker) line number of mark marker +< + To jump to the last known position when opening a file see + |last-position-jump|. + + Can also be used as a |method|: > + GetValue()->line() + +line2byte({lnum}) *line2byte()* + Return the byte count from the start of the buffer for line + {lnum}. This includes the end-of-line character, depending on + the 'fileformat' option for the current buffer. The first + line returns 1. UTF-8 encoding is used, 'fileencoding' is + ignored. This can also be used to get the byte count for the + line just below the last line: > + line2byte(line("$") + 1) +< This is the buffer size plus one. If 'fileencoding' is empty + it is the file size plus one. {lnum} is used like with + |getline()|. When {lnum} is invalid -1 is returned. + Also see |byte2line()|, |go| and |:goto|. + + Can also be used as a |method|: > + GetLnum()->line2byte() + +lispindent({lnum}) *lispindent()* + Get the amount of indent for line {lnum} according the lisp + indenting rules, as with 'lisp'. + The indent is counted in spaces, the value of 'tabstop' is + relevant. {lnum} is used just like in |getline()|. + When {lnum} is invalid, -1 is returned. + + Can also be used as a |method|: > + GetLnum()->lispindent() + +list2str({list} [, {utf8}]) *list2str()* + Convert each number in {list} to a character string can + concatenate them all. Examples: > + list2str([32]) returns " " + list2str([65, 66, 67]) returns "ABC" +< The same can be done (slowly) with: > + join(map(list, {nr, val -> nr2char(val)}), '') +< |str2list()| does the opposite. + + UTF-8 encoding is always used, {utf8} option has no effect, + and exists only for backwards-compatibility. + With UTF-8 composing characters work as expected: > + list2str([97, 769]) returns "á" +< + Can also be used as a |method|: > + GetList()->list2str() + +localtime() *localtime()* + Return the current time, measured as seconds since 1st Jan + 1970. See also |strftime()|, |strptime()| and |getftime()|. + + +log({expr}) *log()* + Return the natural logarithm (base e) of {expr} as a |Float|. + {expr} must evaluate to a |Float| or a |Number| in the range + (0, inf]. + Examples: > + :echo log(10) +< 2.302585 > + :echo log(exp(5)) +< 5.0 + + Can also be used as a |method|: > + Compute()->log() + +log10({expr}) *log10()* + Return the logarithm of Float {expr} to base 10 as a |Float|. + {expr} must evaluate to a |Float| or a |Number|. + Examples: > + :echo log10(1000) +< 3.0 > + :echo log10(0.01) +< -2.0 + + Can also be used as a |method|: > + Compute()->log10() + +luaeval({expr} [, {expr}]) + Evaluate Lua expression {expr} and return its result converted + to Vim data structures. See |lua-eval| for more details. + + Can also be used as a |method|: > + GetExpr()->luaeval() + +map({expr1}, {expr2}) *map()* + {expr1} must be a |List|, |Blob| or |Dictionary|. + Replace each item in {expr1} with the result of evaluating + {expr2}. For a |Blob| each byte is replaced. + + {expr2} must be a |string| or |Funcref|. + + If {expr2} is a |string|, inside {expr2} |v:val| has the value + of the current item. For a |Dictionary| |v:key| has the key + of the current item and for a |List| |v:key| has the index of + the current item. For a |Blob| |v:key| has the index of the + current byte. + Example: > + :call map(mylist, '"> " .. v:val .. " <"') +< This puts "> " before and " <" after each item in "mylist". + + Note that {expr2} is the result of an expression and is then + used as an expression again. Often it is good to use a + |literal-string| to avoid having to double backslashes. You + still have to double ' quotes + + If {expr2} is a |Funcref| it is called with two arguments: + 1. The key or the index of the current item. + 2. the value of the current item. + The function must return the new value of the item. Example + that changes each value by "key-value": > + func KeyValue(key, val) + return a:key .. '-' .. a:val + endfunc + call map(myDict, function('KeyValue')) +< It is shorter when using a |lambda|: > + call map(myDict, {key, val -> key .. '-' .. val}) +< If you do not use "val" you can leave it out: > + call map(myDict, {key -> 'item: ' .. key}) +< If you do not use "key" you can use a short name: > + call map(myDict, {_, val -> 'item: ' .. val}) +< + The operation is done in-place. If you want a |List| or + |Dictionary| to remain unmodified make a copy first: > + :let tlist = map(copy(mylist), ' v:val .. "\t"') + +< Returns {expr1}, the |List|, |Blob| or |Dictionary| that was + filtered. When an error is encountered while evaluating + {expr2} no further items in {expr1} are processed. When + {expr2} is a Funcref errors inside a function are ignored, + unless it was defined with the "abort" flag. + + Can also be used as a |method|: > + mylist->map(expr2) + +maparg({name} [, {mode} [, {abbr} [, {dict}]]]) *maparg()* + When {dict} is omitted or zero: Return the rhs of mapping + {name} in mode {mode}. The returned String has special + characters translated like in the output of the ":map" command + listing. + + When there is no mapping for {name}, an empty String is + returned. When the mapping for {name} is empty, then "<Nop>" + is returned. + + The {name} can have special key names, like in the ":map" + command. + + {mode} can be one of these strings: + "n" Normal + "v" Visual (including Select) + "o" Operator-pending + "i" Insert + "c" Cmd-line + "s" Select + "x" Visual + "l" langmap |language-mapping| + "t" Terminal + "" Normal, Visual and Operator-pending + When {mode} is omitted, the modes for "" are used. + + When {abbr} is there and it is |TRUE| use abbreviations + instead of mappings. + + When {dict} is there and it is |TRUE| return a dictionary + containing all the information of the mapping with the + following items: + "lhs" The {lhs} of the mapping. + "rhs" The {rhs} of the mapping as typed. + "silent" 1 for a |:map-silent| mapping, else 0. + "noremap" 1 if the {rhs} of the mapping is not remappable. + "script" 1 if mapping was defined with <script>. + "expr" 1 for an expression mapping (|:map-<expr>|). + "buffer" 1 for a buffer local mapping (|:map-local|). + "mode" Modes for which the mapping is defined. In + addition to the modes mentioned above, these + characters will be used: + " " Normal, Visual and Operator-pending + "!" Insert and Commandline mode + (|mapmode-ic|) + "sid" The script local ID, used for <sid> mappings + (|<SID>|). + "lnum" The line number in "sid", zero if unknown. + "nowait" Do not wait for other, longer mappings. + (|:map-<nowait>|). + + The mappings local to the current buffer are checked first, + then the global mappings. + This function can be used to map a key even when it's already + mapped, and have it do the original mapping too. Sketch: > + exe 'nnoremap <Tab> ==' .. maparg('<Tab>', 'n') + +< Can also be used as a |method|: > + GetKey()->maparg('n') + +mapcheck({name} [, {mode} [, {abbr}]]) *mapcheck()* + Check if there is a mapping that matches with {name} in mode + {mode}. See |maparg()| for {mode} and special names in + {name}. + When {abbr} is there and it is non-zero use abbreviations + instead of mappings. + A match happens with a mapping that starts with {name} and + with a mapping which is equal to the start of {name}. + + matches mapping "a" "ab" "abc" ~ + mapcheck("a") yes yes yes + mapcheck("abc") yes yes yes + mapcheck("ax") yes no no + mapcheck("b") no no no + + The difference with maparg() is that mapcheck() finds a + mapping that matches with {name}, while maparg() only finds a + mapping for {name} exactly. + When there is no mapping that starts with {name}, an empty + String is returned. If there is one, the RHS of that mapping + is returned. If there are several mappings that start with + {name}, the RHS of one of them is returned. This will be + "<Nop>" if the RHS is empty. + The mappings local to the current buffer are checked first, + then the global mappings. + This function can be used to check if a mapping can be added + without being ambiguous. Example: > + :if mapcheck("_vv") == "" + : map _vv :set guifont=7x13<CR> + :endif +< This avoids adding the "_vv" mapping when there already is a + mapping for "_v" or for "_vvv". + + Can also be used as a |method|: > + GetKey()->mapcheck('n') + +match({expr}, {pat} [, {start} [, {count}]]) *match()* + When {expr} is a |List| then this returns the index of the + first item where {pat} matches. Each item is used as a + String, |Lists| and |Dictionaries| are used as echoed. + + Otherwise, {expr} is used as a String. The result is a + Number, which gives the index (byte offset) in {expr} where + {pat} matches. + + A match at the first character or |List| item returns zero. + If there is no match -1 is returned. + + For getting submatches see |matchlist()|. + Example: > + :echo match("testing", "ing") " results in 4 + :echo match([1, 'x'], '\a') " results in 1 +< See |string-match| for how {pat} is used. + *strpbrk()* + Vim doesn't have a strpbrk() function. But you can do: > + :let sepidx = match(line, '[.,;: \t]') +< *strcasestr()* + Vim doesn't have a strcasestr() function. But you can add + "\c" to the pattern to ignore case: > + :let idx = match(haystack, '\cneedle') +< + If {start} is given, the search starts from byte index + {start} in a String or item {start} in a |List|. + The result, however, is still the index counted from the + first character/item. Example: > + :echo match("testing", "ing", 2) +< result is again "4". > + :echo match("testing", "ing", 4) +< result is again "4". > + :echo match("testing", "t", 2) +< result is "3". + For a String, if {start} > 0 then it is like the string starts + {start} bytes later, thus "^" will match at {start}. Except + when {count} is given, then it's like matches before the + {start} byte are ignored (this is a bit complicated to keep it + backwards compatible). + For a String, if {start} < 0, it will be set to 0. For a list + the index is counted from the end. + If {start} is out of range ({start} > strlen({expr}) for a + String or {start} > len({expr}) for a |List|) -1 is returned. + + When {count} is given use the {count}'th match. When a match + is found in a String the search for the next one starts one + character further. Thus this example results in 1: > + echo match("testing", "..", 0, 2) +< In a |List| the search continues in the next item. + Note that when {count} is added the way {start} works changes, + see above. + + See |pattern| for the patterns that are accepted. + The 'ignorecase' option is used to set the ignore-caseness of + the pattern. 'smartcase' is NOT used. The matching is always + done like 'magic' is set and 'cpoptions' is empty. + Note that a match at the start is preferred, thus when the + pattern is using "*" (any number of matches) it tends to find + zero matches at the start instead of a number of matches + further down in the text. + + Can also be used as a |method|: > + GetText()->match('word') + GetList()->match('word') +< + *matchadd()* *E798* *E799* *E801* *E957* +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 + match using |matchdelete()|. The ID is bound to the window. + Matching is case sensitive and magic, unless case sensitivity + or magicness are explicitly overridden in {pattern}. The + 'magic', 'smartcase' and 'ignorecase' options are not used. + The "Conceal" value is special, it causes the match to be + concealed. + + The optional {priority} argument assigns a priority to the + match. A match with a high priority will have its + highlighting overrule that of a match with a lower priority. + A priority is specified as an integer (negative numbers are no + exception). If the {priority} argument is not specified, the + default priority is 10. The priority of 'hlsearch' is zero, + hence all matches with a priority greater than zero will + overrule it. Syntax highlighting (see 'syntax') is a separate + mechanism, and regardless of the chosen priority a match will + always overrule syntax highlighting. + + The optional {id} argument allows the request for a specific + match ID. If a specified ID is already taken, an error + message will appear and the match will not be added. An ID + is specified as a positive integer (zero excluded). IDs 1, 2 + and 3 are reserved for |:match|, |:2match| and |:3match|, + respectively. If the {id} argument is not specified or -1, + |matchadd()| automatically chooses a free ID. + + The optional {dict} argument allows for further custom + values. Currently this is used to specify a match specific + conceal character that will be shown for |hl-Conceal| + highlighted matches. The dict can have the following members: + + conceal Special character to show instead of the + match (only for |hl-Conceal| highlighed + matches, see |:syn-cchar|) + window Instead of the current window use the + window with this number or window ID. + + The number of matches is not limited, as it is the case with + the |:match| commands. + + Example: > + :highlight MyGroup ctermbg=green guibg=green + :let m = matchadd("MyGroup", "TODO") +< Deletion of the pattern: > + :call matchdelete(m) + +< A list of matches defined by |matchadd()| and |:match| are + available from |getmatches()|. All matches can be deleted in + one operation by |clearmatches()|. + + Can also be used as a |method|: > + GetGroup()->matchadd('TODO') +< + *matchaddpos()* +matchaddpos({group}, {pos} [, {priority} [, {id} [, {dict}]]]) + Same as |matchadd()|, but requires a list of positions {pos} + instead of a pattern. This command is faster than |matchadd()| + because it does not require to handle regular expressions and + sets buffer line boundaries to redraw screen. It is supposed + to be used when fast match additions and deletions are + required, for example to highlight matching parentheses. + *E5030* *E5031* + {pos} is a list of positions. Each position can be one of + these: + - A number. This whole line will be highlighted. The first + line has number 1. + - A list with one number, e.g., [23]. The whole line with this + number will be highlighted. + - A list with two numbers, e.g., [23, 11]. The first number is + the line number, the second one is the column number (first + column is 1, the value must correspond to the byte index as + |col()| would return). The character at this position will + be highlighted. + - A list with three numbers, e.g., [23, 11, 3]. As above, but + the third number gives the length of the highlight in bytes. + + Entries with zero and negative line numbers are silently + ignored, as well as entries with negative column numbers and + lengths. + + The maximum number of positions in {pos} is 8. + + Example: > + :highlight MyGroup ctermbg=green guibg=green + :let m = matchaddpos("MyGroup", [[23, 24], 34]) +< Deletion of the pattern: > + :call matchdelete(m) + +< Matches added by |matchaddpos()| are returned by + |getmatches()|. + + Can also be used as a |method|: > + GetGroup()->matchaddpos([23, 11]) + +matcharg({nr}) *matcharg()* + Selects the {nr} match item, as set with a |:match|, + |:2match| or |:3match| command. + Return a |List| with two elements: + The name of the highlight group used + The pattern used. + When {nr} is not 1, 2 or 3 returns an empty |List|. + When there is no match item set returns ['', '']. + This is useful to save and restore a |:match|. + Highlighting matches using the |:match| commands are limited + to three matches. |matchadd()| does not have this limitation. + + Can also be used as a |method|: > + GetMatch()->matcharg() + +matchdelete({id} [, {win}]) *matchdelete()* *E802* *E803* + Deletes a match with ID {id} previously defined by |matchadd()| + or one of the |:match| commands. Returns 0 if successful, + otherwise -1. See example for |matchadd()|. All matches can + be deleted in one operation by |clearmatches()|. + If {win} is specified, use the window with this number or + window ID instead of the current window. + + Can also be used as a |method|: > + GetMatch()->matchdelete() + +matchend({expr}, {pat} [, {start} [, {count}]]) *matchend()* + Same as |match()|, but return the index of first character + after the match. Example: > + :echo matchend("testing", "ing") +< results in "7". + *strspn()* *strcspn()* + Vim doesn't have a strspn() or strcspn() function, but you can + do it with matchend(): > + :let span = matchend(line, '[a-zA-Z]') + :let span = matchend(line, '[^a-zA-Z]') +< Except that -1 is returned when there are no matches. + + The {start}, if given, has the same meaning as for |match()|. > + :echo matchend("testing", "ing", 2) +< results in "7". > + :echo matchend("testing", "ing", 5) +< result is "-1". + When {expr} is a |List| the result is equal to |match()|. + + 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 + return. Following items are submatches, like "\1", "\2", etc. + in |:substitute|. When an optional submatch didn't match an + empty string is used. Example: > + echo matchlist('acd', '\(a\)\?\(b\)\?\(c\)\?\(.*\)') +< Results in: ['acd', 'a', '', 'c', 'd', '', '', '', '', ''] + When there is no match an empty list is returned. + + You can pass in a List, but that is not very useful. + + Can also be used as a |method|: > + GetText()->matchlist('word') + +matchstr({expr}, {pat} [, {start} [, {count}]]) *matchstr()* + Same as |match()|, but return the matched string. Example: > + :echo matchstr("testing", "ing") +< results in "ing". + When there is no match "" is returned. + The {start}, if given, has the same meaning as for |match()|. > + :echo matchstr("testing", "ing", 2) +< results in "ing". > + :echo matchstr("testing", "ing", 5) +< result is "". + When {expr} is a |List| then the matching item is returned. + The type isn't changed, it's not necessarily a String. + + Can also be used as a |method|: > + GetText()->matchstr('word') + +matchstrpos({expr}, {pat} [, {start} [, {count}]]) *matchstrpos()* + Same as |matchstr()|, but return the matched string, the start + position and the end position of the match. Example: > + :echo matchstrpos("testing", "ing") +< results in ["ing", 4, 7]. + When there is no match ["", -1, -1] is returned. + The {start}, if given, has the same meaning as for |match()|. > + :echo matchstrpos("testing", "ing", 2) +< results in ["ing", 4, 7]. > + :echo matchstrpos("testing", "ing", 5) +< result is ["", -1, -1]. + When {expr} is a |List| then the matching item, the index + of first item where {pat} matches, the start position and the + end position of the match are returned. > + :echo matchstrpos([1, '__x'], '\a') +< result is ["x", 1, 2, 3]. + The type isn't changed, it's not necessarily a String. + + Can also be used as a |method|: > + GetText()->matchstrpos('word') +< + *max()* +max({expr}) Return the maximum value of all items in {expr}. Example: > + echo max([apples, pears, oranges]) + +< {expr} can be a |List| or a |Dictionary|. For a Dictionary, + it returns the maximum of all values in the Dictionary. + If {expr} is neither a List nor a Dictionary, or one of the + items in {expr} cannot be used as a Number this results in + an error. An empty |List| or |Dictionary| results in zero. + + Can also be used as a |method|: > + mylist->max() + +menu_get({path} [, {modes}]) *menu_get()* + Returns a |List| of |Dictionaries| describing |menus| (defined + by |:menu|, |:amenu|, …), including |hidden-menus|. + + {path} matches a menu by name, or all menus if {path} is an + empty string. Example: > + :echo menu_get('File','') + :echo menu_get('') +< + {modes} is a string of zero or more modes (see |maparg()| or + |creating-menus| for the list of modes). "a" means "all". + + Example: > + nnoremenu &Test.Test inormal + inoremenu Test.Test insert + vnoremenu Test.Test x + echo menu_get("") + +< returns something like this: > + + [ { + "hidden": 0, + "name": "Test", + "priority": 500, + "shortcut": 84, + "submenus": [ { + "hidden": 0, + "mappings": { + i": { + "enabled": 1, + "noremap": 1, + "rhs": "insert", + "sid": 1, + "silent": 0 + }, + n": { ... }, + s": { ... }, + v": { ... } + }, + "name": "Test", + "priority": 500, + "shortcut": 0 + } ] + } ] +< + + *min()* +min({expr}) Return the minimum value of all items in {expr}. Example: > + echo min([apples, pears, oranges]) + +< {expr} can be a |List| or a |Dictionary|. For a Dictionary, + it returns the minimum of all values in the Dictionary. + If {expr} is neither a List nor a Dictionary, or one of the + items in {expr} cannot be used as a Number this results in + an error. An empty |List| or |Dictionary| results in zero. + + Can also be used as a |method|: > + mylist->min() + +< *mkdir()* *E739* +mkdir({name} [, {path} [, {prot}]]) + Create directory {name}. + + If {path} is "p" then intermediate directories are created as + necessary. Otherwise it must be "". + + If {prot} is given it is used to set the protection bits of + the new directory. The default is 0o755 (rwxr-xr-x: r/w for + the user, readable for others). Use 0o700 to make it + unreadable for others. + + {prot} is applied for all parts of {name}. Thus if you create + /tmp/foo/bar then /tmp/foo will be created with 0o700. Example: > + :call mkdir($HOME .. "/tmp/foo/bar", "p", 0o700) + +< This function is not available in the |sandbox|. + + If you try to create an existing directory with {path} set to + "p" mkdir() will silently exit. + + The function result is a Number, which is TRUE if the call was + successful or FALSE if the directory creation failed or partly + failed. + + Can also be used as a |method|: > + GetName()->mkdir() +< + *mode()* +mode([expr]) Return a string that indicates the current mode. + If [expr] is supplied and it evaluates to a non-zero Number or + a non-empty String (|non-zero-arg|), then the full mode is + returned, otherwise only the first letter is returned. + + n Normal + no Operator-pending + nov Operator-pending (forced charwise |o_v|) + noV Operator-pending (forced linewise |o_V|) + noCTRL-V Operator-pending (forced blockwise |o_CTRL-V|) + CTRL-V is one character + niI Normal using |i_CTRL-O| in |Insert-mode| + niR Normal using |i_CTRL-O| in |Replace-mode| + niV Normal using |i_CTRL-O| in |Virtual-Replace-mode| + nt Normal in |terminal-emulator| (insert goes to + Terminal mode) + v Visual by character + vs Visual by character using |v_CTRL-O| in Select mode + V Visual by line + Vs Visual by line using |v_CTRL-O| in Select mode + CTRL-V Visual blockwise + CTRL-Vs Visual blockwise using |v_CTRL-O| in Select mode + s Select by character + S Select by line + CTRL-S Select blockwise + i Insert + ic Insert mode completion |compl-generic| + ix Insert mode |i_CTRL-X| completion + R Replace |R| + Rc Replace mode completion |compl-generic| + Rx Replace mode |i_CTRL-X| completion + Rv Virtual Replace |gR| + Rvc Virtual Replace mode completion |compl-generic| + Rvx Virtual Replace mode |i_CTRL-X| completion + c Command-line editing + cv Vim Ex mode |gQ| + r Hit-enter prompt + rm The -- more -- prompt + r? A |:confirm| query of some sort + ! Shell or external command is executing + t Terminal mode: keys go to the job + + This is useful in the 'statusline' option or when used + with |remote_expr()| In most other places it always returns + "c" or "n". + Note that in the future more modes and more specific modes may + be added. It's better not to compare the whole string but only + the leading character(s). + Also see |visualmode()|. + + Can also be used as a |method|: > + DoFull()->mode() + +msgpackdump({list} [, {type}]) *msgpackdump()* + Convert a list of VimL objects to msgpack. Returned value is a + |readfile()|-style list. When {type} contains "B", a |Blob| is + returned instead. Example: > + call writefile(msgpackdump([{}]), 'fname.mpack', 'b') +< or, using a |Blob|: > + call writefile(msgpackdump([{}], 'B'), 'fname.mpack') +< + This will write the single 0x80 byte to a `fname.mpack` file + (dictionary with zero items is represented by 0x80 byte in + messagepack). + + Limitations: *E5004* *E5005* + 1. |Funcref|s cannot be dumped. + 2. Containers that reference themselves cannot be dumped. + 3. Dictionary keys are always dumped as STR strings. + 4. Other strings and |Blob|s are always dumped as BIN strings. + 5. Points 3. and 4. do not apply to |msgpack-special-dict|s. + +msgpackparse({data}) *msgpackparse()* + Convert a |readfile()|-style list or a |Blob| to a list of + VimL objects. + Example: > + let fname = expand('~/.config/nvim/shada/main.shada') + let mpack = readfile(fname, 'b') + let shada_objects = msgpackparse(mpack) +< This will read ~/.config/nvim/shada/main.shada file to + `shada_objects` list. + + Limitations: + 1. Mapping ordering is not preserved unless messagepack + mapping is dumped using generic mapping + (|msgpack-special-map|). + 2. Since the parser aims to preserve all data untouched + (except for 1.) some strings are parsed to + |msgpack-special-dict| format which is not convenient to + use. + *msgpack-special-dict* + Some messagepack strings may be parsed to special + dictionaries. Special dictionaries are dictionaries which + + 1. Contain exactly two keys: `_TYPE` and `_VAL`. + 2. `_TYPE` key is one of the types found in |v:msgpack_types| + variable. + 3. Value for `_VAL` has the following format (Key column + contains name of the key from |v:msgpack_types|): + + Key Value ~ + nil Zero, ignored when dumping. Not returned by + |msgpackparse()| since |v:null| was introduced. + boolean One or zero. When dumping it is only checked that + value is a |Number|. Not returned by |msgpackparse()| + since |v:true| and |v:false| were introduced. + integer |List| with four numbers: sign (-1 or 1), highest two + bits, number with bits from 62nd to 31st, lowest 31 + bits. I.e. to get actual number one will need to use + code like > + _VAL[0] * ((_VAL[1] << 62) + & (_VAL[2] << 31) + & _VAL[3]) +< Special dictionary with this type will appear in + |msgpackparse()| output under one of the following + circumstances: + 1. |Number| is 32-bit and value is either above + INT32_MAX or below INT32_MIN. + 2. |Number| is 64-bit and value is above INT64_MAX. It + cannot possibly be below INT64_MIN because msgpack + C parser does not support such values. + float |Float|. This value cannot possibly appear in + |msgpackparse()| output. + string |readfile()|-style list of strings. This value will + appear in |msgpackparse()| output if string contains + zero byte or if string is a mapping key and mapping is + being represented as special dictionary for other + reasons. + binary |String|, or |Blob| if binary string contains zero + byte. This value cannot appear in |msgpackparse()| + output since blobs were introduced. + array |List|. This value cannot appear in |msgpackparse()| + output. + *msgpack-special-map* + map |List| of |List|s with two items (key and value) each. + This value will appear in |msgpackparse()| output if + parsed mapping contains one of the following keys: + 1. Any key that is not a string (including keys which + are binary strings). + 2. String with NUL byte inside. + 3. Duplicate key. + 4. Empty key. + ext |List| with two values: first is a signed integer + representing extension type. Second is + |readfile()|-style list of strings. + +nextnonblank({lnum}) *nextnonblank()* + Return the line number of the first line at or below {lnum} + that is not blank. Example: > + if getline(nextnonblank(1)) =~ "Java" +< When {lnum} is invalid or there is no non-blank line at or + below it, zero is returned. + {lnum} is used like with |getline()|. + See also |prevnonblank()|. + + Can also be used as a |method|: > + GetLnum()->nextnonblank() + +nr2char({expr} [, {utf8}]) *nr2char()* + Return a string with a single character, which has the number + value {expr}. Examples: > + nr2char(64) returns "@" + nr2char(32) returns " " +< Example for "utf-8": > + nr2char(300) returns I with bow character +< UTF-8 encoding is always used, {utf8} option has no effect, + and exists only for backwards-compatibility. + Note that a NUL character in the file is specified with + nr2char(10), because NULs are represented with newline + characters. nr2char(0) is a real NUL and terminates the + string, thus results in an empty string. + + Can also be used as a |method|: > + GetNumber()->nr2char() + +nvim_...({...}) *E5555* *nvim_...()* *eval-api* + Call nvim |api| functions. The type checking of arguments will + be stricter than for most other builtins. For instance, + if Integer is expected, a |Number| must be passed in, a + |String| will not be autoconverted. + Buffer numbers, as returned by |bufnr()| could be used as + first argument to nvim_buf_... functions. All functions + expecting an object (buffer, window or tabpage) can + also take the numerical value 0 to indicate the current + (focused) object. + +or({expr}, {expr}) *or()* + Bitwise OR on the two arguments. The arguments are converted + to a number. A List, Dict or Float argument causes an error. + Example: > + :let bits = or(bits, 0x80) +< Can also be used as a |method|: > + :let bits = bits->or(0x80) + +pathshorten({expr} [, {len}]) *pathshorten()* + Shorten directory names in the path {path} and return the + result. The tail, the file name, is kept as-is. The other + components in the path are reduced to {len} letters in length. + If {len} is omitted or smaller than 1 then 1 is used (single + letters). Leading '~' and '.' characters are kept. Examples: > + :echo pathshorten('~/.config/nvim/autoload/file1.vim') +< ~/.c/n/a/file1.vim ~ +> + :echo pathshorten('~/.config/nvim/autoload/file2.vim', 2) +< ~/.co/nv/au/file2.vim ~ + It doesn't matter if the path exists or not. + + Can also be used as a |method|: > + GetDirectories()->pathshorten() + +perleval({expr}) *perleval()* + Evaluate |perl| expression {expr} and return its result + converted to Vim data structures. + Numbers and strings are returned as they are (strings are + copied though). + Lists are represented as Vim |List| type. + Dictionaries are represented as Vim |Dictionary| type, + non-string keys result in error. + + Note: If you want an array or hash, {expr} must return a + reference to it. + Example: > + :echo perleval('[1 .. 4]') +< [1, 2, 3, 4] + + Can also be used as a |method|: > + GetExpr()->perleval() + +pow({x}, {y}) *pow()* + Return the power of {x} to the exponent {y} as a |Float|. + {x} and {y} must evaluate to a |Float| or a |Number|. + Examples: > + :echo pow(3, 3) +< 27.0 > + :echo pow(2, 16) +< 65536.0 > + :echo pow(32, 0.20) +< 2.0 + + Can also be used as a |method|: > + Compute()->pow(3) + +prevnonblank({lnum}) *prevnonblank()* + Return the line number of the first line at or above {lnum} + that is not blank. Example: > + let ind = indent(prevnonblank(v:lnum - 1)) +< When {lnum} is invalid or there is no non-blank line at or + above it, zero is returned. + {lnum} is used like with |getline()|. + Also see |nextnonblank()|. + + Can also be used as a |method|: > + GetLnum()->prevnonblank() + +printf({fmt}, {expr1} ...) *printf()* + Return a String with {fmt}, where "%" items are replaced by + the formatted form of their respective arguments. Example: > + printf("%4d: E%d %.30s", lnum, errno, msg) +< May result in: + " 99: E42 asdfasdfasdfasdfasdfasdfasdfas" ~ + + When used as a |method| the base is passed as the second + argument: > + Compute()->printf("result: %d") + +< Often used items are: + %s string + %6S string right-aligned in 6 display cells + %6s string right-aligned in 6 bytes + %.9s string truncated to 9 bytes + %c single byte + %d decimal number + %5d decimal number padded with spaces to 5 characters + %b binary number + %08b binary number padded with zeros to at least 8 characters + %B binary number using upper case letters + %x hex number + %04x hex number padded with zeros to at least 4 characters + %X hex number using upper case letters + %o octal number + %f floating point number as 12.23, inf, -inf or nan + %F floating point number as 12.23, INF, -INF or NAN + %e floating point number as 1.23e3, inf, -inf or nan + %E floating point number as 1.23E3, INF, -INF or NAN + %g floating point number, as %f or %e depending on value + %G floating point number, as %F or %E depending on value + %% the % character itself + %p representation of the pointer to the container + + Conversion specifications start with '%' and end with the + conversion type. All other characters are copied unchanged to + the result. + + The "%" starts a conversion specification. The following + arguments appear in sequence: + + % [flags] [field-width] [.precision] type + + flags + Zero or more of the following flags: + + # The value should be converted to an "alternate + form". For c, d, and s conversions, this option + has no effect. For o conversions, the precision + of the number is increased to force the first + character of the output string to a zero (except + if a zero value is printed with an explicit + precision of zero). + For x and X conversions, a non-zero result has + the string "0x" (or "0X" for X conversions) + prepended to it. + + 0 (zero) Zero padding. For all conversions the converted + value is padded on the left with zeros rather + than blanks. If a precision is given with a + numeric conversion (d, o, x, and X), the 0 flag + is ignored. + + - A negative field width flag; the converted value + is to be left adjusted on the field boundary. + The converted value is padded on the right with + blanks, rather than on the left with blanks or + zeros. A - overrides a 0 if both are given. + + ' ' (space) A blank should be left before a positive + number produced by a signed conversion (d). + + + A sign must always be placed before a number + produced by a signed conversion. A + overrides + a space if both are used. + + field-width + An optional decimal digit string specifying a minimum + field width. If the converted value has fewer bytes + than the field width, it will be padded with spaces on + the left (or right, if the left-adjustment flag has + been given) to fill out the field width. For the S + conversion the count is in cells. + + .precision + An optional precision, in the form of a period '.' + followed by an optional digit string. If the digit + string is omitted, the precision is taken as zero. + This gives the minimum number of digits to appear for + d, o, x, and X conversions, the maximum number of + bytes to be printed from a string for s conversions, + or the maximum number of cells to be printed from a + string for S conversions. + For floating point it is the number of digits after + the decimal point. + + type + A character that specifies the type of conversion to + be applied, see below. + + A field width or precision, or both, may be indicated by an + asterisk '*' instead of a digit string. In this case, a + Number argument supplies the field width or precision. A + negative field width is treated as a left adjustment flag + followed by a positive field width; a negative precision is + treated as though it were missing. Example: > + :echo printf("%d: %.*s", nr, width, line) +< This limits the length of the text used from "line" to + "width" bytes. + + The conversion specifiers and their meanings are: + + *printf-d* *printf-b* *printf-B* *printf-o* *printf-x* *printf-X* + dbBoxX The Number argument is converted to signed decimal (d), + unsigned binary (b and B), unsigned octal (o), or + unsigned hexadecimal (x and X) notation. The letters + "abcdef" are used for x conversions; the letters + "ABCDEF" are used for X conversions. The precision, if + any, gives the minimum number of digits that must + appear; if the converted value requires fewer digits, it + is padded on the left with zeros. In no case does a + non-existent or small field width cause truncation of a + numeric field; if the result of a conversion is wider + than the field width, the field is expanded to contain + the conversion result. + The 'h' modifier indicates the argument is 16 bits. + The 'l' modifier indicates the argument is 32 bits. + The 'L' modifier indicates the argument is 64 bits. + Generally, these modifiers are not useful. They are + ignored when type is known from the argument. + + i alias for d + D alias for ld + U alias for lu + O alias for lo + + *printf-c* + c The Number argument is converted to a byte, and the + resulting character is written. + + *printf-s* + s The text of the String argument is used. If a + precision is specified, no more bytes than the number + specified are used. + If the argument is not a String type, it is + automatically converted to text with the same format + as ":echo". + *printf-S* + S The text of the String argument is used. If a + precision is specified, no more display cells than the + number specified are used. + + *printf-f* *E807* + f F The Float argument is converted into a string of the + form 123.456. The precision specifies the number of + digits after the decimal point. When the precision is + zero the decimal point is omitted. When the precision + is not specified 6 is used. A really big number + (out of range or dividing by zero) results in "inf" + or "-inf" with %f (INF or -INF with %F). + "0.0 / 0.0" results in "nan" with %f (NAN with %F). + Example: > + echo printf("%.2f", 12.115) +< 12.12 + Note that roundoff depends on the system libraries. + Use |round()| when in doubt. + + *printf-e* *printf-E* + e E The Float argument is converted into a string of the + form 1.234e+03 or 1.234E+03 when using 'E'. The + precision specifies the number of digits after the + decimal point, like with 'f'. + + *printf-g* *printf-G* + g G The Float argument is converted like with 'f' if the + value is between 0.001 (inclusive) and 10000000.0 + (exclusive). Otherwise 'e' is used for 'g' and 'E' + for 'G'. When no precision is specified superfluous + zeroes and '+' signs are removed, except for the zero + immediately after the decimal point. Thus 10000000.0 + results in 1.0e7. + + *printf-%* + % A '%' is written. No argument is converted. The + complete conversion specification is "%%". + + When a Number argument is expected a String argument is also + accepted and automatically converted. + When a Float or String argument is expected a Number argument + is also accepted and automatically converted. + Any other argument type results in an error message. + + *E766* *E767* + The number of {exprN} arguments must exactly match the number + of "%" items. If there are not sufficient or too many + arguments an error is given. Up to 18 arguments can be used. + +prompt_getprompt({buf}) *prompt_getprompt()* + Returns the effective prompt text for buffer {buf}. {buf} can + be a buffer name or number. See |prompt-buffer|. + + If the buffer doesn't exist or isn't a prompt buffer, an empty + string is returned. + + Can also be used as a |method|: > + GetBuffer()->prompt_getprompt() + +prompt_setcallback({buf}, {expr}) *prompt_setcallback()* + Set prompt callback for buffer {buf} to {expr}. When {expr} + is an empty string the callback is removed. This has only + effect if {buf} has 'buftype' set to "prompt". + + The callback is invoked when pressing Enter. The current + buffer will always be the prompt buffer. A new line for a + prompt is added before invoking the callback, thus the prompt + for which the callback was invoked will be in the last but one + line. + If the callback wants to add text to the buffer, it must + insert it above the last line, since that is where the current + prompt is. This can also be done asynchronously. + The callback is invoked with one argument, which is the text + that was entered at the prompt. This can be an empty string + if the user only typed Enter. + Example: > + call prompt_setcallback(bufnr(''), function('s:TextEntered')) + func s:TextEntered(text) + if a:text == 'exit' || a:text == 'quit' + stopinsert + close + else + call append(line('$') - 1, 'Entered: "' .. a:text .. '"') + " Reset 'modified' to allow the buffer to be closed. + set nomodified + endif + endfunc + +< Can also be used as a |method|: > + GetBuffer()->prompt_setcallback(callback) + +prompt_setinterrupt({buf}, {expr}) *prompt_setinterrupt()* + Set a callback for buffer {buf} to {expr}. When {expr} is an + empty string the callback is removed. This has only effect if + {buf} has 'buftype' set to "prompt". + + This callback will be invoked when pressing CTRL-C in Insert + mode. Without setting a callback Vim will exit Insert mode, + as in any buffer. + + Can also be used as a |method|: > + GetBuffer()->prompt_setinterrupt(callback) + +prompt_setprompt({buf}, {text}) *prompt_setprompt()* + Set prompt for buffer {buf} to {text}. You most likely want + {text} to end in a space. + The result is only visible if {buf} has 'buftype' set to + "prompt". Example: > + call prompt_setprompt(bufnr(''), 'command: ') +< + Can also be used as a |method|: > + GetBuffer()->prompt_setprompt('command: ') + +pum_getpos() *pum_getpos()* + If the popup menu (see |ins-completion-menu|) is not visible, + returns an empty |Dictionary|, otherwise, returns a + |Dictionary| with the following keys: + height nr of items visible + width screen cells + row top screen row (0 first row) + col leftmost screen column (0 first col) + size total nr of items + scrollbar |TRUE| if scrollbar is visible + + The values are the same as in |v:event| during |CompleteChanged|. + +pumvisible() *pumvisible()* + Returns non-zero when the popup menu is visible, zero + otherwise. See |ins-completion-menu|. + This can be used to avoid some things that would remove the + popup menu. + +py3eval({expr}) *py3eval()* + Evaluate Python expression {expr} and return its result + converted to Vim data structures. + Numbers and strings are returned as they are (strings are + copied though, Unicode strings are additionally converted to + UTF-8). + Lists are represented as Vim |List| type. + Dictionaries are represented as Vim |Dictionary| type with + keys converted to strings. + + Can also be used as a |method|: > + GetExpr()->py3eval() +< + *E858* *E859* +pyeval({expr}) *pyeval()* + Evaluate Python expression {expr} and return its result + converted to Vim data structures. + Numbers and strings are returned as they are (strings are + copied though). + Lists are represented as Vim |List| type. + Dictionaries are represented as Vim |Dictionary| type, + non-string keys result in error. + + Can also be used as a |method|: > + GetExpr()->pyeval() + +pyxeval({expr}) *pyxeval()* + Evaluate Python expression {expr} and return its result + converted to Vim data structures. + Uses Python 2 or 3, see |python_x| and 'pyxversion'. + See also: |pyeval()|, |py3eval()| + + Can also be used as a |method|: > + GetExpr()->pyxeval() +< + *E726* *E727* +range({expr} [, {max} [, {stride}]]) *range()* + Returns a |List| with Numbers: + - If only {expr} is specified: [0, 1, ..., {expr} - 1] + - If {max} is specified: [{expr}, {expr} + 1, ..., {max}] + - If {stride} is specified: [{expr}, {expr} + {stride}, ..., + {max}] (increasing {expr} with {stride} each time, not + producing a value past {max}). + When the maximum is one before the start the result is an + empty list. When the maximum is more than one before the + start this is an error. + Examples: > + range(4) " [0, 1, 2, 3] + range(2, 4) " [2, 3, 4] + range(2, 9, 3) " [2, 5, 8] + range(2, -2, -1) " [2, 1, 0, -1, -2] + range(0) " [] + range(2, 0) " error! +< + Can also be used as a |method|: > + GetExpr()->range() +< +rand([{expr}]) *rand()* + Return a pseudo-random Number generated with an xoshiro128** + algorithm using seed {expr}. The returned number is 32 bits, + also on 64 bits systems, for consistency. + {expr} can be initialized by |srand()| and will be updated by + rand(). If {expr} is omitted, an internal seed value is used + and updated. + + Examples: > + :echo rand() + :let seed = srand() + :echo rand(seed) + :echo rand(seed) % 16 " random number 0 - 15 +< + Can also be used as a |method|: > + seed->rand() +< + *readdir()* +readdir({directory} [, {expr}]) + Return a list with file and directory names in {directory}. + + When {expr} is omitted all entries are included. + When {expr} is given, it is evaluated to check what to do: + If {expr} results in -1 then no further entries will + be handled. + If {expr} results in 0 then this entry will not be + added to the list. + If {expr} results in 1 then this entry will be added + to the list. + Each time {expr} is evaluated |v:val| is set to the entry name. + When {expr} is a function the name is passed as the argument. + For example, to get a list of files ending in ".txt": > + readdir(dirname, {n -> n =~ '.txt$'}) +< To skip hidden and backup files: > + readdir(dirname, {n -> n !~ '^\.\|\~$'}) + +< If you want to get a directory tree: > + function! s:tree(dir) + return {a:dir : map(readdir(a:dir), + \ {_, x -> isdirectory(x) ? + \ {x : s:tree(a:dir .. '/' .. x)} : x})} + endfunction + echo s:tree(".") +< + Can also be used as a |method|: > + GetDirName()->readdir() +< + *readfile()* +readfile({fname} [, {type} [, {max}]]) + Read file {fname} and return a |List|, each line of the file + as an item. Lines are broken at NL characters. Macintosh + files separated with CR will result in a single long line + (unless a NL appears somewhere). + All NUL characters are replaced with a NL character. + When {type} contains "b" binary mode is used: + - When the last line ends in a NL an extra empty list item is + added. + - No CR characters are removed. + When {type} contains "B" a |Blob| is returned with the binary + data of the file unmodified. + Otherwise: + - CR characters that appear before a NL are removed. + - Whether the last line ends in a NL or not does not matter. + - Any UTF-8 byte order mark is removed from the text. + When {max} is given this specifies the maximum number of lines + to be read. Useful if you only want to check the first ten + lines of a file: > + :for line in readfile(fname, '', 10) + : if line =~ 'Date' | echo line | endif + :endfor +< When {max} is negative -{max} lines from the end of the file + are returned, or as many as there are. + When {max} is zero the result is an empty list. + Note that without {max} the whole file is read into memory. + Also note that there is no recognition of encoding. Read a + file into a buffer if you need to. + When the file can't be opened an error message is given and + the result is an empty list. + Also see |writefile()|. + + 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. + See |@|. + +reg_recorded() *reg_recorded()* + Returns the single letter name of the last recorded register. + Returns an empty string when nothing was recorded yet. + See |q| and |Q|. + +reg_recording() *reg_recording()* + Returns the single letter name of the register being recorded. + Returns an empty string when not recording. See |q|. + +reltime([{start} [, {end}]]) *reltime()* + Return an item that represents a time value. The item is a + list with items that depend on the system. + The item can be passed to |reltimestr()| to convert it to a + string or |reltimefloat()| to convert to a Float. + + Without an argument it returns the current "relative time", an + implementation-defined value meaningful only when used as an + argument to |reltime()|, |reltimestr()| and |reltimefloat()|. + + With one argument it returns the time passed since the time + specified in the argument. + With two arguments it returns the time passed between {start} + and {end}. + + The {start} and {end} arguments must be values returned by + reltime(). + + Can also be used as a |method|: > + GetStart()->reltime() +< + Note: |localtime()| returns the current (non-relative) time. + +reltimefloat({time}) *reltimefloat()* + Return a Float that represents the time value of {time}. + Unit of time is seconds. + Example: + let start = reltime() + call MyFunction() + let seconds = reltimefloat(reltime(start)) + See the note of reltimestr() about overhead. + Also see |profiling|. + If there is an error an empty string is returned + + Can also be used as a |method|: > + reltime(start)->reltimefloat() + +reltimestr({time}) *reltimestr()* + Return a String that represents the time value of {time}. + This is the number of seconds, a dot and the number of + microseconds. Example: > + let start = reltime() + call MyFunction() + echo reltimestr(reltime(start)) +< Note that overhead for the commands will be added to the time. + Leading spaces are used to make the string align nicely. You + can use split() to remove it. > + echo split(reltimestr(reltime(start)))[0] +< Also see |profiling|. + If there is an error an empty string is returned + + Can also be used as a |method|: > + reltime(start)->reltimestr() +< + *remote_expr()* *E449* +remote_expr({server}, {string} [, {idvar} [, {timeout}]]) + 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 + and the result will be the empty string. + + Variables will be evaluated in the global namespace, + independent of a function currently being active. Except + when in debug mode, then local function variables and + arguments can be evaluated. + + Examples: > + :echo remote_expr("gvim", "2+2") + :echo remote_expr("gvim1", "b:current_syntax") +< + +remote_foreground({server}) *remote_foreground()* + Move the Vim server with the name {server} to the foreground. + 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 + around the problem that the OS doesn't always allow the server + to bring itself to the foreground. + Note: This does not restore the window if it was minimized, + like foreground() does. + This function is not available in the |sandbox|. + {only in the Win32 GUI and the Win32 console version} + + +remote_peek({serverid} [, {retvar}]) *remote_peek()* + Returns a positive number if there are available strings + from {serverid}. Copies any reply string into the variable + {retvar} if specified. {retvar} must be a string with the + name of a variable. + Returns zero if none are available. + Returns -1 if something is wrong. + See also |clientserver|. + This function is not available in the |sandbox|. + Examples: > + :let repl = "" + :echo "PEEK: " .. remote_peek(id, "repl") .. ": " .. repl + +remote_read({serverid}, [{timeout}]) *remote_read()* + Return the oldest available reply from {serverid} and consume + it. Unless a {timeout} in seconds is given, it blocks until a + reply is available. + See also |clientserver|. + This function is not available in the |sandbox|. + Example: > + :echo remote_read(id) +< + *remote_send()* *E241* +remote_send({server}, {string} [, {idvar}]) + 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|. + + Note: Any errors will be reported in the server and may mess + up the display. + Examples: > + :echo remote_send("gvim", ":DropAndReply " .. file, "serverid") .. + \ remote_read(serverid) + + :autocmd NONE RemoteReply * + \ echo remote_read(expand("<amatch>")) + :echo remote_send("gvim", ":sleep 10 | echo " .. + \ 'server2client(expand("<client>"), "HELLO")<CR>') +< + *remote_startserver()* *E941* *E942* +remote_startserver({name}) + Become the server {name}. This fails if already running as a + server, when |v:servername| is not empty. + +remove({list}, {idx} [, {end}]) *remove()* + Without {end}: Remove the item at {idx} from |List| {list} and + return the item. + With {end}: Remove items from {idx} to {end} (inclusive) and + return a |List| with these items. When {idx} points to the same + item as {end} a list with one item is returned. When {end} + points to an item before {idx} this is an error. + See |list-index| for possible values of {idx} and {end}. + Example: > + :echo "last item: " .. remove(mylist, -1) + :call remove(mylist, 0, 9) +< + Use |delete()| to remove a file. + + Can also be used as a |method|: > + mylist->remove(idx) + +remove({blob}, {idx} [, {end}]) + Without {end}: Remove the byte at {idx} from |Blob| {blob} and + return the byte. + With {end}: Remove bytes from {idx} to {end} (inclusive) and + return a |Blob| with these bytes. When {idx} points to the same + byte as {end} a |Blob| with one byte is returned. When {end} + points to a byte before {idx} this is an error. + Example: > + :echo "last byte: " .. remove(myblob, -1) + :call remove(mylist, 0, 9) + +remove({dict}, {key}) + Remove the entry from {dict} with key {key} and return it. + Example: > + :echo "removed " .. remove(dict, "one") +< If there is no {key} in {dict} this is an error. + +rename({from}, {to}) *rename()* + Rename the file by the name {from} to the name {to}. This + should also work to move files across file systems. The + result is a Number, which is 0 if the file was renamed + successfully, and non-zero when the renaming failed. + NOTE: If {to} exists it is overwritten without warning. + This function is not available in the |sandbox|. + + Can also be used as a |method|: > + GetOldName()->rename(newname) + +repeat({expr}, {count}) *repeat()* + Repeat {expr} {count} times and return the concatenated + result. Example: > + :let separator = repeat('-', 80) +< When {count} is zero or negative the result is empty. + When {expr} is a |List| the result is {expr} concatenated + {count} times. Example: > + :let longlist = repeat(['a', 'b'], 3) +< Results in ['a', 'b', 'a', 'b', 'a', 'b']. + + Can also be used as a |method|: > + mylist->repeat(count) + +resolve({filename}) *resolve()* *E655* + On MS-Windows, when {filename} is a shortcut (a .lnk file), + returns the path the shortcut points to in a simplified form. + On Unix, repeat resolving symbolic links in all path + components of {filename} and return the simplified result. + To cope with link cycles, resolving of symbolic links is + stopped after 100 iterations. + On other systems, return the simplified {filename}. + The simplification step is done as by |simplify()|. + resolve() keeps a leading path component specifying the + current directory (provided the result is still a relative + path name) and also keeps a trailing path separator. + + Can also be used as a |method|: > + GetName()->resolve() +< + *reverse()* +reverse({object}) + Reverse the order of items in {object} in-place. + {object} can be a |List| or a |Blob|. + Returns {object}. + If you want an object to remain unmodified make a copy first: > + :let revlist = reverse(copy(mylist)) +< Can also be used as a |method|: > + mylist->reverse() + +round({expr}) *round()* + Round off {expr} to the nearest integral value and return it + as a |Float|. If {expr} lies halfway between two integral + values, then use the larger one (away from zero). + {expr} must evaluate to a |Float| or a |Number|. + Examples: > + echo round(0.456) +< 0.0 > + echo round(4.5) +< 5.0 > + echo round(-4.5) +< -5.0 + + Can also be used as a |method|: > + Compute()->round() + +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()* + 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()* + Deprecated. Replace > + :let id = rpcstart('prog', ['arg1', 'arg2']) +< with > + :let id = jobstart(['prog', 'arg1', 'arg2'], {'rpc': v:true}) + +rubyeval({expr}) *rubyeval()* + Evaluate Ruby expression {expr} and return its result + converted to Vim data structures. + Numbers, floats and strings are returned as they are (strings + are copied though). + Arrays are represented as Vim |List| type. + Hashes are represented as Vim |Dictionary| type. + Other objects are represented as strings resulted from their + "Object#to_s" method. + + Can also be used as a |method|: > + GetRubyExpr()->rubyeval() + +screenattr({row}, {col}) *screenattr()* + Like |screenchar()|, but return the attribute. This is a rather + arbitrary number that can only be used to compare to the + attribute at other positions. + + Can also be used as a |method|: > + GetRow()->screenattr(col) + +screenchar({row}, {col}) *screenchar()* + The result is a Number, which is the character at position + [row, col] on the screen. This works for every possible + screen position, also status lines, window separators and the + command line. The top left position is row one, column one + The character excludes composing characters. For double-byte + encodings it may only be the first byte. + This is mainly to be used for testing. + Returns -1 when row or col is out of range. + + Can also be used as a |method|: > + GetRow()->screenchar(col) + +screenchars({row}, {col}) *screenchars()* + The result is a List of Numbers. The first number is the same + as what |screenchar()| returns. Further numbers are + composing characters on top of the base character. + This is mainly to be used for testing. + Returns an empty List when row or col is out of range. + + Can also be used as a |method|: > + GetRow()->screenchars(col) + +screencol() *screencol()* + The result is a Number, which is the current screen column of + the cursor. The leftmost column has number 1. + This function is mainly used for testing. + + Note: Always returns the current screen column, thus if used + in a command (e.g. ":echo screencol()") it will return the + column inside the command line, which is 1 when the command is + executed. To get the cursor position in the file use one of + the following mappings: > + nnoremap <expr> GG ":echom " .. screencol() .. "\n" + nnoremap <silent> GG :echom screencol()<CR> + noremap GG <Cmd>echom screencol()<Cr> +< +screenpos({winid}, {lnum}, {col}) *screenpos()* + The result is a Dict with the screen position of the text + character in window {winid} at buffer line {lnum} and column + {col}. {col} is a one-based byte index. + The Dict has these members: + row screen row + col first screen column + endcol last screen column + curscol cursor screen column + If the specified position is not visible, all values are zero. + The "endcol" value differs from "col" when the character + occupies more than one screen cell. E.g. for a Tab "col" can + be 1 and "endcol" can be 8. + The "curscol" value is where the cursor would be placed. For + a Tab it would be the same as "endcol", while for a double + width character it would be the same as "col". + The |conceal| feature is ignored here, the column numbers are + as if 'conceallevel' is zero. You can set the cursor to the + right position and use |screencol()| to get the value with + |conceal| taken into account. + + Can also be used as a |method|: > + GetWinid()->screenpos(lnum, col) + +screenrow() *screenrow()* + The result is a Number, which is the current screen row of the + cursor. The top line has number one. + This function is mainly used for testing. + Alternatively you can use |winline()|. + + Note: Same restrictions as with |screencol()|. + +screenstring({row}, {col}) *screenstring()* + The result is a String that contains the base character and + any composing characters at position [row, col] on the screen. + This is like |screenchars()| but returning a String with the + characters. + This is mainly to be used for testing. + Returns an empty String when row or col is out of range. + + Can also be used as a |method|: > + GetRow()->screenstring(col) +< + *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). + + When a match has been found its line number is returned. + If there is no match a 0 is returned and the cursor doesn't + move. No error message is given. + + {flags} is a String, which can contain these character flags: + 'b' search Backward instead of forward + 'c' accept a match at the Cursor position + 'e' move to the End of the match + 'n' do Not move the cursor + 'p' return number of matching sub-Pattern (see below) + 's' Set the ' mark at the previous location of the cursor + 'w' Wrap around the end of the file + 'W' don't Wrap around the end of the file + 'z' start searching at the cursor column instead of Zero + If neither 'w' or 'W' is given, the 'wrapscan' option applies. + + If the 's' flag is supplied, the ' mark is set, only if the + cursor is moved. The 's' flag cannot be combined with the 'n' + flag. + + 'ignorecase', 'smartcase' and 'magic' are used. + + When the 'z' flag is not given, forward searching always + starts in column zero and then matches before the cursor are + skipped. When the 'c' flag is present in 'cpo' the next + search starts after the match. Without the 'c' flag the next + search starts one column further. This matters for + overlapping matches. + When searching backwards and the 'z' flag is given then the + search starts in column zero, thus no match in the current + line will be found (unless wrapping around the end of the + file). + + When the {stopline} argument is given then the search stops + after searching this line. This is useful to restrict the + search to a range of lines. Examples: > + let match = search('(', 'b', line("w0")) + let end = search('END', '', line("w$")) +< When {stopline} is used and it is not zero this also implies + that the search does not wrap around the end of the file. + A zero value is equal to not giving the argument. + + When the {timeout} argument is given the search stops when + more than this many milliseconds have passed. Thus when + {timeout} is 500 the search stops after half a second. + 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 + whole pattern did match. + To get the column number too use |searchpos()|. + + The cursor will be positioned at the match, unless the 'n' + flag is used. + + Example (goes over all files in the argument list): > + :let n = 1 + :while n <= argc() " loop over all files in arglist + : exe "argument " .. n + : " start at the last char in the file and wrap for the + : " first search to find match at start of file + : normal G$ + : let flags = "w" + : while search("foo", flags) > 0 + : s/foo/bar/g + : let flags = "W" + : endwhile + : update " write the file if modified + : let n = n + 1 + :endwhile +< + Example for using some flags: > + :echo search('\<if\|\(else\)\|\(endif\)', 'ncpe') +< This will search for the keywords "if", "else", and "endif" + under or after the cursor. Because of the 'p' flag, it + returns 1, 2, or 3 depending on which keyword is found, or 0 + if the search fails. With the cursor on the first word of the + line: + if (foo == 0) | let foo = foo + 1 | endif ~ + the function returns 1. Without the 'c' flag, the function + finds the "endif" and returns 3. The same thing happens + without the 'e' flag if the cursor is on the "f" of "if". + The 'n' flag tells the function not to move the cursor. + + Can also be used as a |method|: > + GetPattern()->search() + +searchcount([{options}]) *searchcount()* + Get or update the last search count, like what is displayed + without the "S" flag in 'shortmess'. This works even if + 'shortmess' does contain the "S" flag. + + This returns a Dictionary. The dictionary is empty if the + previous pattern was not set and "pattern" was not specified. + + key type meaning ~ + current |Number| current position of match; + 0 if the cursor position is + before the first match + exact_match |Boolean| 1 if "current" is matched on + "pos", otherwise 0 + total |Number| total count of matches found + incomplete |Number| 0: search was fully completed + 1: recomputing was timed out + 2: max count exceeded + + For {options} see further down. + + To get the last search count when |n| or |N| was pressed, call + this function with `recompute: 0` . This sometimes returns + wrong information because |n| and |N|'s maximum count is 99. + If it exceeded 99 the result must be max count + 1 (100). If + you want to get correct information, specify `recompute: 1`: > + + " result == maxcount + 1 (100) when many matches + let result = searchcount(#{recompute: 0}) + + " Below returns correct result (recompute defaults + " to 1) + let result = searchcount() +< + The function is useful to add the count to |statusline|: > + function! LastSearchCount() abort + let result = searchcount(#{recompute: 0}) + if empty(result) + return '' + endif + if result.incomplete ==# 1 " timed out + return printf(' /%s [?/??]', @/) + elseif result.incomplete ==# 2 " max count exceeded + if result.total > result.maxcount && + \ result.current > result.maxcount + return printf(' /%s [>%d/>%d]', @/, + \ result.current, result.total) + elseif result.total > result.maxcount + return printf(' /%s [%d/>%d]', @/, + \ result.current, result.total) + endif + endif + return printf(' /%s [%d/%d]', @/, + \ result.current, result.total) + endfunction + let &statusline ..= '%{LastSearchCount()}' + + " Or if you want to show the count only when + " 'hlsearch' was on + " let &statusline ..= + " \ '%{v:hlsearch ? LastSearchCount() : ""}' +< + You can also update the search count, which can be useful in a + |CursorMoved| or |CursorMovedI| autocommand: > + + autocmd CursorMoved,CursorMovedI * + \ let s:searchcount_timer = timer_start( + \ 200, function('s:update_searchcount')) + function! s:update_searchcount(timer) abort + if a:timer ==# s:searchcount_timer + call searchcount(#{ + \ recompute: 1, maxcount: 0, timeout: 100}) + redrawstatus + endif + endfunction +< + This can also be used to count matched texts with specified + pattern in the current buffer using "pattern": > + + " Count '\<foo\>' in this buffer + " (Note that it also updates search count) + let result = searchcount(#{pattern: '\<foo\>'}) + + " To restore old search count by old pattern, + " search again + call searchcount() +< + {options} must be a Dictionary. It can contain: + key type meaning ~ + recompute |Boolean| if |TRUE|, recompute the count + like |n| or |N| was executed. + otherwise returns the last + result by |n|, |N|, or this + function is returned. + (default: |TRUE|) + pattern |String| recompute if this was given + and different with |@/|. + this works as same as the + below command is executed + before calling this function > + let @/ = pattern +< (default: |@/|) + timeout |Number| 0 or negative number is no + timeout. timeout milliseconds + for recomputing the result + (default: 0) + maxcount |Number| 0 or negative number is no + limit. max count of matched + text while recomputing the + result. if search exceeded + total count, "total" value + becomes `maxcount + 1` + (default: 0) + pos |List| `[lnum, col, off]` value + when recomputing the result. + this changes "current" result + value. see |cursor()|, |getpos() + (default: cursor's position) + + Can also be used as a |method|: > + GetSearchOpts()->searchcount() +< +searchdecl({name} [, {global} [, {thisblock}]]) *searchdecl()* + Search for the declaration of {name}. + + With a non-zero {global} argument it works like |gD|, find + first match in the file. Otherwise it works like |gd|, find + first match in the function. + + With a non-zero {thisblock} argument matches in a {} block + that ends before the cursor position are ignored. Avoids + finding variable declarations only valid in another scope. + + Moves the cursor to the found match. + Returns zero for success, non-zero for failure. + Example: > + if searchdecl('myvar') == 0 + echo getline('.') + endif +< + Can also be used as a |method|: > + GetName()->searchdecl() +< + *searchpair()* +searchpair({start}, {middle}, {end} [, {flags} [, {skip} + [, {stopline} [, {timeout}]]]]) + Search for the match of a nested start-end pair. This can be + used to find the "endif" that matches an "if", while other + if/endif pairs in between are ignored. + The search starts at the cursor. The default is to search + forward, include 'b' in {flags} to search backward. + If a match is found, the cursor is positioned at it and the + line number is returned. If no match is found 0 or -1 is + returned and the cursor doesn't move. No error message is + given. + + {start}, {middle} and {end} are patterns, see |pattern|. They + must not contain \( \) pairs. Use of \%( \) is allowed. When + {middle} is not empty, it is found when searching from either + direction, but only when not in a nested start-end pair. A + typical use is: > + searchpair('\<if\>', '\<else\>', '\<endif\>') +< By leaving {middle} empty the "else" is skipped. + + {flags} 'b', 'c', 'n', 's', 'w' and 'W' are used like with + |search()|. Additionally: + 'r' Repeat until no more matches found; will find the + outer pair. Implies the 'W' flag. + 'm' Return number of matches instead of line number with + the match; will be > 1 when 'r' is used. + Note: it's nearly always a good idea to use the 'W' flag, to + avoid wrapping around the end of the file. + + When a match for {start}, {middle} or {end} is found, the + {skip} expression is evaluated with the cursor positioned on + the start of the match. It should return non-zero if this + match is to be skipped. E.g., because it is inside a comment + or a string. + When {skip} is omitted or empty, every match is accepted. + When evaluating {skip} causes an error the search is aborted + and -1 returned. + {skip} can be a string, a lambda, a funcref or a partial. + Anything else makes the function fail. + + For {stopline} and {timeout} see |search()|. + + The value of 'ignorecase' is used. 'magic' is ignored, the + patterns are used like it's on. + + The search starts exactly at the cursor. A match with + {start}, {middle} or {end} at the next character, in the + direction of searching, is the first one found. Example: > + if 1 + if 2 + endif 2 + endif 1 +< When starting at the "if 2", with the cursor on the "i", and + searching forwards, the "endif 2" is found. When starting on + the character just before the "if 2", the "endif 1" will be + found. That's because the "if 2" will be found first, and + then this is considered to be a nested if/endif from "if 2" to + "endif 2". + When searching backwards and {end} is more than one character, + it may be useful to put "\zs" at the end of the pattern, so + that when the cursor is inside a match with the end it finds + the matching start. + + Example, to find the "endif" command in a Vim script: > + + :echo searchpair('\<if\>', '\<el\%[seif]\>', '\<en\%[dif]\>', 'W', + \ 'getline(".") =~ "^\\s*\""') + +< The cursor must be at or after the "if" for which a match is + to be found. Note that single-quote strings are used to avoid + having to double the backslashes. The skip expression only + catches comments at the start of a line, not after a command. + Also, a word "en" or "if" halfway through a line is considered + a match. + Another example, to search for the matching "{" of a "}": > + + :echo searchpair('{', '', '}', 'bW') + +< This works when the cursor is at or before the "}" for which a + match is to be found. To reject matches that syntax + highlighting recognized as strings: > + + :echo searchpair('{', '', '}', 'bW', + \ 'synIDattr(synID(line("."), col("."), 0), "name") =~? "string"') +< + *searchpairpos()* +searchpairpos({start}, {middle}, {end} [, {flags} [, {skip} + [, {stopline} [, {timeout}]]]]) + Same as |searchpair()|, 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 + the column position of the match. If no match is found, + returns [0, 0]. > + + :let [lnum,col] = searchpairpos('{', '', '}', 'n') +< + See |match-parens| for a bigger and more useful example. + + *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 + the column position of the match. If no match is found, + returns [0, 0]. + Example: > + :let [lnum, col] = searchpos('mypattern', 'n') + +< When the 'p' flag is given then there is an extra item with + the sub-pattern match number |search()-sub-match|. Example: > + :let [lnum, col, submatch] = searchpos('\(\l\)\|\(\u\)', 'np') +< In this example "submatch" is 2 when a lowercase letter is + found |/\l|, 3 when an uppercase letter is found |/\u|. + + Can also be used as a |method|: > + GetPattern()->searchpos() + +server2client({clientid}, {string}) *server2client()* + Send a reply string to {clientid}. The most recent {clientid} + that sent a string can be retrieved with expand("<client>"). + Note: + Returns zero for success, -1 for failure. + This id has to be stored before the next command can be + received. I.e. before returning from the received command and + before calling any commands that waits for input. + See also |clientserver|. + Example: > + :echo server2client(expand("<client>"), "HELLO") + +< Can also be used as a |method|: > + GetClientId()->server2client(string) +< +serverlist() *serverlist()* + Returns a list of server addresses, or empty if all servers + were stopped. |serverstart()| |serverstop()| + Example: > + :echo serverlist() + +serverstart([{address}]) *serverstart()* + Opens a socket or named pipe at {address} and listens for + |RPC| messages. Clients can send |API| commands to the address + to control Nvim. Returns the address string. + + If {address} does not contain a colon ":" it is interpreted as + a named pipe or Unix domain socket path. + + Example: > + if has('win32') + call serverstart('\\.\pipe\nvim-pipe-1234') + else + call serverstart('nvim.sock') + endif +< + If {address} contains a colon ":" it is interpreted as a TCP + address where the last ":" separates the host and port. + Assigns a random port if it is empty or 0. Supports IPv4/IPv6. + + Example: > + :call serverstart('::1:12345') +< + If no address is given, it is equivalent to: > + :call serverstart(tempname()) + +< |$NVIM_LISTEN_ADDRESS| is set to {address} if not already set. + +serverstop({address}) *serverstop()* + Closes the pipe or socket at {address}. + Returns TRUE if {address} is valid, else FALSE. + If |$NVIM_LISTEN_ADDRESS| is stopped it is unset. + If |v:servername| is stopped it is set to the next available + address returned by |serverlist()|. + +setbufline({buf}, {lnum}, {text}) *setbufline()* + Set line {lnum} to {text} in buffer {buf}. This works like + |setline()| for the specified buffer. + + This function works only for loaded buffers. First call + |bufload()| if needed. + + To insert lines use |appendbufline()|. + Any text properties in {lnum} are cleared. + + {text} can be a string to set one line, or a list of strings + to set multiple lines. If the list extends below the last + line then those lines are added. + + For the use of {buf}, see |bufname()| above. + + {lnum} is used like with |setline()|. + Use "$" to refer to the last line in buffer {buf}. + When {lnum} is just below the last line the {text} will be + added below the last line. + On success 0 is returned, on failure 1 is returned. + + If {buf} is not a valid buffer or {lnum} is not valid, an + error message is given. + + Can also be used as a |method|, the base is passed as the + third argument: > + GetText()->setbufline(buf, lnum) + +setbufvar({buf}, {varname}, {val}) *setbufvar()* + Set option or local variable {varname} in buffer {buf} to + {val}. + This also works for a global or local window option, but it + doesn't work for a global or local window variable. + For a local window option the global value is unchanged. + For the use of {buf}, see |bufname()| above. + The {varname} argument is a string. + Note that the variable name without "b:" must be used. + Examples: > + :call setbufvar(1, "&mod", 1) + :call setbufvar("todo", "myvar", "foobar") +< This function is not available in the |sandbox|. + + Can also be used as a |method|, the base is passed as the + third argument: > + GetValue()->setbufvar(buf, varname) + +setcharpos({expr}, {list}) *setcharpos()* + Same as |setpos()| but uses the specified column number as the + character index instead of the byte index in the line. + + Example: + With the text "여보세요" in line 8: > + call setcharpos('.', [0, 8, 4, 0]) +< positions the cursor on the fourth character '요'. > + call setpos('.', [0, 8, 4, 0]) +< positions the cursor on the second character '보'. + + Can also be used as a |method|: > + GetPosition()->setcharpos('.') + +setcharsearch({dict}) *setcharsearch()* + Set the current character search information to {dict}, + which contains one or more of the following entries: + + char character which will be used for a subsequent + |,| or |;| command; an empty string clears the + character search + forward direction of character search; 1 for forward, + 0 for backward + until type of character search; 1 for a |t| or |T| + character search, 0 for an |f| or |F| + character search + + This can be useful to save/restore a user's character search + from a script: > + :let prevsearch = getcharsearch() + :" Perform a command which clobbers user's search + :call setcharsearch(prevsearch) +< Also see |getcharsearch()|. + + Can also be used as a |method|: > + SavedSearch()->setcharsearch() + +setcmdpos({pos}) *setcmdpos()* + Set the cursor position in the command line to byte position + {pos}. The first position is 1. + Use |getcmdpos()| to obtain the current position. + Only works while editing the command line, thus you must use + |c_CTRL-\_e|, |c_CTRL-R_=| or |c_CTRL-R_CTRL-R| with '='. For + |c_CTRL-\_e| and |c_CTRL-R_CTRL-R| with '=' the position is + set after the command line is set to the expression. For + |c_CTRL-R_=| it is set after evaluating the expression but + before inserting the resulting text. + When the number is too big the cursor is put at the end of the + line. A number smaller than one has undefined results. + Returns FALSE when successful, TRUE when not editing the + command line. + + Can also be used as a |method|: > + GetPos()->setcmdpos() + +setcursorcharpos({lnum}, {col} [, {off}]) *setcursorcharpos()* +setcursorcharpos({list}) + Same as |cursor()| but uses the specified column number as the + character index instead of the byte index in the line. + + Example: + With the text "여보세요" in line 4: > + call setcursorcharpos(4, 3) +< positions the cursor on the third character '세'. > + call cursor(4, 3) +< positions the cursor on the first character '여'. + + Can also be used as a |method|: > + GetCursorPos()->setcursorcharpos() + +setenv({name}, {val}) *setenv()* + Set environment variable {name} to {val}. Example: > + call setenv('HOME', '/home/myhome') + +< When {val} is |v:null| the environment variable is deleted. + See also |expr-env|. + + Can also be used as a |method|, the base is passed as the + second argument: > + GetPath()->setenv('PATH') + +setfperm({fname}, {mode}) *setfperm()* *chmod* + Set the file permissions for {fname} to {mode}. + {mode} must be a string with 9 characters. It is of the form + "rwxrwxrwx", where each group of "rwx" flags represent, in + turn, the permissions of the owner of the file, the group the + file belongs to, and other users. A '-' character means the + permission is off, any other character means on. Multi-byte + characters are not supported. + + For example "rw-r-----" means read-write for the user, + readable by the group, not accessible by others. "xx-x-----" + would do the same thing. + + Returns non-zero for success, zero for failure. + + Can also be used as a |method|: > + GetFilename()->setfperm(mode) +< + To read permissions see |getfperm()|. + +setline({lnum}, {text}) *setline()* + Set line {lnum} of the current buffer to {text}. To insert + lines use |append()|. To set lines in another buffer use + |setbufline()|. + + {lnum} is used like with |getline()|. + When {lnum} is just below the last line the {text} will be + added below the last line. + + If this succeeds, FALSE is returned. If this fails (most likely + because {lnum} is invalid) TRUE is returned. + + Example: > + :call setline(5, strftime("%c")) + +< When {text} is a |List| then line {lnum} and following lines + will be set to the items in the list. Example: > + :call setline(5, ['aaa', 'bbb', 'ccc']) +< This is equivalent to: > + :for [n, l] in [[5, 'aaa'], [6, 'bbb'], [7, 'ccc']] + : call setline(n, l) + :endfor + +< Note: The '[ and '] marks are not set. + + Can also be used as a |method|, the base is passed as the + second argument: > + GetText()->setline(lnum) + +setloclist({nr}, {list} [, {action} [, {what}]]) *setloclist()* + Create or replace or add to 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. + + For a location list window, the displayed location list is + modified. For an invalid window number {nr}, -1 is returned. + Otherwise, same as |setqflist()|. + Also see |location-list|. + + For {action} see |setqflist-action|. + + If the optional {what} dictionary argument is supplied, then + only the items listed in {what} are set. Refer to |setqflist()| + for the list of supported keys in {what}. + + Can also be used as a |method|, the base is passed as the + second argument: > + GetLoclist()->setloclist(winnr) + +setmatches({list} [, {win}]) *setmatches()* + Restores a list of matches saved by |getmatches() for the + current window|. Returns 0 if successful, otherwise -1. All + current matches are cleared before the list is restored. See + example for |getmatches()|. + If {win} is specified, use the window with this number or + window ID instead of the current window. + + Can also be used as a |method|: > + GetMatches()->setmatches() +< + *setpos()* +setpos({expr}, {list}) + Set the position for String {expr}. Possible values: + . the cursor + 'x mark x + + {list} must be a |List| with four or five numbers: + [bufnum, lnum, col, off] + [bufnum, lnum, col, off, curswant] + + "bufnum" is the buffer number. Zero can be used for the + current buffer. When setting an uppercase mark "bufnum" is + used for the mark position. For other marks it specifies the + buffer to set the mark in. You can use the |bufnr()| function + to turn a file name into a buffer number. + For setting the cursor and the ' mark "bufnum" is ignored, + since these are associated with a window, not a buffer. + Does not change the jumplist. + + "lnum" and "col" are the position in the buffer. The first + column is 1. Use a zero "lnum" to delete a mark. If "col" is + smaller than 1 then 1 is used. To use the character count + instead of the byte count, use |setcharpos()|. + + The "off" number is only used when 'virtualedit' is set. Then + it is the offset in screen columns from the start of the + character. E.g., a position within a <Tab> or after the last + character. + + The "curswant" number is only used when setting the cursor + position. It sets the preferred column for when moving the + cursor vertically. When the "curswant" number is missing the + preferred column is not set. When it is present and setting a + mark position it is not used. + + Note that for '< and '> changing the line number may result in + the marks to be effectively be swapped, so that '< is always + before '>. + + Returns 0 when the position could be set, -1 otherwise. + An error message is given if {expr} is invalid. + + Also see |setcharpos()|, |getpos()| and |getcurpos()|. + + This does not restore the preferred column for moving + vertically; if you set the cursor position with this, |j| and + |k| motions will jump to previous columns! Use |cursor()| to + also set the preferred column. Also see the "curswant" key in + |winrestview()|. + + Can also be used as a |method|: > + GetPosition()->setpos('.') + +setqflist({list} [, {action} [, {what}]]) *setqflist()* + Create or replace or add to the quickfix list. + + If the optional {what} dictionary argument is supplied, then + only the items listed in {what} are set. The first {list} + argument is ignored. See below for the supported items in + {what}. + *setqflist-what* + When {what} is not present, the items in {list} are used. Each + item must be a dictionary. Non-dictionary items in {list} are + ignored. Each dictionary item can contain the following + entries: + + bufnr buffer number; must be the number of a valid + buffer + filename name of a file; only used when "bufnr" is not + present or it is invalid. + module name of a module; if given it will be used in + quickfix error window instead of the filename + lnum line number in the file + pattern search pattern used to locate the error + col column number + vcol when non-zero: "col" is visual column + when zero: "col" is byte index + nr error number + text description of the error + type single-character error type, 'E', 'W', etc. + valid recognized error message + + The "col", "vcol", "nr", "type" and "text" entries are + optional. Either "lnum" or "pattern" entry can be used to + locate a matching error line. + If the "filename" and "bufnr" entries are not present or + neither the "lnum" or "pattern" entries are present, then the + item will not be handled as an error line. + If both "pattern" and "lnum" are present then "pattern" will + be used. + If the "valid" entry is not supplied, then the valid flag is + set when "bufnr" is a valid buffer or "filename" exists. + If you supply an empty {list}, the quickfix list will be + cleared. + Note that the list is not exactly the same as what + |getqflist()| returns. + + {action} values: *setqflist-action* *E927* + 'a' The items from {list} are added to the existing + quickfix list. If there is no existing list, then a + new list is created. + + 'r' The items from the current quickfix list are replaced + with the items from {list}. This can also be used to + clear the list: > + :call setqflist([], 'r') +< + 'f' All the quickfix lists in the quickfix stack are + freed. + + If {action} is not present or is set to ' ', then a new list + is created. The new quickfix list is added after the current + quickfix list in the stack and all the following lists are + freed. To add a new quickfix list at the end of the stack, + set "nr" in {what} to "$". + + The following items can be specified in dictionary {what}: + context quickfix list context. See |quickfix-context| + efm errorformat to use when parsing text from + "lines". If this is not present, then the + 'errorformat' option value is used. + See |quickfix-parse| + id quickfix list identifier |quickfix-ID| + idx index of the current entry in the quickfix + list specified by 'id' or 'nr'. If set to '$', + then the last entry in the list is set as the + current entry. See |quickfix-index| + items list of quickfix entries. Same as the {list} + argument. + lines use 'errorformat' to parse a list of lines and + add the resulting entries to the quickfix list + {nr} or {id}. Only a |List| value is supported. + See |quickfix-parse| + nr list number in the quickfix stack; zero + means the current quickfix list and "$" means + the last quickfix list. + quickfixtextfunc + function to get the text to display in the + quickfix window. The value can be the name of + a function or a funcref or a lambda. Refer to + |quickfix-window-function| for an explanation + of how to write the function and an example. + title quickfix list title text. See |quickfix-title| + Unsupported keys in {what} are ignored. + If the "nr" item is not present, then the current quickfix list + is modified. When creating a new quickfix list, "nr" can be + set to a value one greater than the quickfix stack size. + When modifying a quickfix list, to guarantee that the correct + list is modified, "id" should be used instead of "nr" to + specify the list. + + Examples (See also |setqflist-examples|): > + :call setqflist([], 'r', {'title': 'My search'}) + :call setqflist([], 'r', {'nr': 2, 'title': 'Errors'}) + :call setqflist([], 'a', {'id':qfid, 'lines':["F1:10:L10"]}) +< + Returns zero for success, -1 for failure. + + This function can be used to create a quickfix list + independent of the 'errorformat' setting. Use a command like + `:cc 1` to jump to the first position. + + Can also be used as a |method|, the base is passed as the + second argument: > + GetErrorlist()->setqflist() +< + *setreg()* +setreg({regname}, {value} [, {options}]) + Set the register {regname} to {value}. + The {regname} argument is a string. + + {value} may be any value returned by |getreg()| or + |getreginfo()|, including a |List| or |Dict|. + If {options} contains "a" or {regname} is upper case, + then the value is appended. + + {options} can also contain a register type specification: + "c" or "v" |charwise| mode + "l" or "V" |linewise| mode + "b" or "<CTRL-V>" |blockwise-visual| mode + If a number immediately follows "b" or "<CTRL-V>" then this is + used as the width of the selection - if it is not specified + then the width of the block is set to the number of characters + in the longest line (counting a <Tab> as 1 character). + If {options} contains "u" or '"', then the unnamed register is + set to point to register {regname}. + + If {options} contains no register settings, then the default + is to use character mode unless {value} ends in a <NL> for + string {value} and linewise mode for list {value}. Blockwise + mode is never selected automatically. + Returns zero for success, non-zero for failure. + + *E883* + Note: you may not use |List| containing more than one item to + set search and expression registers. Lists containing no + items act like empty strings. + + Examples: > + :call setreg(v:register, @*) + :call setreg('*', @%, 'ac') + :call setreg('a', "1\n2\n3", 'b5') + :call setreg('"', { 'points_to': 'a'}) + +< This example shows using the functions to save and restore a + register: > + :let var_a = getreginfo() + :call setreg('a', var_a) +< or: > + :let var_a = getreg('a', 1, 1) + :let var_amode = getregtype('a') + .... + :call setreg('a', var_a, var_amode) +< Note: you may not reliably restore register value + without using the third argument to |getreg()| as without it + newlines are represented as newlines AND Nul bytes are + represented as newlines as well, see |NL-used-for-Nul|. + + You can also change the type of a register by appending + nothing: > + :call setreg('a', '', 'al') + +< Can also be used as a |method|, the base is passed as the + second argument: > + GetText()->setreg('a') + +settabvar({tabnr}, {varname}, {val}) *settabvar()* + Set tab-local variable {varname} to {val} in tab page {tabnr}. + |t:var| + The {varname} argument is a string. + Note that the variable name without "t:" must be used. + Tabs are numbered starting with one. + This function is not available in the |sandbox|. + + Can also be used as a |method|, the base is passed as the + third argument: > + GetValue()->settabvar(tab, name) + +settabwinvar({tabnr}, {winnr}, {varname}, {val}) *settabwinvar()* + Set option or local variable {varname} in window {winnr} to + {val}. + Tabs are numbered starting with one. For the current tabpage + use |setwinvar()|. + {winnr} can be the window number or the |window-ID|. + When {winnr} is zero the current window is used. + This also works for a global or local buffer option, but it + doesn't work for a global or local buffer variable. + For a local buffer option the global value is unchanged. + Note that the variable name without "w:" must be used. + Examples: > + :call settabwinvar(1, 1, "&list", 0) + :call settabwinvar(3, 2, "myvar", "foobar") +< This function is not available in the |sandbox|. + + Can also be used as a |method|, the base is passed as the + fourth argument: > + GetValue()->settabwinvar(tab, winnr, name) + +settagstack({nr}, {dict} [, {action}]) *settagstack()* + Modify the tag stack of the window {nr} using {dict}. + {nr} can be the window number or the |window-ID|. + + For a list of supported items in {dict}, refer to + |gettagstack()|. "curidx" takes effect before changing the tag + stack. + *E962* + How the tag stack is modified depends on the {action} + argument: + - If {action} is not present or is set to 'r', then the tag + stack is replaced. + - If {action} is set to 'a', then new entries from {dict} are + pushed (added) onto the tag stack. + - If {action} is set to 't', then all the entries from the + current entry in the tag stack or "curidx" in {dict} are + removed and then new entries are pushed to the stack. + + The current index is set to one after the length of the tag + stack after the modification. + + Returns zero for success, -1 for failure. + + Examples (for more examples see |tagstack-examples|): + Empty the tag stack of window 3: > + call settagstack(3, {'items' : []}) + +< Save and restore the tag stack: > + let stack = gettagstack(1003) + " do something else + call settagstack(1003, stack) + unlet stack +< + Can also be used as a |method|, the base is passed as the + second argument: > + GetStack()->settagstack(winnr) + +setwinvar({nr}, {varname}, {val}) *setwinvar()* + Like |settabwinvar()| for the current tab page. + Examples: > + :call setwinvar(1, "&list", 0) + :call setwinvar(2, "myvar", "foobar") + +< Can also be used as a |method|, the base is passed as the + third argument: > + GetValue()->setwinvar(winnr, name) + +sha256({string}) *sha256()* + Returns a String with 64 hex characters, which is the SHA256 + checksum of {string}. + + Can also be used as a |method|: > + GetText()->sha256() + +shellescape({string} [, {special}]) *shellescape()* + Escape {string} for use as a shell command argument. + + On Windows when 'shellslash' is not set, encloses {string} in + double-quotes and doubles all double-quotes within {string}. + Otherwise encloses {string} in single-quotes and replaces all + "'" with "'\''". + + If {special} is a |non-zero-arg|: + - Special items such as "!", "%", "#" and "<cword>" will be + preceded by a backslash. The backslash will be removed again + by the |:!| command. + - The <NL> character is escaped. + + If 'shell' contains "csh" in the tail: + - The "!" character will be escaped. This is because csh and + tcsh use "!" for history replacement even in single-quotes. + - The <NL> character is escaped (twice if {special} is + a |non-zero-arg|). + + If 'shell' contains "fish" in the tail, the "\" character will + be escaped because in fish it is used as an escape character + inside single quotes. + + Example of use with a |:!| command: > + :exe '!dir ' .. shellescape(expand('<cfile>'), 1) +< This results in a directory listing for the file under the + cursor. Example of use with |system()|: > + :call system("chmod +w -- " .. shellescape(expand("%"))) +< See also |::S|. + + Can also be used as a |method|: > + GetCommand()->shellescape() + +shiftwidth([{col}]) *shiftwidth()* + Returns the effective value of 'shiftwidth'. This is the + 'shiftwidth' value unless it is zero, in which case it is the + 'tabstop' value. To be backwards compatible in indent + plugins, use this: > + if exists('*shiftwidth') + func s:sw() + return shiftwidth() + endfunc + else + func s:sw() + return &sw + endfunc + endif +< And then use s:sw() instead of &sw. + + When there is one argument {col} this is used as column number + for which to return the 'shiftwidth' value. This matters for the + 'vartabstop' feature. If no {col} argument is given, column 1 + will be assumed. + + Can also be used as a |method|: > + GetColumn()->shiftwidth() + +sign_ functions are documented here: |sign-functions-details| + +simplify({filename}) *simplify()* + Simplify the file name as much as possible without changing + the meaning. Shortcuts (on MS-Windows) or symbolic links (on + Unix) are not resolved. If the first path component in + {filename} designates the current directory, this will be + valid for the result as well. A trailing path separator is + not removed either. On Unix "//path" is unchanged, but + "///path" is simplified to "/path" (this follows the Posix + standard). + Example: > + simplify("./dir/.././/file/") == "./file/" +< Note: The combination "dir/.." is only removed if "dir" is + a searchable directory or does not exist. On Unix, it is also + removed when "dir" is a symbolic link within the same + directory. In order to resolve all the involved symbolic + links before simplifying the path name, use |resolve()|. + + Can also be used as a |method|: > + GetName()->simplify() + +sin({expr}) *sin()* + Return the sine of {expr}, measured in radians, as a |Float|. + {expr} must evaluate to a |Float| or a |Number|. + Examples: > + :echo sin(100) +< -0.506366 > + :echo sin(-4.01) +< 0.763301 + + Can also be used as a |method|: > + Compute()->sin() + +sinh({expr}) *sinh()* + Return the hyperbolic sine of {expr} as a |Float| in the range + [-inf, inf]. + {expr} must evaluate to a |Float| or a |Number|. + Examples: > + :echo sinh(0.5) +< 0.521095 > + :echo sinh(-0.9) +< -1.026517 + + Can also be used as a |method|: > + Compute()->sinh() + +sockconnect({mode}, {address} [, {opts}]) *sockconnect()* + Connect a socket to an address. If {mode} is "pipe" then + {address} should be the path of a named pipe. If {mode} is + "tcp" then {address} should be of the form "host:port" where + the host should be an ip adderess or host name, and port the + port number. + + Returns a |channel| ID. Close the socket with |chanclose()|. + Use |chansend()| to send data over a bytes socket, and + |rpcrequest()| and |rpcnotify()| to communicate with a RPC + socket. + + {opts} is an optional dictionary with these keys: + |on_data| : callback invoked when data was read from socket + data_buffered : read socket data in |channel-buffered| mode. + rpc : If set, |msgpack-rpc| will be used to communicate + over the socket. + Returns: + - The channel ID on success (greater than zero) + - 0 on invalid arguments or connection failure. + +sort({list} [, {func} [, {dict}]]) *sort()* *E702* + Sort the items in {list} in-place. Returns {list}. + + If you want a list to remain unmodified make a copy first: > + :let sortedlist = sort(copy(mylist)) + +< When {func} is omitted, is empty or zero, then sort() uses the + string representation of each item to sort on. Numbers sort + after Strings, |Lists| after Numbers. For sorting text in the + current buffer use |:sort|. + + When {func} is given and it is '1' or 'i' then case is + ignored. + + When {func} is given and it is 'l' then the current collation + locale is used for ordering. Implementation details: strcoll() + is used to compare strings. See |:language| check or set the + collation locale. |v:collate| can also be used to check the + current locale. Sorting using the locale typically ignores + case. Example: > + " ö is sorted similarly to o with English locale. + :language collate en_US.UTF8 + :echo sort(['n', 'o', 'O', 'ö', 'p', 'z'], 'l') +< ['n', 'o', 'O', 'ö', 'p', 'z'] ~ +> + " ö is sorted after z with Swedish locale. + :language collate sv_SE.UTF8 + :echo sort(['n', 'o', 'O', 'ö', 'p', 'z'], 'l') +< ['n', 'o', 'O', 'p', 'z', 'ö'] ~ + This does not work properly on Mac. + + When {func} is given and it is 'n' then all items will be + sorted numerical (Implementation detail: this uses the + strtod() function to parse numbers, Strings, Lists, Dicts and + Funcrefs will be considered as being 0). + + When {func} is given and it is 'N' then all items will be + sorted numerical. This is like 'n' but a string containing + digits will be used as the number they represent. + + When {func} is given and it is 'f' then all items will be + sorted numerical. All values must be a Number or a Float. + + When {func} is a |Funcref| or a function name, this function + is called to compare items. The function is invoked with two + items as argument and must return zero if they are equal, 1 or + bigger if the first one sorts after the second one, -1 or + smaller if the first one sorts before the second one. + + {dict} is for functions with the "dict" attribute. It will be + used to set the local variable "self". |Dictionary-function| + + The sort is stable, items which compare equal (as number or as + string) will keep their relative position. E.g., when sorting + on numbers, text strings will sort next to each other, in the + same order as they were originally. + + Can also be used as a |method|: > + mylist->sort() + +< Also see |uniq()|. + + Example: > + func MyCompare(i1, i2) + return a:i1 == a:i2 ? 0 : a:i1 > a:i2 ? 1 : -1 + endfunc + eval mylist->sort("MyCompare") +< A shorter compare version for this specific simple case, which + ignores overflow: > + func MyCompare(i1, i2) + return a:i1 - a:i2 + endfunc +< For a simple expression you can use a lambda: > + eval mylist->sort({i1, i2 -> i1 - i2}) +< + *soundfold()* +soundfold({word}) + Return the sound-folded equivalent of {word}. Uses the first + language in 'spelllang' for the current window that supports + soundfolding. 'spell' must be set. When no sound folding is + possible the {word} is returned unmodified. + This can be used for making spelling suggestions. Note that + the method can be quite slow. + + Can also be used as a |method|: > + GetWord()->soundfold() +< + *spellbadword()* +spellbadword([{sentence}]) + Without argument: The result is the badly spelled word under + or after the cursor. The cursor is moved to the start of the + bad word. When no bad word is found in the cursor line the + result is an empty string and the cursor doesn't move. + + With argument: The result is the first word in {sentence} that + is badly spelled. If there are no spelling mistakes the + result is an empty string. + + The return value is a list with two items: + - The badly spelled word or an empty string. + - The type of the spelling error: + "bad" spelling mistake + "rare" rare word + "local" word only valid in another region + "caps" word should start with Capital + Example: > + echo spellbadword("the quik brown fox") +< ['quik', 'bad'] ~ + + The spelling information for the current window and the value + of 'spelllang' are used. + + Can also be used as a |method|: > + GetText()->spellbadword() +< + *spellsuggest()* +spellsuggest({word} [, {max} [, {capital}]]) + Return a |List| with spelling suggestions to replace {word}. + When {max} is given up to this number of suggestions are + returned. Otherwise up to 25 suggestions are returned. + + When the {capital} argument is given and it's non-zero only + suggestions with a leading capital will be given. Use this + after a match with 'spellcapcheck'. + + {word} can be a badly spelled word followed by other text. + This allows for joining two words that were split. The + suggestions also include the following text, thus you can + replace a line. + + {word} may also be a good word. Similar words will then be + returned. {word} itself is not included in the suggestions, + although it may appear capitalized. + + The spelling information for the current window is used. The + values of 'spelllang' and 'spellsuggest' are used. + + Can also be used as a |method|: > + GetWord()->spellsuggest() + +split({string} [, {pattern} [, {keepempty}]]) *split()* + Make a |List| out of {string}. When {pattern} is omitted or + empty each white-separated sequence of characters becomes an + item. + Otherwise the string is split where {pattern} matches, + removing the matched characters. 'ignorecase' is not used + here, add \c to ignore case. |/\c| + When the first or last item is empty it is omitted, unless the + {keepempty} argument is given and it's non-zero. + Other empty items are kept when {pattern} matches at least one + character or when {keepempty} is non-zero. + Example: > + :let words = split(getline('.'), '\W\+') +< To split a string in individual characters: > + :for c in split(mystring, '\zs') +< If you want to keep the separator you can also use '\zs' at + the end of the pattern: > + :echo split('abc:def:ghi', ':\zs') +< ['abc:', 'def:', 'ghi'] ~ + Splitting a table where the first element can be empty: > + :let items = split(line, ':', 1) +< The opposite function is |join()|. + + Can also be used as a |method|: > + GetString()->split() + +sqrt({expr}) *sqrt()* + Return the non-negative square root of Float {expr} as a + |Float|. + {expr} must evaluate to a |Float| or a |Number|. When {expr} + is negative the result is NaN (Not a Number). + Examples: > + :echo sqrt(100) +< 10.0 > + :echo sqrt(-4.01) +< nan + "nan" may be different, it depends on system libraries. + + Can also be used as a |method|: > + Compute()->sqrt() + +srand([{expr}]) *srand()* + Initialize seed used by |rand()|: + - If {expr} is not given, seed values are initialized by + reading from /dev/urandom, if possible, or using time(NULL) + a.k.a. epoch time otherwise; this only has second accuracy. + - If {expr} is given it must be a Number. It is used to + initialize the seed values. This is useful for testing or + when a predictable sequence is intended. + + Examples: > + :let seed = srand() + :let seed = srand(userinput) + :echo rand(seed) +< + Can also be used as a |method|: > + userinput->srand() + +stdioopen({opts}) *stdioopen()* + With |--headless| this opens stdin and stdout as a |channel|. + May be called only once. See |channel-stdio|. stderr is not + handled by this function, see |v:stderr|. + + Close the stdio handles with |chanclose()|. Use |chansend()| + to send data to stdout, and |rpcrequest()| and |rpcnotify()| + to communicate over RPC. + + {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 + Returns: + - |channel-id| on success (value is always 1) + - 0 on invalid arguments + + +stdpath({what}) *stdpath()* *E6100* + Returns |standard-path| locations of various default files and + directories. + + {what} Type Description ~ + cache String Cache directory. Arbitrary temporary + storage for plugins, etc. + config String User configuration directory. The + |init.vim| is stored here. + config_dirs List Additional configuration directories. + data String User data directory. The |shada-file| + is stored here. + data_dirs List Additional data directories. + + Example: > + :echo stdpath("config") + + +str2float({string} [, {quoted}]) *str2float()* + Convert String {string} to a Float. This mostly works the + same as when using a floating point number in an expression, + see |floating-point-format|. But it's a bit more permissive. + E.g., "1e40" is accepted, while in an expression you need to + write "1.0e40". The hexadecimal form "0x123" is also + accepted, but not others, like binary or octal. + When {quoted} is present and non-zero then embedded single + quotes before the dot are ignored, thus "1'000.0" is a + thousand. + Text after the number is silently ignored. + The decimal point is always '.', no matter what the locale is + set to. A comma ends the number: "12,345.67" is converted to + 12.0. You can strip out thousands separators with + |substitute()|: > + let f = str2float(substitute(text, ',', '', 'g')) +< + Can also be used as a |method|: > + let f = text->substitute(',', '', 'g')->str2float() + +str2list({string} [, {utf8}]) *str2list()* + Return a list containing the number values which represent + each character in String {string}. Examples: > + str2list(" ") returns [32] + str2list("ABC") returns [65, 66, 67] +< |list2str()| does the opposite. + + UTF-8 encoding is always used, {utf8} option has no effect, + and exists only for backwards-compatibility. + With UTF-8 composing characters are handled properly: > + str2list("á") returns [97, 769] + +< Can also be used as a |method|: > + GetString()->str2list() + +str2nr({string} [, {base}]) *str2nr()* + Convert string {string} to a number. + {base} is the conversion base, it can be 2, 8, 10 or 16. + When {quoted} is present and non-zero then embedded single + quotes are ignored, thus "1'000'000" is a million. + + When {base} is omitted base 10 is used. This also means that + a leading zero doesn't cause octal conversion to be used, as + with the default String to Number conversion. Example: > + let nr = str2nr('0123') +< + When {base} is 16 a leading "0x" or "0X" is ignored. With a + different base the result will be zero. Similarly, when + {base} is 8 a leading "0", "0o" or "0O" is ignored, and when + {base} is 2 a leading "0b" or "0B" is ignored. + Text after the number is silently ignored. + + Can also be used as a |method|: > + GetText()->str2nr() + +strcharpart({src}, {start} [, {len}]) *strcharpart()* + Like |strpart()| but using character index and length instead + of byte index and length. Composing characters are counted + separately. + When a character index is used where a character does not + exist it is assumed to be one character. For example: > + strcharpart('abc', -1, 2) +< results in 'a'. + + Can also be used as a |method|: > + GetText()->strcharpart(5) + +strchars({string} [, {skipcc}]) *strchars()* + The result is a Number, which is the number of characters + in String {string}. + When {skipcc} is omitted or zero, composing characters are + counted separately. + When {skipcc} set to 1, Composing characters are ignored. + Also see |strlen()|, |strdisplaywidth()| and |strwidth()|. + + {skipcc} is only available after 7.4.755. For backward + compatibility, you can define a wrapper function: > + if has("patch-7.4.755") + function s:strchars(str, skipcc) + return strchars(a:str, a:skipcc) + endfunction + else + function s:strchars(str, skipcc) + if a:skipcc + return strlen(substitute(a:str, ".", "x", "g")) + else + return strchars(a:str) + endif + endfunction + endif +< + Can also be used as a |method|: > + GetText()->strchars() + +strdisplaywidth({string} [, {col}]) *strdisplaywidth()* + The result is a Number, which is the number of display cells + String {string} occupies on the screen when it starts at {col} + (first column is zero). When {col} is omitted zero is used. + Otherwise it is the screen column where to start. This + matters for Tab characters. + The option settings of the current window are used. This + matters for anything that's displayed differently, such as + 'tabstop' and 'display'. + When {string} contains characters with East Asian Width Class + Ambiguous, this function's return value depends on 'ambiwidth'. + Also see |strlen()|, |strwidth()| and |strchars()|. + + Can also be used as a |method|: > + GetText()->strdisplaywidth() + +strftime({format} [, {time}]) *strftime()* + The result is a String, which is a formatted date and time, as + specified by the {format} string. The given {time} is used, + or the current time if no time is given. The accepted + {format} depends on your system, thus this is not portable! + See the manual page of the C function strftime() for the + format. The maximum length of the result is 80 characters. + See also |localtime()|, |getftime()| and |strptime()|. + The language can be changed with the |:language| command. + Examples: > + :echo strftime("%c") Sun Apr 27 11:49:23 1997 + :echo strftime("%Y %b %d %X") 1997 Apr 27 11:53:25 + :echo strftime("%y%m%d %T") 970427 11:53:55 + :echo strftime("%H:%M") 11:55 + :echo strftime("%c", getftime("file.c")) + Show mod time of file.c. + +< Can also be used as a |method|: > + GetFormat()->strftime() + +strgetchar({str}, {index}) *strgetchar()* + Get character {index} from {str}. This uses a character + index, not a byte index. Composing characters are considered + separate characters here. + Also see |strcharpart()| and |strchars()|. + + Can also be used as a |method|: > + GetText()->strgetchar(5) + +stridx({haystack}, {needle} [, {start}]) *stridx()* + The result is a Number, which gives the byte index in + {haystack} of the first occurrence of the String {needle}. + If {start} is specified, the search starts at index {start}. + This can be used to find a second match: > + :let colon1 = stridx(line, ":") + :let colon2 = stridx(line, ":", colon1 + 1) +< The search is done case-sensitive. + For pattern searches use |match()|. + -1 is returned if the {needle} does not occur in {haystack}. + See also |strridx()|. + Examples: > + :echo stridx("An Example", "Example") 3 + :echo stridx("Starting point", "Start") 0 + :echo stridx("Starting point", "start") -1 +< *strstr()* *strchr()* + stridx() works similar to the C function strstr(). When used + with a single character it works similar to strchr(). + + Can also be used as a |method|: > + GetHaystack()->stridx(needle) + + *string()* +string({expr}) Return {expr} converted to a String. If {expr} is a Number, + Float, String, Blob or a composition of them, then the result + can be parsed back with |eval()|. + {expr} type result ~ + String 'string' + Number 123 + Float 123.123456 or 1.123456e8 or + `str2float('inf')` + Funcref `function('name')` + Blob 0z00112233.44556677.8899 + List [item, item] + Dictionary {key: value, key: value} + Note that in String values the ' character is doubled. + Also see |strtrans()|. + Note 2: Output format is mostly compatible with YAML, except + for infinite and NaN floating-point values representations + which use |str2float()|. Strings are also dumped literally, + only single quote is escaped, which does not allow using YAML + for parsing back binary strings. |eval()| should always work for + strings and floats though and this is the only official + method, use |msgpackdump()| or |json_encode()| if you need to + share data with other application. + + Can also be used as a |method|: > + mylist->string() + +strlen({string}) *strlen()* + The result is a Number, which is the length of the String + {string} in bytes. + If the argument is a Number it is first converted to a String. + For other types an error is given. + If you want to count the number of multibyte characters use + |strchars()|. + Also see |len()|, |strdisplaywidth()| and |strwidth()|. + + Can also be used as a |method|: > + GetString()->strlen() + +strpart({src}, {start} [, {len} [, {chars}]]) *strpart()* + The result is a String, which is part of {src}, starting from + byte {start}, with the byte length {len}. + When {chars} is present and TRUE then {len} is the number of + characters positions (composing characters are not counted + separately, thus "1" means one base character and any + following composing characters). + To count {start} as characters instead of bytes use + |strcharpart()|. + + When bytes are selected which do not exist, this doesn't + result in an error, the bytes are simply omitted. + If {len} is missing, the copy continues from {start} till the + end of the {src}. > + strpart("abcdefg", 3, 2) == "de" + strpart("abcdefg", -2, 4) == "ab" + strpart("abcdefg", 5, 4) == "fg" + strpart("abcdefg", 3) == "defg" + +< Note: To get the first character, {start} must be 0. For + example, to get the character under the cursor: > + strpart(getline("."), col(".") - 1, 1, v:true) +< + Can also be used as a |method|: > + GetText()->strpart(5) + +strptime({format}, {timestring}) *strptime()* + The result is a Number, which is a unix timestamp representing + the date and time in {timestring}, which is expected to match + the format specified in {format}. + + The accepted {format} depends on your system, thus this is not + portable! See the manual page of the C function strptime() + for the format. Especially avoid "%c". The value of $TZ also + matters. + + If the {timestring} cannot be parsed with {format} zero is + returned. If you do not know the format of {timestring} you + can try different {format} values until you get a non-zero + result. + + See also |strftime()|. + Examples: > + :echo strptime("%Y %b %d %X", "1997 Apr 27 11:49:23") +< 862156163 > + :echo strftime("%c", strptime("%y%m%d %T", "970427 11:53:55")) +< Sun Apr 27 11:53:55 1997 > + :echo strftime("%c", strptime("%Y%m%d%H%M%S", "19970427115355") + 3600) +< Sun Apr 27 12:53:55 1997 + + Can also be used as a |method|: > + GetFormat()->strptime(timestring) +< +strridx({haystack}, {needle} [, {start}]) *strridx()* + The result is a Number, which gives the byte index in + {haystack} of the last occurrence of the String {needle}. + When {start} is specified, matches beyond this index are + ignored. This can be used to find a match before a previous + match: > + :let lastcomma = strridx(line, ",") + :let comma2 = strridx(line, ",", lastcomma - 1) +< The search is done case-sensitive. + For pattern searches use |match()|. + -1 is returned if the {needle} does not occur in {haystack}. + If the {needle} is empty the length of {haystack} is returned. + See also |stridx()|. Examples: > + :echo strridx("an angry armadillo", "an") 3 +< *strrchr()* + When used with a single character it works similar to the C + function strrchr(). + + Can also be used as a |method|: > + GetHaystack()->strridx(needle) + +strtrans({string}) *strtrans()* + The result is a String, which is {string} with all unprintable + characters translated into printable characters |'isprint'|. + Like they are shown in a window. Example: > + echo strtrans(@a) +< This displays a newline in register a as "^@" instead of + starting a new line. + + Can also be used as a |method|: > + GetString()->strtrans() + +strwidth({string}) *strwidth()* + The result is a Number, which is the number of display cells + String {string} occupies. A Tab character is counted as one + cell, alternatively use |strdisplaywidth()|. + When {string} contains characters with East Asian Width Class + Ambiguous, this function's return value depends on 'ambiwidth'. + Also see |strlen()|, |strdisplaywidth()| and |strchars()|. + + Can also be used as a |method|: > + GetString()->strwidth() + +submatch({nr} [, {list}]) *submatch()* *E935* + Only for an expression in a |:substitute| command or + substitute() function. + Returns the {nr}'th submatch of the matched text. When {nr} + is 0 the whole matched text is returned. + Note that a NL in the string can stand for a line break of a + multi-line match or a NUL character in the text. + Also see |sub-replace-expression|. + + If {list} is present and non-zero then submatch() returns + a list of strings, similar to |getline()| with two arguments. + NL characters in the text represent NUL characters in the + text. + Only returns more than one item for |:substitute|, inside + |substitute()| this list will always contain one or zero + items, since there are no real line breaks. + + When substitute() is used recursively only the submatches in + the current (deepest) call can be obtained. + + Examples: > + :s/\d\+/\=submatch(0) + 1/ + :echo substitute(text, '\d\+', '\=submatch(0) + 1', '') +< This finds the first number in the line and adds one to it. + A line break is included as a newline character. + + Can also be used as a |method|: > + GetNr()->submatch() + +substitute({string}, {pat}, {sub}, {flags}) *substitute()* + The result is a String, which is a copy of {string}, in which + the first match of {pat} is replaced with {sub}. + When {flags} is "g", all matches of {pat} in {string} are + replaced. Otherwise {flags} should be "". + + This works like the ":substitute" command (without any flags). + But the matching with {pat} is always done like the 'magic' + option is set and 'cpoptions' is empty (to make scripts + portable). 'ignorecase' is still relevant, use |/\c| or |/\C| + if you want to ignore or match case and ignore 'ignorecase'. + 'smartcase' is not used. See |string-match| for how {pat} is + used. + + A "~" in {sub} is not replaced with the previous {sub}. + Note that some codes in {sub} have a special meaning + |sub-replace-special|. For example, to replace something with + "\n" (two characters), use "\\\\n" or '\\n'. + + When {pat} does not match in {string}, {string} is returned + unmodified. + + Example: > + :let &path = substitute(&path, ",\\=[^,]*$", "", "") +< This removes the last component of the 'path' option. > + :echo substitute("testing", ".*", "\\U\\0", "") +< results in "TESTING". + + When {sub} starts with "\=", the remainder is interpreted as + an expression. See |sub-replace-expression|. Example: > + :echo substitute(s, '%\(\x\x\)', + \ '\=nr2char("0x" .. submatch(1))', 'g') + +< When {sub} is a Funcref that function is called, with one + optional argument. Example: > + :echo substitute(s, '%\(\x\x\)', SubNr, 'g') +< The optional argument is a list which contains the whole + matched string and up to nine submatches, like what + |submatch()| returns. Example: > + :echo substitute(s, '%\(\x\x\)', {m -> '0x' .. m[1]}, 'g') + +< Can also be used as a |method|: > + GetString()->substitute(pat, sub, flags) + +swapinfo({fname}) *swapinfo()* + The result is a dictionary, which holds information about the + swapfile {fname}. The available fields are: + version Vim version + user user name + host host name + fname original file name + pid PID of the Vim process that created the swap + file + mtime last modification time in seconds + inode Optional: INODE number of the file + dirty 1 if file was modified, 0 if not + In case of failure an "error" item is added with the reason: + Cannot open file: file not found or in accessible + Cannot read file: cannot read first block + Not a swap file: does not contain correct block ID + Magic number mismatch: Info in first block is invalid + + Can also be used as a |method|: > + GetFilename()->swapinfo() + +swapname({buf}) *swapname()* + The result is the swap file path of the buffer {buf}. + For the use of {buf}, see |bufname()| above. + If buffer {buf} is the current buffer, the result is equal to + |:swapname| (unless there is no swap file). + If buffer {buf} has no swap file, returns an empty string. + + Can also be used as a |method|: > + GetBufname()->swapname() + +synID({lnum}, {col}, {trans}) *synID()* + The result is a Number, which is the syntax ID at the position + {lnum} and {col} in the current window. + The syntax ID can be used with |synIDattr()| and + |synIDtrans()| to obtain syntax information about text. + + {col} is 1 for the leftmost column, {lnum} is 1 for the first + line. 'synmaxcol' applies, in a longer line zero is returned. + Note that when the position is after the last character, + that's where the cursor can be in Insert mode, synID() returns + zero. {lnum} is used like with |getline()|. + + When {trans} is |TRUE|, transparent items are reduced to the + item that they reveal. This is useful when wanting to know + the effective color. When {trans} is |FALSE|, the transparent + item is returned. This is useful when wanting to know which + syntax item is effective (e.g. inside parens). + Warning: This function can be very slow. Best speed is + obtained by going through the file in forward direction. + + Example (echoes the name of the syntax item under the cursor): > + :echo synIDattr(synID(line("."), col("."), 1), "name") +< + +synIDattr({synID}, {what} [, {mode}]) *synIDattr()* + The result is a String, which is the {what} attribute of + syntax ID {synID}. This can be used to obtain information + about a syntax item. + {mode} can be "gui", "cterm" or "term", to get the attributes + for that mode. When {mode} is omitted, or an invalid value is + used, the attributes for the currently active highlighting are + used (GUI, cterm or term). + Use synIDtrans() to follow linked highlight groups. + {what} result + "name" the name of the syntax item + "fg" foreground color (GUI: color name used to set + the color, cterm: color number as a string, + term: empty string) + "bg" background color (as with "fg") + "font" font name (only available in the GUI) + |highlight-font| + "sp" special color (as with "fg") |highlight-guisp| + "fg#" like "fg", but for the GUI and the GUI is + running the name in "#RRGGBB" form + "bg#" like "fg#" for "bg" + "sp#" like "fg#" for "sp" + "bold" "1" if bold + "italic" "1" if italic + "reverse" "1" if reverse + "inverse" "1" if inverse (= reverse) + "standout" "1" if standout + "underline" "1" if underlined + "undercurl" "1" if undercurled + "strikethrough" "1" if struckthrough + + Example (echoes the color of the syntax item under the + cursor): > + :echo synIDattr(synIDtrans(synID(line("."), col("."), 1)), "fg") +< + Can also be used as a |method|: > + :echo synID(line("."), col("."), 1)->synIDtrans()->synIDattr("fg") + +synIDtrans({synID}) *synIDtrans()* + The result is a Number, which is the translated syntax ID of + {synID}. This is the syntax group ID of what is being used to + highlight the character. Highlight links given with + ":highlight link" are followed. + + Can also be used as a |method|: > + :echo synID(line("."), col("."), 1)->synIDtrans()->synIDattr("fg") + +synconcealed({lnum}, {col}) *synconcealed()* + The result is a |List| with currently three items: + 1. The first item in the list is 0 if the character at the + position {lnum} and {col} is not part of a concealable + region, 1 if it is. {lnum} is used like with |getline()|. + 2. The second item in the list is a string. If the first item + is 1, the second item contains the text which will be + displayed in place of the concealed text, depending on the + current setting of 'conceallevel' and 'listchars'. + 3. The third and final item in the list is a number + representing the specific syntax region matched in the + line. When the character is not concealed the value is + zero. This allows detection of the beginning of a new + concealable region if there are two consecutive regions + with the same replacement character. For an example, if + the text is "123456" and both "23" and "45" are concealed + and replaced by the character "X", then: + call returns ~ + synconcealed(lnum, 1) [0, '', 0] + synconcealed(lnum, 2) [1, 'X', 1] + synconcealed(lnum, 3) [1, 'X', 1] + synconcealed(lnum, 4) [1, 'X', 2] + synconcealed(lnum, 5) [1, 'X', 2] + synconcealed(lnum, 6) [0, '', 0] + + +synstack({lnum}, {col}) *synstack()* + Return a |List|, which is the stack of syntax items at the + position {lnum} and {col} in the current window. {lnum} is + used like with |getline()|. Each item in the List is an ID + like what |synID()| returns. + The first item in the List is the outer region, following are + items contained in that one. The last one is what |synID()| + returns, unless not the whole item is highlighted or it is a + transparent item. + This function is useful for debugging a syntax file. + Example that shows the syntax stack under the cursor: > + for id in synstack(line("."), col(".")) + echo synIDattr(id, "name") + endfor +< When the position specified with {lnum} and {col} is invalid + nothing is returned. The position just after the last + character in a line and the first column in an empty line are + valid positions. + +system({cmd} [, {input}]) *system()* *E677* + Gets the output of {cmd} as a |string| (|systemlist()| returns + a |List|) and sets |v:shell_error| to the error code. + {cmd} is treated as in |jobstart()|: + If {cmd} is a List it runs directly (no 'shell'). + If {cmd} is a String it runs in the 'shell', like this: > + :call jobstart(split(&shell) + split(&shellcmdflag) + ['{cmd}']) + +< Not to be used for interactive commands. + + Result is a String, filtered to avoid platform-specific quirks: + - <CR><NL> is replaced with <NL> + - NUL characters are replaced with SOH (0x01) + + Example: > + :echo system(['ls', expand('%:h')]) + +< If {input} is a string it is written to a pipe and passed as + stdin to the command. The string is written as-is, line + separators are not changed. + If {input} is a |List| it is written to the pipe as + |writefile()| does with {binary} set to "b" (i.e. with + a newline between each list item, and newlines inside list + items converted to NULs). + When {input} is given and is a valid buffer id, the content of + the buffer is written to the file line by line, each line + terminated by NL (and NUL where the text has NL). + *E5677* + Note: system() cannot write to or read from backgrounded ("&") + shell commands, e.g.: > + :echo system("cat - &", "foo") +< which is equivalent to: > + $ echo foo | bash -c 'cat - &' +< The pipes are disconnected (unless overridden by shell + redirection syntax) before input can reach it. Use + |jobstart()| instead. + + Note: Use |shellescape()| or |::S| with |expand()| or + |fnamemodify()| to escape special characters in a command + argument. 'shellquote' and 'shellxquote' must be properly + configured. Example: > + :echo system('ls '..shellescape(expand('%:h'))) + :echo system('ls '..expand('%:h:S')) + +< Unlike ":!cmd" there is no automatic check for changed files. + Use |:checktime| to force a check. + + Can also be used as a |method|: > + :echo GetCmd()->system() + +systemlist({cmd} [, {input} [, {keepempty}]]) *systemlist()* + Same as |system()|, but returns a |List| with lines (parts of + output separated by NL) with NULs transformed into NLs. Output + is the same as |readfile()| will output with {binary} argument + set to "b", except that a final newline is not preserved, + unless {keepempty} is non-zero. + Note that on MS-Windows you may get trailing CR characters. + + To see the difference between "echo hello" and "echo -n hello" + use |system()| and |split()|: > + echo split(system('echo hello'), '\n', 1) +< + Returns an empty string on error. + + Can also be used as a |method|: > + :echo GetCmd()->systemlist() + +tabpagebuflist([{arg}]) *tabpagebuflist()* + The result is a |List|, where each item is the number of the + buffer associated with each window in the current tab page. + {arg} specifies the number of the tab page to be used. When + omitted the current tab page is used. + When {arg} is invalid the number zero is returned. + To get a list of all buffers in all tabs use this: > + let buflist = [] + for i in range(tabpagenr('$')) + call extend(buflist, tabpagebuflist(i + 1)) + endfor +< Note that a buffer may appear in more than one window. + + Can also be used as a |method|: > + GetTabpage()->tabpagebuflist() + +tabpagenr([{arg}]) *tabpagenr()* + The result is a Number, which is the number of the current + tab page. The first tab page has number 1. + The optional argument {arg} supports the following values: + $ the number of the last tab page (the tab page + count). + # the number of the last accessed tab page (where + |g<Tab>| goes to). If there is no previous + tab page, 0 is returned. + The number can be used with the |:tab| command. + + +tabpagewinnr({tabarg} [, {arg}]) *tabpagewinnr()* + Like |winnr()| but for tab page {tabarg}. + {tabarg} specifies the number of tab page to be used. + {arg} is used like with |winnr()|: + - When omitted the current window number is returned. This is + the window which will be used when going to this tab page. + - When "$" the number of windows is returned. + - When "#" the previous window nr is returned. + Useful examples: > + tabpagewinnr(1) " current window of tab page 1 + tabpagewinnr(4, '$') " number of windows in tab page 4 +< When {tabarg} is invalid zero is returned. + + Can also be used as a |method|: > + GetTabpage()->tabpagewinnr() +< + *tagfiles()* +tagfiles() Returns a |List| with the file names used to search for tags + for the current buffer. This is the 'tags' option expanded. + + +taglist({expr} [, {filename}]) *taglist()* + Returns a |List| of tags matching the regular expression {expr}. + + If {filename} is passed it is used to prioritize the results + in the same way that |:tselect| does. See |tag-priority|. + {filename} should be the full path of the file. + + Each list item is a dictionary with at least the following + entries: + name Name of the tag. + filename Name of the file where the tag is + defined. It is either relative to the + current directory or a full path. + cmd Ex command used to locate the tag in + the file. + kind Type of the tag. The value for this + entry depends on the language specific + kind values. Only available when + using a tags file generated by + Exuberant ctags or hdrtag. + static A file specific tag. Refer to + |static-tag| for more information. + More entries may be present, depending on the content of the + tags file: access, implementation, inherits and signature. + Refer to the ctags documentation for information about these + fields. For C code the fields "struct", "class" and "enum" + may appear, they give the name of the entity the tag is + contained in. + + The ex-command "cmd" can be either an ex search pattern, a + line number or a line number followed by a byte number. + + If there are no matching tags, then an empty list is returned. + + To get an exact tag match, the anchors '^' and '$' should be + used in {expr}. This also make the function work faster. + Refer to |tag-regexp| for more information about the tag + search regular expression pattern. + + Refer to |'tags'| for information about how the tags file is + located by Vim. Refer to |tags-file-format| for the format of + the tags file generated by the different ctags tools. + + Can also be used as a |method|: > + GetTagpattern()->taglist() + +tempname() *tempname()* *temp-file-name* + The result is a String, which is the name of a file that + doesn't exist. It can be used for a temporary file. Example: > + :let tmpfile = tempname() + :exe "redir > " .. tmpfile +< For Unix, the file will be in a private directory |tempfile|. + For MS-Windows forward slashes are used when the 'shellslash' + option is set or when 'shellcmdflag' starts with '-'. + +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 + modified (all buffer contents are destroyed). + + The {opts} dict is similar to the one passed to |jobstart()|, + but the `pty`, `width`, `height`, and `TERM` fields are + ignored: `height`/`width` are taken from the current window + and `$TERM` is set to "xterm-256color". + Returns the same values as |jobstart()|. + + See |terminal| for more information. + +test_ functions are documented here: |test-functions-details| + +tan({expr}) *tan()* + Return the tangent of {expr}, measured in radians, as a |Float| + in the range [-inf, inf]. + {expr} must evaluate to a |Float| or a |Number|. + Examples: > + :echo tan(10) +< 0.648361 > + :echo tan(-4.01) +< -1.181502 + + Can also be used as a |method|: > + Compute()->tan() + +tanh({expr}) *tanh()* + Return the hyperbolic tangent of {expr} as a |Float| in the + range [-1, 1]. + {expr} must evaluate to a |Float| or a |Number|. + Examples: > + :echo tanh(0.5) +< 0.462117 > + :echo tanh(-1) +< -0.761594 + + Can also be used as a |method|: > + Compute()->tanh() +< + *timer_info()* +timer_info([{id}]) + Return a list with information about timers. + When {id} is given only information about this timer is + returned. When timer {id} does not exist an empty list is + returned. + When {id} is omitted information about all timers is returned. + + For each timer the information is stored in a |Dictionary| with + these items: + "id" the timer ID + "time" time the timer was started with + "repeat" number of times the timer will still fire; + -1 means forever + "callback" the callback + + Can also be used as a |method|: > + GetTimer()->timer_info() +< +timer_pause({timer}, {paused}) *timer_pause()* + Pause or unpause a timer. A paused timer does not invoke its + callback when its time expires. Unpausing a timer may cause + the callback to be invoked almost immediately if enough time + has passed. + + Pausing a timer is useful to avoid the callback to be called + for a short time. + + If {paused} evaluates to a non-zero Number or a non-empty + String, then the timer is paused, otherwise it is unpaused. + See |non-zero-arg|. + + Can also be used as a |method|: > + GetTimer()->timer_pause(1) +< + *timer_start()* *timer* *timers* +timer_start({time}, {callback} [, {options}]) + Create a timer and return the timer ID. + + {time} is the waiting time in milliseconds. This is the + minimum time before invoking the callback. When the system is + busy or Vim is not waiting for input the time will be longer. + + {callback} is the function to call. It can be the name of a + function or a |Funcref|. It is called with one argument, which + is the timer ID. The callback is only invoked when Vim is + waiting for input. + + {options} is a dictionary. Supported entries: + "repeat" Number of times to repeat the callback. + -1 means forever. Default is 1. + If the timer causes an error three times in a + row the repeat is cancelled. + + Example: > + func MyHandler(timer) + echo 'Handler called' + endfunc + let timer = timer_start(500, 'MyHandler', + \ {'repeat': 3}) +< This invokes MyHandler() three times at 500 msec intervals. + + Can also be used as a |method|: > + GetMsec()->timer_start(callback) + +< Not available in the |sandbox|. + +timer_stop({timer}) *timer_stop()* + Stop a timer. The timer callback will no longer be invoked. + {timer} is an ID returned by timer_start(), thus it must be a + Number. If {timer} does not exist there is no error. + + Can also be used as a |method|: > + GetTimer()->timer_stop() +< +timer_stopall() *timer_stopall()* + Stop all timers. The timer callbacks will no longer be + invoked. Useful if some timers is misbehaving. If there are + no timers there is no error. + +tolower({expr}) *tolower()* + The result is a copy of the String given, with all uppercase + characters turned into lowercase (just like applying |gu| to + the string). + + Can also be used as a |method|: > + GetText()->tolower() + +toupper({expr}) *toupper()* + The result is a copy of the String given, with all lowercase + characters turned into uppercase (just like applying |gU| to + the string). + + Can also be used as a |method|: > + GetText()->toupper() + +tr({src}, {fromstr}, {tostr}) *tr()* + The result is a copy of the {src} string with all characters + which appear in {fromstr} replaced by the character in that + position in the {tostr} string. Thus the first character in + {fromstr} is translated into the first character in {tostr} + and so on. Exactly like the unix "tr" command. + This code also deals with multibyte characters properly. + + Examples: > + echo tr("hello there", "ht", "HT") +< returns "Hello THere" > + echo tr("<blob>", "<>", "{}") +< returns "{blob}" + + Can also be used as a |method|: > + GetText()->tr(from, to) + +trim({text} [, {mask} [, {dir}]]) *trim()* + Return {text} as a String where any character in {mask} is + removed from the beginning and/or end of {text}. + If {mask} is not given, {mask} is all characters up to 0x20, + which includes Tab, space, NL and CR, plus the non-breaking + space character 0xa0. + The optional {dir} argument specifies where to remove the + characters: + 0 remove from the beginning and end of {text} + 1 remove only at the beginning of {text} + 2 remove only at the end of {text} + When omitted both ends are trimmed. + This function deals with multibyte characters properly. + Examples: > + echo trim(" some text ") +< returns "some text" > + echo trim(" \r\t\t\r RESERVE \t\n\x0B\xA0") .. "_TAIL" +< returns "RESERVE_TAIL" > + echo trim("rm<Xrm<>X>rrm", "rm<>") +< returns "Xrm<>X" (characters in the middle are not removed) > + echo trim(" vim ", " ", 2) +< returns " vim" + + Can also be used as a |method|: > + GetText()->trim() + +trunc({expr}) *trunc()* + Return the largest integral value with magnitude less than or + equal to {expr} as a |Float| (truncate towards zero). + {expr} must evaluate to a |Float| or a |Number|. + Examples: > + echo trunc(1.456) +< 1.0 > + echo trunc(-5.456) +< -5.0 > + echo trunc(4.0) +< 4.0 + + Can also be used as a |method|: > + Compute()->trunc() + +type({expr}) *type()* + The result is a Number representing the type of {expr}. + Instead of using the number directly, it is better to use the + v:t_ variable that has the value: + Number: 0 (|v:t_number|) + String: 1 (|v:t_string|) + Funcref: 2 (|v:t_func|) + List: 3 (|v:t_list|) + Dictionary: 4 (|v:t_dict|) + Float: 5 (|v:t_float|) + Boolean: 6 (|v:true| and |v:false|) + Null: 7 (|v:null|) + Blob: 10 (|v:t_blob|) + For backward compatibility, this method can be used: > + :if type(myvar) == type(0) + :if type(myvar) == type("") + :if type(myvar) == type(function("tr")) + :if type(myvar) == type([]) + :if type(myvar) == type({}) + :if type(myvar) == type(0.0) + :if type(myvar) == type(v:true) +< In place of checking for |v:null| type it is better to check + for |v:null| directly as it is the only value of this type: > + :if myvar is v:null +< To check if the v:t_ variables exist use this: > + :if exists('v:t_number') + +< Can also be used as a |method|: > + mylist->type() + +undofile({name}) *undofile()* + Return the name of the undo file that would be used for a file + with name {name} when writing. This uses the 'undodir' + option, finding directories that exist. It does not check if + the undo file exists. + {name} is always expanded to the full path, since that is what + is used internally. + If {name} is empty undofile() returns an empty string, since a + buffer without a file name will not write an undo file. + Useful in combination with |:wundo| and |:rundo|. + + Can also be used as a |method|: > + GetFilename()->undofile() + +undotree() *undotree()* + Return the current state of the undo tree in a dictionary with + the following items: + "seq_last" The highest undo sequence number used. + "seq_cur" The sequence number of the current position in + the undo tree. This differs from "seq_last" + when some changes were undone. + "time_cur" Time last used for |:earlier| and related + commands. Use |strftime()| to convert to + something readable. + "save_last" Number of the last file write. Zero when no + write yet. + "save_cur" Number of the current position in the undo + tree. + "synced" Non-zero when the last undo block was synced. + This happens when waiting from input from the + user. See |undo-blocks|. + "entries" A list of dictionaries with information about + undo blocks. + + The first item in the "entries" list is the oldest undo item. + Each List item is a |Dictionary| with these items: + "seq" Undo sequence number. Same as what appears in + |:undolist|. + "time" Timestamp when the change happened. Use + |strftime()| to convert to something readable. + "newhead" Only appears in the item that is the last one + that was added. This marks the last change + and where further changes will be added. + "curhead" Only appears in the item that is the last one + that was undone. This marks the current + position in the undo tree, the block that will + be used by a redo command. When nothing was + undone after the last change this item will + not appear anywhere. + "save" Only appears on the last block before a file + write. The number is the write count. The + first write has number 1, the last one the + "save_last" mentioned above. + "alt" Alternate entry. This is again a List of undo + blocks. Each item may again have an "alt" + item. + +uniq({list} [, {func} [, {dict}]]) *uniq()* *E882* + Remove second and succeeding copies of repeated adjacent + {list} items in-place. Returns {list}. If you want a list + to remain unmodified make a copy first: > + :let newlist = uniq(copy(mylist)) +< The default compare function uses the string representation of + each item. For the use of {func} and {dict} see |sort()|. + + Can also be used as a |method|: > + mylist->uniq() + +values({dict}) *values()* + Return a |List| with all the values of {dict}. The |List| is + in arbitrary order. Also see |items()| and |keys()|. + + Can also be used as a |method|: > + mydict->values() + +virtcol({expr}) *virtcol()* + The result is a Number, which is the screen column of the file + position given with {expr}. That is, the last screen position + occupied by the character at that position, when the screen + would be of unlimited width. When there is a <Tab> at the + position, the returned Number will be the column at the end of + the <Tab>. For example, for a <Tab> in column 1, with 'ts' + set to 8, it returns 8. |conceal| is ignored. + For the byte position use |col()|. + For the use of {expr} see |col()|. + When 'virtualedit' is used {expr} can be [lnum, col, off], where + "off" is the offset in screen columns from the start of the + character. E.g., a position within a <Tab> or after the last + character. When "off" is omitted zero is used. + When Virtual editing is active in the current mode, a position + beyond the end of the line can be returned. |'virtualedit'| + The accepted positions are: + . the cursor position + $ the end of the cursor line (the result is the + number of displayed characters in the cursor line + plus one) + 'x position of mark x (if the mark is not set, 0 is + returned) + v In Visual mode: the start of the Visual area (the + cursor is the end). When not in Visual mode + returns the cursor position. Differs from |'<| in + that it's updated right away. + Note that only marks in the current file can be used. + Examples: > + virtcol(".") with text "foo^Lbar", with cursor on the "^L", returns 5 + virtcol("$") with text "foo^Lbar", returns 9 + virtcol("'t") with text " there", with 't at 'h', returns 6 +< The first column is 1. 0 is returned for an error. + A more advanced example that echoes the maximum length of + all lines: > + echo max(map(range(1, line('$')), "virtcol([v:val, '$'])")) + +< Can also be used as a |method|: > + GetPos()->virtcol() + +visualmode([{expr}]) *visualmode()* + The result is a String, which describes the last Visual mode + used in the current buffer. Initially it returns an empty + string, but once Visual mode has been used, it returns "v", + "V", or "<CTRL-V>" (a single CTRL-V character) for + character-wise, line-wise, or block-wise Visual mode + respectively. + Example: > + :exe "normal " .. visualmode() +< This enters the same Visual mode as before. It is also useful + in scripts if you wish to act differently depending on the + Visual mode that was used. + If Visual mode is active, use |mode()| to get the Visual mode + (e.g., in a |:vmap|). + If {expr} is supplied and it evaluates to a non-zero Number or + 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()* + Waits until {condition} evaluates to |TRUE|, where {condition} + is a |Funcref| or |string| containing an expression. + + {timeout} is the maximum waiting time in milliseconds, -1 + means forever. + + Condition is evaluated on user events, internal events, and + every {interval} milliseconds (default: 200). + + Returns a status integer: + 0 if the condition was satisfied before timeout + -1 if the timeout was exceeded + -2 if the function was interrupted (by |CTRL-C|) + -3 if an error occurred + +wildmenumode() *wildmenumode()* + Returns |TRUE| when the wildmenu is active and |FALSE| + otherwise. See 'wildmenu' and 'wildmode'. + This can be used in mappings to handle the 'wildcharm' option + gracefully. (Makes only sense with |mapmode-c| mappings). + + For example to make <c-j> work like <down> in wildmode, use: > + :cnoremap <expr> <C-j> wildmenumode() ? "\<Down>\<Tab>" : "\<c-j>" +< + (Note, this needs the 'wildcharm' option set appropriately). + +win_execute({id}, {command} [, {silent}]) *win_execute()* + Like `execute()` but in the context of window {id}. + The window will temporarily be made the current window, + without triggering autocommands or changing directory. When + executing {command} autocommands will be triggered, this may + have unexpected side effects. Use |:noautocmd| if needed. + Example: > + call win_execute(winid, 'syntax enable') +< + Can also be used as a |method|, the base is passed as the + second argument: > + GetCommand()->win_execute(winid) + +win_findbuf({bufnr}) *win_findbuf()* + Returns a |List| with |window-ID|s for windows that contain + buffer {bufnr}. When there is none the list is empty. + + Can also be used as a |method|: > + GetBufnr()->win_findbuf() + +win_getid([{win} [, {tab}]]) *win_getid()* + Get the |window-ID| for the specified window. + When {win} is missing use the current window. + With {win} this is the window number. The top window has + number 1. + Without {tab} use the current tab, otherwise the tab with + number {tab}. The first tab has number one. + Return zero if the window cannot be found. + + Can also be used as a |method|: > + GetWinnr()->win_getid() + +win_gettype([{nr}]) *win_gettype()* + Return the type of the window: + "autocmd" autocommand window. Temporary window + used to execute autocommands. + "command" command-line window |cmdwin| + (empty) normal window + "loclist" |location-list-window| + "popup" popup window |popup| + "preview" preview window |preview-window| + "quickfix" |quickfix-window| + "unknown" window {nr} not found + + When {nr} is omitted return the type of the current window. + When {nr} is given return the type of this window by number or + |window-ID|. + + Also see the 'buftype' option. When running a terminal in a + popup window then 'buftype' is "terminal" and win_gettype() + returns "popup". + + Can also be used as a |method|: > + GetWinid()->win_gettype() +< +win_gotoid({expr}) *win_gotoid()* + Go to window with ID {expr}. This may also change the current + tabpage. + Return TRUE if successful, FALSE if the window cannot be found. + + Can also be used as a |method|: > + GetWinid()->win_gotoid() + +win_id2tabwin({expr}) *win_id2tabwin()* + Return a list with the tab number and window number of window + with ID {expr}: [tabnr, winnr]. + Return [0, 0] if the window cannot be found. + + Can also be used as a |method|: > + GetWinid()->win_id2tabwin() + +win_id2win({expr}) *win_id2win()* + Return the window number of window with ID {expr}. + Return 0 if the window cannot be found in the current tabpage. + + Can also be used as a |method|: > + GetWinid()->win_id2win() + +win_move_separator({nr}, {offset}) *win_move_separator()* + Move window {nr}'s vertical separator (i.e., the right border) + by {offset} columns, as if being dragged by the mouse. {nr} + can be a window number or |window-ID|. A positive {offset} + moves right and a negative {offset} moves left. Moving a + window's vertical separator will change the width of the + window and the width of other windows adjacent to the vertical + separator. The magnitude of movement may be smaller than + specified (e.g., as a consequence of maintaining + 'winminwidth'). Returns TRUE if the window can be found and + FALSE otherwise. + + Can also be used as a |method|: > + GetWinnr()->win_move_separator(offset) + +win_move_statusline({nr}, {offset}) *win_move_statusline()* + Move window {nr}'s status line (i.e., the bottom border) by + {offset} rows, as if being dragged by the mouse. {nr} can be a + window number or |window-ID|. A positive {offset} moves down + and a negative {offset} moves up. Moving a window's status + line will change the height of the window and the height of + other windows adjacent to the status line. The magnitude of + movement may be smaller than specified (e.g., as a consequence + of maintaining 'winminheight'). Returns TRUE if the window can + be found and FALSE otherwise. + + Can also be used as a |method|: > + GetWinnr()->win_move_statusline(offset) + +win_screenpos({nr}) *win_screenpos()* + Return the screen position of window {nr} as a list with two + numbers: [row, col]. The first window always has position + [1, 1], unless there is a tabline, then it is [2, 1]. + {nr} can be the window number or the |window-ID|. Use zero + for the current window. + Returns [0, 0] if the window cannot be found in the current + tabpage. + + Can also be used as a |method|: > + GetWinid()->win_screenpos() +< +win_splitmove({nr}, {target} [, {options}]) *win_splitmove()* + Move the window {nr} to a new split of the window {target}. + This is similar to moving to {target}, creating a new window + using |:split| but having the same contents as window {nr}, and + then closing {nr}. + + Both {nr} and {target} can be window numbers or |window-ID|s. + Both must be in the current tab page. + + Returns zero for success, non-zero for failure. + + {options} is a |Dictionary| with the following optional entries: + "vertical" When TRUE, the split is created vertically, + like with |:vsplit|. + "rightbelow" When TRUE, the split is made below or to the + right (if vertical). When FALSE, it is done + above or to the left (if vertical). When not + present, the values of 'splitbelow' and + 'splitright' are used. + + Can also be used as a |method|: > + GetWinid()->win_splitmove(target) +< + *winbufnr()* +winbufnr({nr}) The result is a Number, which is the number of the buffer + associated with window {nr}. {nr} can be the window number or + the |window-ID|. + When {nr} is zero, the number of the buffer in the current + window is returned. + When window {nr} doesn't exist, -1 is returned. + Example: > + :echo "The file in the current window is " .. bufname(winbufnr(0)) +< + Can also be used as a |method|: > + FindWindow()->winbufnr()->bufname() +< + *wincol()* +wincol() The result is a Number, which is the virtual column of the + cursor in the window. This is counting screen cells from the + left side of the window. The leftmost column is one. + + *windowsversion()* +windowsversion() + The result is a String. For MS-Windows it indicates the OS + version. E.g, Windows 10 is "10.0", Windows 8 is "6.2", + Windows XP is "5.1". For non-MS-Windows systems the result is + an empty string. + +winheight({nr}) *winheight()* + The result is a Number, which is the height of window {nr}. + {nr} can be the window number or the |window-ID|. + When {nr} is zero, the height of the current window is + returned. When window {nr} doesn't exist, -1 is returned. + An existing window always has a height of zero or more. + This excludes any window toolbar line. + Examples: > + :echo "The current window has " .. winheight(0) .. " lines." + +< Can also be used as a |method|: > + GetWinid()->winheight() +< +winlayout([{tabnr}]) *winlayout()* + The result is a nested List containing the layout of windows + in a tabpage. + + Without {tabnr} use the current tabpage, otherwise the tabpage + with number {tabnr}. If the tabpage {tabnr} is not found, + returns an empty list. + + For a leaf window, it returns: + ['leaf', {winid}] + For horizontally split windows, which form a column, it + returns: + ['col', [{nested list of windows}]] + For vertically split windows, which form a row, it returns: + ['row', [{nested list of windows}]] + + Example: > + " Only one window in the tab page + :echo winlayout() + ['leaf', 1000] + " Two horizontally split windows + :echo winlayout() + ['col', [['leaf', 1000], ['leaf', 1001]]] + " The second tab page, with three horizontally split + " windows, with two vertically split windows in the + " middle window + :echo winlayout(2) + ['col', [['leaf', 1002], ['row', [['leaf', 1003], + ['leaf', 1001]]], ['leaf', 1000]]] +< + Can also be used as a |method|: > + GetTabnr()->winlayout() +< + *winline()* +winline() The result is a Number, which is the screen line of the cursor + in the window. This is counting screen lines from the top of + the window. The first line is one. + If the cursor was moved the view on the file will be updated + first, this may cause a scroll. + + *winnr()* +winnr([{arg}]) The result is a Number, which is the number of the current + window. The top window has number 1. + Returns zero for a popup window. + + The optional argument {arg} supports the following values: + $ the number of the last window (the window + count). + # the number of the last accessed window (where + |CTRL-W_p| goes to). If there is no previous + window or it is in another tab page 0 is + returned. + {N}j the number of the Nth window below the + current window (where |CTRL-W_j| goes to). + {N}k the number of the Nth window above the current + window (where |CTRL-W_k| goes to). + {N}h the number of the Nth window left of the + current window (where |CTRL-W_h| goes to). + {N}l the number of the Nth window right of the + current window (where |CTRL-W_l| goes to). + The number can be used with |CTRL-W_w| and ":wincmd w" + |:wincmd|. + Also see |tabpagewinnr()| and |win_getid()|. + Examples: > + let window_count = winnr('$') + let prev_window = winnr('#') + let wnum = winnr('3k') + +< Can also be used as a |method|: > + GetWinval()->winnr() +< + *winrestcmd()* +winrestcmd() Returns a sequence of |:resize| commands that should restore + the current window sizes. Only works properly when no windows + are opened or closed and the current window and tab page is + unchanged. + Example: > + :let cmd = winrestcmd() + :call MessWithWindowSizes() + :exe cmd +< + *winrestview()* +winrestview({dict}) + Uses the |Dictionary| returned by |winsaveview()| to restore + the view of the current window. + Note: The {dict} does not have to contain all values, that are + returned by |winsaveview()|. If values are missing, those + settings won't be restored. So you can use: > + :call winrestview({'curswant': 4}) +< + This will only set the curswant value (the column the cursor + wants to move on vertical movements) of the cursor to column 5 + (yes, that is 5), while all other settings will remain the + same. This is useful, if you set the cursor position manually. + + If you have changed the values the result is unpredictable. + If the window size changed the result won't be the same. + + Can also be used as a |method|: > + GetView()->winrestview() +< + *winsaveview()* +winsaveview() Returns a |Dictionary| that contains information to restore + the view of the current window. Use |winrestview()| to + restore the view. + This is useful if you have a mapping that jumps around in the + buffer and you want to go back to the original view. + This does not save fold information. Use the 'foldenable' + option to temporarily switch off folding, so that folds are + not opened when moving around. This may have side effects. + The return value includes: + lnum cursor line number + col cursor column (Note: the first column + zero, as opposed to what getpos() + returns) + coladd cursor column offset for 'virtualedit' + curswant column for vertical movement + topline first line in the window + topfill filler lines, only in diff mode + leftcol first column displayed; only used when + 'wrap' is off + skipcol columns skipped + Note that no option values are saved. + + +winwidth({nr}) *winwidth()* + The result is a Number, which is the width of window {nr}. + {nr} can be the window number or the |window-ID|. + When {nr} is zero, the width of the current window is + returned. When window {nr} doesn't exist, -1 is returned. + An existing window always has a width of zero or more. + Examples: > + :echo "The current window has " .. winwidth(0) .. " columns." + :if winwidth(0) <= 50 + : 50 wincmd | + :endif +< For getting the terminal or screen size, see the 'columns' + option. + + Can also be used as a |method|: > + GetWinid()->winwidth() + +wordcount() *wordcount()* + The result is a dictionary of byte/chars/word statistics for + the current buffer. This is the same info as provided by + |g_CTRL-G| + The return value includes: + bytes Number of bytes in the buffer + chars Number of chars in the buffer + words Number of words in the buffer + cursor_bytes Number of bytes before cursor position + (not in Visual mode) + cursor_chars Number of chars before cursor position + (not in Visual mode) + cursor_words Number of words before cursor position + (not in Visual mode) + visual_bytes Number of bytes visually selected + (only in Visual mode) + visual_chars Number of chars visually selected + (only in Visual mode) + visual_words Number of words visually selected + (only in Visual mode) + + + *writefile()* +writefile({object}, {fname} [, {flags}]) + When {object} is a |List| write it to file {fname}. Each list + item is separated with a NL. Each list item must be a String + or Number. + When {flags} contains "b" then binary mode is used: There will + not be a NL after the last list item. An empty item at the + end does cause the last line in the file to end in a NL. + + When {object} is a |Blob| write the bytes to file {fname} + unmodified. + + When {flags} contains "a" then append mode is used, lines are + appended to the file: > + :call writefile(["foo"], "event.log", "a") + :call writefile(["bar"], "event.log", "a") +< + When {flags} contains "S" fsync() call is not used, with "s" + it is used, 'fsync' option applies by default. No fsync() + means that writefile() will finish faster, but writes may be + left in OS buffers and not yet written to disk. Such changes + will disappear if system crashes before OS does writing. + + All NL characters are replaced with a NUL character. + Inserting CR characters needs to be done before passing {list} + to writefile(). + An existing file is overwritten, if possible. + When the write fails -1 is returned, otherwise 0. There is an + error message if the file can't be created or when writing + fails. + Also see |readfile()|. + To copy a file byte for byte: > + :let fl = readfile("foo", "b") + :call writefile(fl, "foocopy", "b") + +< Can also be used as a |method|: > + GetText()->writefile("thefile") + +xor({expr}, {expr}) *xor()* + Bitwise XOR on the two arguments. The arguments are converted + to a number. A List, Dict or Float argument causes an error. + Example: > + :let bits = xor(bits, 0x80) +< + Can also be used as a |method|: > + :let bits = bits->xor(0x80) +< +============================================================================== +3. Matching a pattern in a String *string-match* + +This is common between several functions. A regexp pattern as explained at +|pattern| is normally used to find a match in the buffer lines. When a +pattern is used to find a match in a String, almost everything works in the +same way. The difference is that a String is handled like it is one line. +When it contains a "\n" character, this is not seen as a line break for the +pattern. It can be matched with a "\n" in the pattern, or with ".". Example: +> + :let a = "aaaa\nxxxx" + :echo matchstr(a, "..\n..") + aa + xx + :echo matchstr(a, "a.x") + a + x + +Don't forget that "^" will only match at the first character of the String and +"$" at the last character of the string. They don't match after or before a +"\n". + + vim:tw=78:ts=8:noet:ft=help:norl: diff --git a/runtime/doc/change.txt b/runtime/doc/change.txt index ffdd8427f9..b170f7cf65 100644 --- a/runtime/doc/change.txt +++ b/runtime/doc/change.txt @@ -906,7 +906,7 @@ Consider using a character like "@" or ":". There is no problem if the result of the expression contains the separation character. Examples: > - :s@\n@\="\r" . expand("$HOME") . "\r"@ + :s@\n@\="\r" .. expand("$HOME") .. "\r"@ This replaces an end-of-line with a new line containing the value of $HOME. > s/E/\="\<Char-0x20ac>"/g @@ -1065,7 +1065,7 @@ inside of strings can change! Also see 'softtabstop' option. > the command. You need to escape the '|' and '"' characters to prevent them from terminating the command. Example: > - :put ='path' . \",/test\" + :put ='path' .. \",/test\" < If there is no expression after '=', Vim uses the previous expression. You can see it with ":dis =". @@ -1118,10 +1118,13 @@ 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, the +replace and use "0p . You can repeat this as many times as you like, and the unnamed register will be changed each time. When you use a blockwise Visual mode command and yank only a single line into @@ -1595,7 +1598,8 @@ r Automatically insert the current comment leader after hitting <Enter> in Insert mode. *fo-o* o Automatically insert the current comment leader after hitting 'o' or - 'O' in Normal mode. + 'O' in Normal mode. In case comment is unwanted in a specific place + use CTRL-U to quickly delete it. |i_CTRL-U| *fo-q* q Allow formatting of comments with "gq". Note that formatting will not change blank lines or lines containing diff --git a/runtime/doc/channel.txt b/runtime/doc/channel.txt index 5f376a600e..e14427494d 100644 --- a/runtime/doc/channel.txt +++ b/runtime/doc/channel.txt @@ -44,7 +44,7 @@ functions like |chansend()| consume channel ids. 2. Reading and writing raw bytes *channel-bytes* Channels opened by Vimscript functions operate with raw bytes by default. For -a job channel using RPC, bytes can still be read over its stderr. Similarily, +a job channel using RPC, bytes can still be read over its stderr. Similarly, only bytes can be written to Nvim's own stderr. *channel-callback* diff --git a/runtime/doc/cmdline.txt b/runtime/doc/cmdline.txt index 7716af25bd..c6e4bf003f 100644 --- a/runtime/doc/cmdline.txt +++ b/runtime/doc/cmdline.txt @@ -207,7 +207,7 @@ CTRL-\ e {expr} *c_CTRL-\_e* Example: > :cmap <F7> <C-\>eAppendSome()<CR> :func AppendSome() - :let cmd = getcmdline() . " Some()" + :let cmd = getcmdline() .. " Some()" :" place the cursor on the ) :call setcmdpos(strlen(cmd)) :return cmd @@ -697,7 +697,8 @@ Line numbers may be specified with: *:range* *{address}* Each may be followed (several times) by '+' or '-' and an optional number. This number is added or subtracted from the preceding line number. If the -number is omitted, 1 is used. +number is omitted, 1 is used. If there is nothing before the '+' or '-' then +the current line is used. The "/" and "?" after {pattern} are required to separate the pattern from anything that follows. @@ -727,7 +728,7 @@ Some commands allow for a count after the command. This count is used as the number of lines to be used, starting with the line given in the last line specifier (the default is the cursor line). The commands that accept a count are the ones that use a range but do not have a file name argument (because -a file name can also be a number). +a file name can also be a number). The count cannot be negative. Examples: > :s/x/X/g 5 substitute 'x' by 'X' in the current line and four @@ -907,8 +908,7 @@ These modifiers can be given, in this order: directory. :. Reduce file name to be relative to current directory, if possible. File name is unmodified if it is not below the - current directory, but on MS-Windows the drive is removed if - it is the current drive. + current directory. For maximum shortness, use ":~:.". :h Head of the file name (the last component and any separators removed). Cannot be used with :e, :r or :t. diff --git a/runtime/doc/develop.txt b/runtime/doc/develop.txt index 7127c74134..178b0dc62b 100644 --- a/runtime/doc/develop.txt +++ b/runtime/doc/develop.txt @@ -105,7 +105,7 @@ in eval.c: - eval_call_provider(name, method, arguments, discard): calls provider#{name}#Call with the method and arguments. If discard is true, any - value returned by the provider will be discarded and and empty value be + value returned by the provider will be discarded and empty value will be returned. - eval_has_provider(name): Checks the `g:loaded_{name}_provider` variable which must be set to 2 by the provider script to indicate that it is diff --git a/runtime/doc/diagnostic.txt b/runtime/doc/diagnostic.txt index a825435179..19db3158be 100644 --- a/runtime/doc/diagnostic.txt +++ b/runtime/doc/diagnostic.txt @@ -132,7 +132,7 @@ with |vim.notify()|: > In this example, there is nothing to do when diagnostics are hidden, so we omit the "hide" function. -Existing handlers can be overriden. For example, use the following to only +Existing handlers can be overridden. For example, use the following to only show a sign for the highest severity diagnostic on a given line: > -- Create a custom namespace. This will aggregate signs from all other @@ -177,8 +177,9 @@ All highlights defined for diagnostics begin with `Diagnostic` followed by the type of highlight (e.g., `Sign`, `Underline`, etc.) and the severity (e.g. `Error`, `Warn`, etc.) -Sign, underline and virtual text highlights (by default) are linked to their -corresponding default highlight. +By default, highlights for signs, floating windows, and virtual text are linked to the +corresponding default highlight. Underline highlights are not linked and use their +own default highlight groups. For example, the default highlighting for |hl-DiagnosticSignError| is linked to |hl-DiagnosticError|. To change the default (and therefore the linked @@ -298,7 +299,6 @@ Example: > autocmd DiagnosticChanged * lua vim.diagnostic.setqflist({open = false }) < ============================================================================== -============================================================================== Lua module: vim.diagnostic *diagnostic-api* config({opts}, {namespace}) *vim.diagnostic.config()* @@ -334,8 +334,9 @@ config({opts}, {namespace}) *vim.diagnostic.config()* that returns any of the above. Parameters: ~ - {opts} table Configuration table with the following - keys: + {opts} table|nil When omitted or "nil", retrieve the + current configuration. Otherwise, a + configuration table with the following keys: • underline: (default true) Use underline for diagnostics. Options: • severity: Only underline diagnostics @@ -343,7 +344,10 @@ config({opts}, {namespace}) *vim.diagnostic.config()* |diagnostic-severity| • virtual_text: (default true) Use virtual - text for diagnostics. Options: + text for diagnostics. If multiple + diagnostics are set for a namespace, one + prefix per diagnostic + the last diagnostic + message are shown. Options: • severity: Only show virtual text for diagnostics matching the given severity |diagnostic-severity| @@ -353,6 +357,11 @@ config({opts}, {namespace}) *vim.diagnostic.config()* is more than one diagnostic source in the buffer. Otherwise, any truthy value means to always show the diagnostic source. + • spacing: (number) Amount of empty spaces + inserted at the beginning of the virtual + text. + • prefix: (string) Prepend diagnostic + message with prefix. • format: (function) A function that takes a diagnostic as input and returns a string. The return value is the text used diff --git a/runtime/doc/diff.txt b/runtime/doc/diff.txt index 6115a5d235..9c5792dd43 100644 --- a/runtime/doc/diff.txt +++ b/runtime/doc/diff.txt @@ -324,8 +324,9 @@ After setting this variable, reload the syntax script: > FINDING THE DIFFERENCES *diff-diffexpr* -The 'diffexpr' option can be set to use something else than the standard -"diff" program to compare two files and find the differences. *E959* +The 'diffexpr' option can be set to use something else than the internal diff +support or the standard "diff" program to compare two files and find the +differences. When 'diffexpr' is empty, Vim uses this command to find the differences between file1 and file2: > @@ -358,7 +359,7 @@ format mentioned. These variables are set to the file names used: v:fname_in original file v:fname_new new version of the same file - v:fname_out resulting diff file + v:fname_out where to write the resulting diff file Additionally, 'diffexpr' should take care of "icase" and "iwhite" in the 'diffopt' option. 'diffexpr' cannot change the value of 'lines' and @@ -370,13 +371,13 @@ Example (this does almost the same as 'diffexpr' being empty): > function MyDiff() let opt = "" if &diffopt =~ "icase" - let opt = opt . "-i " + let opt = opt .. "-i " endif if &diffopt =~ "iwhite" - let opt = opt . "-b " + let opt = opt .. "-b " endif - silent execute "!diff -a --binary " . opt . v:fname_in . " " . v:fname_new . - \ " > " . v:fname_out + silent execute "!diff -a --binary " .. opt .. v:fname_in .. " " .. v:fname_new .. + \ " > " .. v:fname_out redraw! endfunction @@ -426,8 +427,8 @@ Example (this does the same as 'patchexpr' being empty): > set patchexpr=MyPatch() function MyPatch() - :call system("patch -o " . v:fname_out . " " . v:fname_in . - \ " < " . v:fname_diff) + :call system("patch -o " .. v:fname_out .. " " .. v:fname_in .. + \ " < " .. v:fname_diff) endfunction Make sure that using the "patch" program doesn't have unwanted side effects. diff --git a/runtime/doc/editing.txt b/runtime/doc/editing.txt index 14df41e6c8..bfa01f45a7 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|. @@ -411,9 +411,9 @@ does apply like to other wildcards. Environment variables in the expression are expanded when evaluating the expression, thus this works: > - :e `=$HOME . '/.vimrc'` + :e `=$HOME .. '/.vimrc'` This does not work, $HOME is inside a string and used literally: > - :e `='$HOME' . '/.vimrc'` + :e `='$HOME' .. '/.vimrc'` If the expression returns a string then names are to be separated with line breaks. When the result is a |List| then each item is used as a name. Line @@ -845,7 +845,7 @@ Note: When the 'write' option is off, you are not able to write any file. *:w* *:write* *E502* *E503* *E504* *E505* - *E512* *E514* *E667* *E796* *E949* + *E512* *E514* *E667* *E949* :w[rite] [++opt] Write the whole buffer to the current file. This is the normal way to save changes to a file. It fails when the 'readonly' option is set or when there is @@ -1331,6 +1331,7 @@ current directory for that window. Windows where the |:lcd| command has not been used stick to the global or tab-local directory. When jumping to another window the current directory is changed to the last specified local current directory. If none was specified, the global or tab-local directory is used. +When creating a new window it inherits the local directory of the current window. When changing tabs the same behaviour applies. If the current tab has no local working directory the global working directory is used. @@ -1449,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] @@ -1489,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: @@ -1568,6 +1574,12 @@ There are three different types of searching: /u/user_x/work/include /u/user_x/include +< Note: If your 'path' setting includes a non-existing directory, Vim will + skip the non-existing directory, but continues searching in the parent of + the non-existing directory if upwards searching is used. E.g. when + searching "../include" and that doesn't exist, and upward searching is + used, also searches in "..". + 3) Combined up/downward search: If Vim's current path is /u/user_x/work/release and you do > set path=**;/u/user_x diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt index 63f6c5628a..3015e232a7 100644 --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -111,7 +111,7 @@ When mixing Number and Float the Number is converted to Float. Otherwise there is no automatic conversion of Float. You can use str2float() for String to Float, printf() for Float to String and float2nr() for Float to Number. - *E891* *E892* *E893* *E894* + *E362* *E891* *E892* *E893* *E894* *E907* When expecting a Float a Number can also be used, but nothing else. *no-type-checking* @@ -372,8 +372,8 @@ Changing the order of items in a list: > For loop ~ -The |:for| loop executes commands for each item in a |List| or |Blob|. -A variable is set to each item in the sequence. Example with a List: > +The |:for| loop executes commands for each item in a |List|, |String| or |Blob|. +A variable is set to each item in sequence. Example with a List: > :for item in mylist : call Doit(item) :endfor @@ -390,7 +390,7 @@ If all you want to do is modify each item in the list then the |map()| function will be a simpler method than a for loop. Just like the |:let| command, |:for| also accepts a list of variables. This -requires the argument to be a list of lists. > +requires the argument to be a List of Lists. > :for [lnum, col] in [[1, 3], [2, 8], [3, 0]] : call Doit(lnum, col) :endfor @@ -402,12 +402,18 @@ It is also possible to put remaining items in a List variable: > :for [i, j; rest] in listlist : call Doit(i, j) : if !empty(rest) - : echo "remainder: " . string(rest) + : echo "remainder: " .. string(rest) : endif :endfor For a Blob one byte at a time is used. +For a String one character, including any composing characters, is used as a +String. Example: > + for c in text + echo 'This character is ' .. c + endfor + List functions ~ *E714* @@ -424,11 +430,11 @@ Functions that are useful with a List: > :let list = split("a b c") " create list from items in a string :let string = join(list, ', ') " create string from list items :let s = string(list) " String representation of list - :call map(list, '">> " . v:val') " prepend ">> " to each item + :call map(list, '">> " .. v:val') " prepend ">> " to each item Don't forget that a combination of features can make things simple. For example, to add up all the numbers in a list: > - :exe 'let sum = ' . join(nrlist, '+') + :exe 'let sum = ' .. join(nrlist, '+') 1.4 Dictionaries ~ @@ -490,7 +496,7 @@ turn the Dictionary into a List and pass it to |:for|. Most often you want to loop over the keys, using the |keys()| function: > :for key in keys(mydict) - : echo key . ': ' . mydict[key] + : echo key .. ': ' .. mydict[key] :endfor The List of keys is unsorted. You may want to sort them first: > @@ -498,13 +504,13 @@ The List of keys is unsorted. You may want to sort them first: > To loop over the values use the |values()| function: > :for v in values(mydict) - : echo "value: " . v + : echo "value: " .. v :endfor If you want both the key and the value use the |items()| function. It returns a List in which each item is a List with two items, the key and the value: > :for [key, value] in items(mydict) - : echo key . ': ' . value + : echo key .. ': ' .. value :endfor @@ -599,7 +605,7 @@ Functions that can be used with a Dictionary: > :let small = min(dict) " minimum value in dict :let xs = count(dict, 'x') " count nr of times 'x' appears in dict :let s = string(dict) " String representation of dict - :call map(dict, '">> " . v:val') " prepend ">> " to each item + :call map(dict, '">> " .. v:val') " prepend ">> " to each item 1.5 Blobs ~ @@ -676,7 +682,7 @@ similar to -1. > :let otherblob = myblob[:] " make a copy of the Blob If the first index is beyond the last byte of the Blob or the second byte is -before the first byte, the result is an empty Blob. There is no error +before the first index, the result is an empty Blob. There is no error message. If the second index is equal to or greater than the length of the Blob the @@ -834,7 +840,7 @@ Example: > All expressions within one level are parsed from left to right. -expr1 *expr1* *trinary* *E109* +expr1 *expr1* *ternary* *E109* ----- expr2 ? expr1 : expr1 @@ -1066,7 +1072,7 @@ expr7 *expr7* For '!' |TRUE| becomes |FALSE|, |FALSE| becomes |TRUE| (one). For '-' the sign of the number is changed. -For '+' the number is unchanged. +For '+' the number is unchanged. Note: "++" has no effect. A String will be converted to a Number first. @@ -1195,6 +1201,7 @@ When expr8 is a |Funcref| type variable, invoke the function it refers to. expr8->name([args]) method call *method* *->* expr8->{lambda}([args]) + *E260* *E276* For methods that are also available as global functions this is the same as: > name(expr8 [, args]) There can also be methods specifically for the type of "expr8". @@ -1227,8 +1234,8 @@ And NOT: > number ------ number number constant *expr-number* - *hex-number* *octal-number* *binary-number* + *0x* *hex-number* *0o* *octal-number* *binary-number* Decimal, Hexadecimal (starting with 0x or 0X), Binary (starting with 0b or 0B) and Octal (starting with 0, 0o or 0O). @@ -1355,7 +1362,7 @@ option *expr-option* *E112* *E113* &l:option local option value Examples: > - echo "tabstop is " . &tabstop + echo "tabstop is " .. &tabstop if &insertmode Any option name can be used here. See |options|. When using the local value @@ -1450,7 +1457,7 @@ the function returns: > :let Bar = Foo(4) :echo Bar(6) < 5 -Note that the variables must exist in the outer scope before the lamba is +Note that the variables must exist in the outer scope before the lambda is defined for this to work. See also |:func-closure|. Lambda and closure support can be checked with: > @@ -1485,7 +1492,7 @@ Notice how execute() is used to execute an Ex command. That's ugly though. Lambda expressions have internal names like '<lambda>42'. If you get an error for a lambda expression, you can find what it is with the following command: > - :function {'<lambda>42'} + :function <lambda>42 See also: |numbered-function| ============================================================================== @@ -1630,7 +1637,7 @@ maintain a counter: > echo "script executed for the first time" else let s:counter = s:counter + 1 - echo "script executed " . s:counter . " times now" + echo "script executed " .. s:counter .. " times now" endif Note that this means that filetype plugins don't get a different set of script @@ -1729,7 +1736,7 @@ v:completed_item *v:count* *count-variable* v:count The count given for the last Normal mode command. Can be used to get the count before a mapping. Read-only. Example: > - :map _x :<C-U>echo "the count is " . v:count<CR> + :map _x :<C-U>echo "the count is " .. v:count<CR> < Note: The <C-U> is required to remove the line range that you get when typing ':' after a count. When there are two counts, as in "3d2w", they are multiplied, @@ -1875,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 @@ -2266,8654 +2278,13 @@ v:windowid Application-specific window "handle" which may be set by any ============================================================================== 4. Builtin Functions *vim-function* *functions* -The Vimscript subsystem (referred to as "eval" internally) provides the -following builtin functions. Scripts can also define |user-function|s. +The Vimscript subsystem (referred to as "eval" internally) provides builtin +functions. Scripts can also define |user-function|s. See |function-list| to browse functions by topic. -(Use CTRL-] on the function name to jump to the full explanation.) - -USAGE RESULT DESCRIPTION ~ - -abs({expr}) Float or Number absolute value of {expr} -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} -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 -argv({nr} [, {winid}]) String {nr} entry of the argument list -argv([-1, {winid}]) List the argument list -asin({expr}) Float arc sine of {expr} -assert_beeps({cmd}) Number assert {cmd} causes a beep -assert_equal({exp}, {act} [, {msg}]) - Number assert {exp} is equal to {act} -assert_equalfile({fname-one}, {fname-two} [, {msg}]) - Number assert file contents are equal -assert_exception({error} [, {msg}]) - Number assert {error} is in v:exception -assert_fails({cmd} [, {error}]) Number assert {cmd} fails -assert_false({actual} [, {msg}]) - Number assert {actual} is false -assert_inrange({lower}, {upper}, {actual} [, {msg}]) - Number assert {actual} is inside the range -assert_match({pat}, {text} [, {msg}]) - Number assert {pat} matches {text} -assert_nobeep({cmd}) Number assert {cmd} does not cause a beep -assert_notequal({exp}, {act} [, {msg}]) - Number assert {exp} is not equal {act} -assert_notmatch({pat}, {text} [, {msg}]) - Number assert {pat} not matches {text} -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} -browse({save}, {title}, {initdir}, {default}) - String put up a file requester -browsedir({title}, {initdir}) String put up a directory requester -bufadd({name}) Number add a buffer to the buffer list -bufexists({expr}) Number |TRUE| if buffer {expr} exists -buflisted({expr}) Number |TRUE| if buffer {expr} is listed -bufload({expr}) Number load buffer {expr} if not loaded yet -bufloaded({expr}) Number |TRUE| if buffer {expr} is loaded -bufname([{expr}]) String Name of the buffer {expr} -bufnr([{expr} [, {create}]]) Number Number of the buffer {expr} -bufwinid({expr}) Number |window-ID| of buffer {expr} -bufwinnr({expr}) Number window number of buffer {expr} -byte2line({byte}) Number line number at byte count {byte} -byteidx({expr}, {nr}) Number byte index of {nr}'th char in {expr} -byteidxcomp({expr}, {nr}) Number byte index of {nr}'th char in {expr} -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 -chansend({id}, {data}) Number Writes {data} to channel -char2nr({expr}[, {utf8}]) Number ASCII/UTF-8 value of first char in {expr} -charidx({string}, {idx} [, {countcc}]) - Number char index of byte {idx} in {string} -chdir({dir}) String change current working directory -cindent({lnum}) Number C indent for line {lnum} -clearmatches([{win}]) none clear all matches -col({expr}) Number column nr of cursor or mark -complete({startcol}, {matches}) none set Insert mode completion -complete_add({expr}) Number add completion match -complete_check() Number check for key typed during completion -complete_info([{what}]) Dict get current completion information -confirm({msg} [, {choices} [, {default} [, {type}]]]) - Number number of choice picked by user -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} -cscope_connection([{num}, {dbpath} [, {prepend}]]) - Number checks existence of cscope connection -ctxget([{index}]) Dict return the |context| dict at {index} -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} -ctxsize() Number return |context-stack| size -cursor({lnum}, {col} [, {off}]) - Number move cursor to {lnum}, {col}, {off} -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}]) - Number delete lines from buffer {buf} -dictwatcheradd({dict}, {pattern}, {callback}) - Start watching a dictionary -dictwatcherdel({dict}, {pattern}, {callback}) - Stop watching a dictionary -did_filetype() Number |TRUE| if FileType autocommand event used -diff_filler({lnum}) Number diff filler lines about {lnum} -diff_hlID({lnum}, {col}) Number diff highlighting at {lnum}/{col} -empty({expr}) Number |TRUE| if {expr} is empty -environ() Dict return environment variables -escape({string}, {chars}) String escape {chars} in {string} with '\' -eval({string}) any evaluate {string} into its value -eventhandler() Number |TRUE| if inside an event handler -executable({expr}) Number 1 if executable {expr} exists -execute({command}) String execute and capture output of {command} -exepath({expr}) String full path of the command {expr} -exists({expr}) Number |TRUE| if {expr} exists -extend({expr1}, {expr2} [, {expr3}]) - List/Dict insert items of {expr2} into {expr1} -exp({expr}) Float exponential of {expr} -expand({expr} [, {nosuf} [, {list}]]) - any expand special keywords in {expr} -expandcmd({expr}) String expand {expr} like with `:edit` -feedkeys({string} [, {mode}]) Number add key sequence to typeahead buffer -filereadable({file}) Number |TRUE| if {file} is a readable file -filewritable({file}) Number |TRUE| if {file} is a writable file -filter({expr1}, {expr2}) List/Dict remove items from {expr1} where - {expr2} is 0 -finddir({name} [, {path} [, {count}]]) - String find directory {name} in {path} -findfile({name} [, {path} [, {count}]]) - String find file {name} in {path} -flatten({list} [, {maxdepth}]) List flatten {list} up to {maxdepth} levels -float2nr({expr}) Number convert Float {expr} to a Number -floor({expr}) Float round {expr} down -fmod({expr1}, {expr2}) Float remainder of {expr1} / {expr2} -fnameescape({fname}) String escape special characters in {fname} -fnamemodify({fname}, {mods}) String modify file name -foldclosed({lnum}) Number first line of fold at {lnum} if closed -foldclosedend({lnum}) Number last line of fold at {lnum} if closed -foldlevel({lnum}) Number fold level at {lnum} -foldtext() String line displayed for closed fold -foldtextresult({lnum}) String text for closed fold at {lnum} -foreground() Number bring the Vim window to the foreground -fullcommand({name}) String get full command from {name} -funcref({name} [, {arglist}] [, {dict}]) - Funcref reference to function {name} -function({name} [, {arglist}] [, {dict}]) - Funcref named reference to function {name} -garbagecollect([{atexit}]) none free memory, breaking cyclic references -get({list}, {idx} [, {def}]) any get item {idx} from {list} or {def} -get({dict}, {key} [, {def}]) any get item {key} from {dict} or {def} -get({func}, {what}) any get property of funcref/partial {func} -getbufinfo([{buf}]) List information about buffers -getbufline({buf}, {lnum} [, {end}]) - List lines {lnum} to {end} of buffer {buf} -getbufvar({buf}, {varname} [, {def}]) - any variable {varname} in buffer {buf} -getchangelist([{buf}]) List list of change list items -getchar([expr]) Number or String - get one character from the user -getcharmod() Number modifiers for the last typed character -getcharsearch() Dict last character search -getcharstr([expr]) String get one character from the user -getcmdline() String return the current command-line -getcmdpos() Number return cursor position in command-line -getcmdtype() String return current command-line type -getcmdwintype() String return current command-line window type -getcompletion({pat}, {type} [, {filtered}]) - List list of cmdline completion matches -getcurpos() List position of the cursor -getcwd([{winnr} [, {tabnr}]]) String get the current working directory -getenv({name}) String return environment variable -getfontname([{name}]) String name of font being used -getfperm({fname}) String file permissions of file {fname} -getfsize({fname}) Number size in bytes of file {fname} -getftime({fname}) Number last modification time of file -getftype({fname}) String description of type of file {fname} -getjumplist([{winnr} [, {tabnr}]]) - List list of jump list items -getline({lnum}) String line {lnum} of current buffer -getline({lnum}, {end}) List lines {lnum} to {end} of current buffer -getloclist({nr}) List list of location list items -getloclist({nr}, {what}) Dict get specific location list properties -getmarklist([{buf}]) List list of global/local marks -getmatches([{win}]) List list of current matches -getmousepos() Dict last known mouse position -getpid() Number process ID of Vim -getpos({expr}) List position of cursor, mark, etc. -getqflist() List list of quickfix items -getqflist({what}) Dict get specific quickfix list properties -getreg([{regname} [, 1 [, {list}]]]) - String or List contents of a register -getreginfo([{regname}]) Dict information about a register -getregtype([{regname}]) String type of a register -gettabinfo([{expr}]) List list of tab pages -gettabvar({nr}, {varname} [, {def}]) - any variable {varname} in tab {nr} or {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 -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 -getwinvar({nr}, {varname} [, {def}]) - any variable {varname} in window {nr} -glob({expr} [, {nosuf} [, {list} [, {alllinks}]]]) - any expand file wildcards in {expr} -glob2regpat({expr}) String convert a glob pat into a search pat -globpath({path}, {expr} [, {nosuf} [, {list} [, {alllinks}]]]) - String do glob({expr}) for all dirs in {path} -has({feature}) Number |TRUE| if feature {feature} supported -has_key({dict}, {key}) Number |TRUE| if {dict} has entry {key} -haslocaldir([{winnr} [, {tabnr}]]) - Number |TRUE| if the window executed |:lcd| or - the tab executed |:tcd| -hasmapto({what} [, {mode} [, {abbr}]]) - Number |TRUE| if mapping to {what} exists -histadd({history}, {item}) String add an item to a history -histdel({history} [, {item}]) String remove an item from a history -histget({history} [, {index}]) String get the item {index} from a history -histnr({history}) Number highest index of a history -hlexists({name}) Number |TRUE| if highlight group {name} exists -hlID({name}) Number syntax ID of highlight group {name} -hostname() String name of the machine Vim is running on -iconv({expr}, {from}, {to}) String convert encoding of {expr} -indent({lnum}) Number indent of line {lnum} -index({object}, {expr} [, {start} [, {ic}]]) - Number index in {object} where {expr} appears -input({prompt} [, {text} [, {completion}]]) - String get input from the user -inputlist({textlist}) Number let the user pick from a choice list -inputrestore() Number restore typeahead -inputsave() Number save and clear typeahead -inputsecret({prompt} [, {text}]) - String like input() but hiding the text -insert({object}, {item} [, {idx}]) - List insert {item} in {object} [before {idx}] -interrupt() none interrupt script execution -invert({expr}) Number bitwise invert -isdirectory({directory}) Number |TRUE| if {directory} is a directory -isinf({expr}) Number determine if {expr} is infinity value - (positive or negative) -islocked({expr}) Number |TRUE| if {expr} is locked -isnan({expr}) Number |TRUE| if {expr} is NaN -id({expr}) String identifier of the container -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 -jobstop({id}) Number Stops a job -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 -keys({dict}) List keys in {dict} -len({expr}) Number the length of {expr} -libcall({lib}, {func}, {arg}) String call {func} in library {lib} with {arg} -libcallnr({lib}, {func}, {arg}) Number idem, but return a Number -line({expr} [, {winid}]) Number line nr of cursor, last line or mark -line2byte({lnum}) Number byte count of line {lnum} -lispindent({lnum}) Number Lisp indent for line {lnum} -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 -map({expr1}, {expr2}) List/Dict change each item in {expr1} to {expr} -maparg({name}[, {mode} [, {abbr} [, {dict}]]]) - String or Dict - rhs of mapping {name} in mode {mode} -mapcheck({name}[, {mode} [, {abbr}]]) - String check for mappings matching {name} -match({expr}, {pat}[, {start}[, {count}]]) - Number position where {pat} matches in {expr} -matchadd({group}, {pattern}[, {priority}[, {id}]]) - Number highlight {pattern} with {group} -matchaddpos({group}, {list}[, {priority}[, {id}]]) - 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}]]) - Number position where {pat} ends in {expr} -matchlist({expr}, {pat}[, {start}[, {count}]]) - List match and submatches of {pat} in {expr} -matchstr({expr}, {pat}[, {start}[, {count}]]) - String {count}'th match of {pat} in {expr} -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} -min({expr}) Number minimum value of items in {expr} -mkdir({name} [, {path} [, {prot}]]) - Number create directory {name} -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} -nvim_...({args}...) any call nvim |api| functions -or({expr}, {expr}) Number bitwise OR -pathshorten({expr}) String shorten directory names in a path -perleval({expr}) any evaluate |perl| expression -pow({x}, {y}) Float {x} to the power of {y} -prevnonblank({lnum}) Number line nr of non-blank line <= {lnum} -printf({fmt}, {expr1}...) String format text -prompt_getprompt({buf}) String get prompt text -prompt_setcallback({buf}, {expr}) none set prompt callback function -prompt_setinterrupt({buf}, {text}) none set prompt interrupt function -prompt_setprompt({buf}, {text}) none set prompt text -pum_getpos() Dict position and size of pum if visible -pumvisible() Number whether popup menu is visible -pyeval({expr}) any evaluate |Python| expression -py3eval({expr}) any evaluate |python3| expression -pyxeval({expr}) any evaluate |python_x| expression -range({expr} [, {max} [, {stride}]]) - List items from {expr} to {max} -readdir({dir} [, {expr}]) List file names in {dir} selected by {expr} -readfile({fname} [, {type} [, {max}]]) - List get list of lines from file {fname} -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 -reltime([{start} [, {end}]]) List get time value -reltimefloat({time}) Float turn the time value into a Float -reltimestr({time}) String turn time value into a String -remote_expr({server}, {string} [, {idvar} [, {timeout}]]) - String send expression -remote_foreground({server}) Number bring Vim server to the foreground -remote_peek({serverid} [, {retvar}]) - Number check for reply string -remote_read({serverid} [, {timeout}]) - String read reply string -remote_send({server}, {string} [, {idvar}]) - String send key sequence -remote_startserver({name}) none become server {name} -remove({list}, {idx} [, {end}]) any/List - remove items {idx}-{end} from {list} -remove({blob}, {idx} [, {end}]) Number/Blob - remove bytes {idx}-{end} from {blob} -remove({dict}, {key}) any remove entry {key} from {dict} -rename({from}, {to}) Number rename (move) file from {from} to {to} -repeat({expr}, {count}) String repeat {expr} {count} times -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}...]) - Sends an |RPC| notification to {channel} -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 -screenchars({row}, {col}) List List of characters at screen position -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}]]]) - Number search for {pattern} -searchcount([{options}]) Dict Get or update the last search count -searchdecl({name} [, {global} [, {thisblock}]]) - Number search for variable declaration -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}]]]) - 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 - {expr} -setbufvar({buf}, {varname}, {val}) set {varname} in buffer {buf} to {val} -setcharsearch({dict}) Dict set character search from {dict} -setcmdpos({pos}) Number set cursor position in command-line -setenv({name}, {val}) none set environment variable -setfperm({fname}, {mode} Number set {fname} file permissions to {mode} -setline({lnum}, {line}) Number set line {lnum} to {line} -setloclist({nr}, {list} [, {action}]) - Number modify location list using {list} -setloclist({nr}, {list}, {action}, {what}) - Number modify specific location list props -setmatches({list} [, {win}]) Number restore a list of matches -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 -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} -settagstack({nr}, {dict} [, {action}]) - Number modify tag stack using {dict} -setwinvar({nr}, {varname}, {val}) set {varname} in window {nr} to {val} -sha256({string}) String SHA256 checksum of {string} -shellescape({string} [, {special}]) - String escape {string} for use as shell - command argument -shiftwidth([{col}]) Number effective value of 'shiftwidth' -sign_define({name} [, {dict}]) Number define or update a sign -sign_define({list}) List define or update a list of signs -sign_getdefined([{name}]) List get a list of defined signs -sign_getplaced([{buf} [, {dict}]]) - List get a list of placed signs -sign_jump({id}, {group}, {buf}) - Number jump to a sign -sign_place({id}, {group}, {name}, {buf} [, {dict}]) - Number place a sign -sign_placelist({list}) List place a list of signs -sign_undefine([{name}]) Number undefine a sign -sign_undefine({list}) List undefine a list of signs -sign_unplace({group} [, {dict}]) - Number unplace a sign -sign_unplacelist({list}) List unplace a list of signs -simplify({filename}) String simplify filename as much as possible -sin({expr}) Float sine of {expr} -sinh({expr}) Float hyperbolic sine of {expr} -sockconnect({mode}, {address} [, {opts}]) - Number Connects to socket -sort({list} [, {func} [, {dict}]]) - List sort {list}, using {func} to compare -soundfold({word}) String sound-fold {word} -spellbadword() String badly spelled word at cursor -spellsuggest({word} [, {max} [, {capital}]]) - List spelling suggestions -split({expr} [, {pat} [, {keepempty}]]) - List make |List| from {pat} separated {expr} -sqrt({expr}) Float square root of {expr} -stdioopen({dict}) Number open stdio in a headless instance. -stdpath({what}) String/List returns the standard path(s) for {what} -str2float({expr} [, {quoted}]) Float convert String to Float -str2list({expr} [, {utf8}]) List convert each character of {expr} to - ASCII/UTF-8 value -str2nr({expr} [, {base} [, {quoted}]]) - Number convert String to Number -strchars({expr} [, {skipcc}]) Number character length of the String {expr} -strcharpart({str}, {start} [, {len}]) - String {len} characters of {str} at - character {start} -strdisplaywidth({expr} [, {col}]) Number display length of the String {expr} -strftime({format} [, {time}]) String format time with a specified format -strgetchar({str}, {index}) Number get char {index} from {str} -stridx({haystack}, {needle} [, {start}]) - Number index of {needle} in {haystack} -string({expr}) String String representation of {expr} value -strlen({expr}) Number length of the String {expr} -strpart({str}, {start} [, {len} [, {chars}]]) - String {len} bytes/chars of {str} at - byte {start} -strptime({format}, {timestring}) - Number Convert {timestring} to unix timestamp -strridx({haystack}, {needle} [, {start}]) - Number last index of {needle} in {haystack} -strtrans({expr}) String translate string to make it printable -strwidth({expr}) Number display cell length of the String {expr} -submatch({nr} [, {list}]) String or List - specific match in ":s" or substitute() -substitute({expr}, {pat}, {sub}, {flags}) - String all {pat} in {expr} replaced with {sub} -swapinfo({fname}) Dict information about swap file {fname} -swapname({buf}) String swap file of buffer {buf} -synID({lnum}, {col}, {trans}) Number syntax ID at {lnum} and {col} -synIDattr({synID}, {what} [, {mode}]) - String attribute {what} of syntax ID {synID} -synIDtrans({synID}) Number translated syntax ID of {synID} -synconcealed({lnum}, {col}) List info about concealing -synstack({lnum}, {col}) List stack of syntax IDs at {lnum} and {col} -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}]) - Number number of current window in tab page -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} -tempname() String name for a temporary file -test_garbagecollect_now() none free memory right now for testing -timer_info([{id}]) List information about timers -timer_pause({id}, {pause}) none pause or unpause a timer -timer_start({time}, {callback} [, {options}]) - Number create a timer -timer_stop({timer}) none stop a timer -timer_stopall() none stop all timers -tolower({expr}) String the String {expr} switched to lowercase -toupper({expr}) String the String {expr} switched to uppercase -tr({src}, {fromstr}, {tostr}) String translate chars of {src} in {fromstr} - to chars in {tostr} -trim({text} [, {mask} [, {dir}]]) - String trim characters in {mask} from {text} -trunc({expr}) Float truncate Float {expr} -type({name}) Number type of variable {name} -undofile({name}) String undo file name for {name} -undotree() List undo file tree -uniq({list} [, {func} [, {dict}]]) - List remove adjacent duplicates from a list -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}]) - Number Wait until {condition} is satisfied -wildmenumode() Number whether 'wildmenu' mode is active -win_execute({id}, {command} [, {silent}]) - String execute {command} in window {id} -win_findbuf({bufnr}) List find windows containing {bufnr} -win_getid([{win} [, {tab}]]) Number get |window-ID| for {win} in {tab} -win_gettype([{nr}]) String type of window {nr} -win_gotoid({expr}) Number go to |window-ID| {expr} -win_id2tabwin({expr}) List get tab and window nr from |window-ID| -win_id2win({expr}) Number get window nr from |window-ID| -win_screenpos({nr}) List get screen position of window {nr} -win_splitmove({nr}, {target} [, {options}]) - Number move window {nr} to split of {target} -winbufnr({nr}) Number buffer number of window {nr} -wincol() Number window column of the cursor -windowsversion() String MS-Windows OS version -winheight({nr}) Number height of window {nr} -winlayout([{tabnr}]) List layout of windows in tab {tabnr} -winline() Number window line of the cursor -winnr([{expr}]) Number number of current window -winrestcmd() String returns command to restore window sizes -winrestview({dict}) none restore view of current window -winsaveview() Dict save view of current window -winwidth({nr}) Number width of window {nr} -wordcount() Dict get byte/char/word statistics -writefile({object}, {fname} [, {flags}]) - Number write |Blob| or |List| of lines to file -xor({expr}, {expr}) Number bitwise XOR - - -abs({expr}) *abs()* - Return the absolute value of {expr}. When {expr} evaluates to - a |Float| abs() returns a |Float|. When {expr} can be - converted to a |Number| abs() returns a |Number|. Otherwise - abs() gives an error message and returns -1. - Examples: > - echo abs(1.456) -< 1.456 > - echo abs(-5.456) -< 5.456 > - echo abs(-4) -< 4 - - Can also be used as a |method|: > - Compute()->abs() - -acos({expr}) *acos()* - Return the arc cosine of {expr} measured in radians, as a - |Float| in the range of [0, pi]. - {expr} must evaluate to a |Float| or a |Number| in the range - [-1, 1]. - Examples: > - :echo acos(0) -< 1.570796 > - :echo acos(-0.5) -< 2.094395 - - Can also be used as a |method|: > - Compute()->acos() - -add({object}, {expr}) *add()* - Append the item {expr} to |List| or |Blob| {object}. Returns - the resulting |List| or |Blob|. Examples: > - :let alist = add([1, 2, 3], item) - :call add(mylist, "woodstock") -< Note that when {expr} is a |List| it is appended as a single - item. Use |extend()| to concatenate |Lists|. - When {object} is a |Blob| then {expr} must be a number. - Use |insert()| to add an item at another position. - - Can also be used as a |method|: > - mylist->add(val1)->add(val2) - -and({expr}, {expr}) *and()* - Bitwise AND on the two arguments. The arguments are converted - to a number. A List, Dict or Float argument causes an error. - Example: > - :let flag = and(bits, 0x80) -< Can also be used as a |method|: > - :let flag = bits->and(0x80) - -api_info() *api_info()* - Returns Dictionary of |api-metadata|. - - View it in a nice human-readable format: > - :lua print(vim.inspect(vim.fn.api_info())) - -append({lnum}, {text}) *append()* - When {text} is a |List|: Append each item of the |List| as a - text line below line {lnum} in the current buffer. - Otherwise append {text} as one text line below line {lnum} in - the current buffer. - {lnum} can be zero to insert a line before the first one. - {lnum} is used like with |getline()|. - Returns 1 for failure ({lnum} out of range or out of memory), - 0 for success. Example: > - :let failed = append(line('$'), "# THE END") - :let failed = append(0, ["Chapter 1", "the beginning"]) - -< Can also be used as a |method| after a List: > - mylist->append(lnum) - -appendbufline({buf}, {lnum}, {text}) *appendbufline()* - Like |append()| but append the text in buffer {expr}. - - This function works only for loaded buffers. First call - |bufload()| if needed. - - For the use of {buf}, see |bufname()|. - - {lnum} is used like with |append()|. Note that using |line()| - would use the current buffer, not the one appending to. - Use "$" to append at the end of the buffer. - - On success 0 is returned, on failure 1 is returned. - - If {buf} is not a valid buffer or {lnum} is not valid, an - error message is given. Example: > - :let failed = appendbufline(13, 0, "# THE START") -< - Can also be used as a |method| after a List: > - mylist->appendbufline(buf, lnum) - -argc([{winid}]) *argc()* - The result is the number of files in the argument list. See - |arglist|. - If {winid} is not supplied, the argument list of the current - window is used. - If {winid} is -1, the global argument list is used. - Otherwise {winid} specifies the window of which the argument - list is used: either the window number or the window ID. - Returns -1 if the {winid} argument is invalid. - - *argidx()* -argidx() The result is the current index in the argument list. 0 is - the first file. argc() - 1 is the last one. See |arglist|. - - *arglistid()* -arglistid([{winnr} [, {tabnr}]]) - Return the argument list ID. This is a number which - identifies the argument list being used. Zero is used for the - global argument list. See |arglist|. - Returns -1 if the arguments are invalid. - - Without arguments use the current window. - With {winnr} only use this window in the current tab page. - With {winnr} and {tabnr} use the window in the specified tab - page. - {winnr} can be the window number or the |window-ID|. - - *argv()* -argv([{nr} [, {winid}]]) - The result is the {nr}th file in the argument list. See - |arglist|. "argv(0)" is the first one. Example: > - :let i = 0 - :while i < argc() - : let f = escape(fnameescape(argv(i)), '.') - : exe 'amenu Arg.' . f . ' :e ' . f . '<CR>' - : let i = i + 1 - :endwhile -< Without the {nr} argument, or when {nr} is -1, a |List| with - the whole |arglist| is returned. - - The {winid} argument specifies the window ID, see |argc()|. - For the Vim command line arguments see |v:argv|. - -asin({expr}) *asin()* - Return the arc sine of {expr} measured in radians, as a |Float| - in the range of [-pi/2, pi/2]. - {expr} must evaluate to a |Float| or a |Number| in the range - [-1, 1]. - Examples: > - :echo asin(0.8) -< 0.927295 > - :echo asin(-0.5) -< -0.523599 - - Can also be used as a |method|: > - Compute()->asin() - - -assert_ functions are documented here: |assert-functions-details| - - -atan({expr}) *atan()* - Return the principal value of the arc tangent of {expr}, in - the range [-pi/2, +pi/2] radians, as a |Float|. - {expr} must evaluate to a |Float| or a |Number|. - Examples: > - :echo atan(100) -< 1.560797 > - :echo atan(-4.01) -< -1.326405 - - Can also be used as a |method|: > - Compute()->atan() - -atan2({expr1}, {expr2}) *atan2()* - Return the arc tangent of {expr1} / {expr2}, measured in - radians, as a |Float| in the range [-pi, pi]. - {expr1} and {expr2} must evaluate to a |Float| or a |Number|. - Examples: > - :echo atan2(-1, 1) -< -0.785398 > - :echo atan2(1, -1) -< 2.356194 - - Can also be used as a |method|: > - Compute()->atan2(1) - - *browse()* -browse({save}, {title}, {initdir}, {default}) - Put up a file requester. This only works when "has("browse")" - returns |TRUE| (only in some GUI versions). - The input fields are: - {save} when |TRUE|, select file to write - {title} title for the requester - {initdir} directory to start browsing in - {default} default file name - An empty string is returned when the "Cancel" button is hit, - something went wrong, or browsing is not possible. - - *browsedir()* -browsedir({title}, {initdir}) - Put up a directory requester. This only works when - "has("browse")" returns |TRUE| (only in some GUI versions). - On systems where a directory browser is not supported a file - browser is used. In that case: select a file in the directory - to be used. - The input fields are: - {title} title for the requester - {initdir} directory to start browsing in - When the "Cancel" button is hit, something went wrong, or - browsing is not possible, an empty string is returned. - -bufadd({name}) *bufadd()* - Add a buffer to the buffer list with String {name}. - If a buffer for file {name} already exists, return that buffer - number. Otherwise return the buffer number of the newly - created buffer. When {name} is an empty string then a new - buffer is always created. - The buffer will not have 'buflisted' set and not be loaded - yet. To add some text to the buffer use this: > - let bufnr = bufadd('someName') - call bufload(bufnr) - call setbufline(bufnr, 1, ['some', 'text']) -< Can also be used as a |method|: > - let bufnr = 'somename'->bufadd() - -bufexists({buf}) *bufexists()* - The result is a Number, which is |TRUE| if a buffer called - {buf} exists. - If the {buf} argument is a number, buffer numbers are used. - Number zero is the alternate buffer for the current window. - - If the {buf} argument is a string it must match a buffer name - exactly. The name can be: - - Relative to the current directory. - - A full path. - - The name of a buffer with 'buftype' set to "nofile". - - A URL name. - Unlisted buffers will be found. - Note that help files are listed by their short name in the - output of |:buffers|, but bufexists() requires using their - long name to be able to find them. - bufexists() may report a buffer exists, but to use the name - with a |:buffer| command you may need to use |expand()|. Esp - for MS-Windows 8.3 names in the form "c:\DOCUME~1" - Use "bufexists(0)" to test for the existence of an alternate - file name. - - Can also be used as a |method|: > - let exists = 'somename'->bufexists() - -buflisted({buf}) *buflisted()* - The result is a Number, which is |TRUE| if a buffer called - {buf} exists and is listed (has the 'buflisted' option set). - The {buf} argument is used like with |bufexists()|. - - Can also be used as a |method|: > - let listed = 'somename'->buflisted() - -bufload({buf}) *bufload()* - Ensure the buffer {buf} is loaded. When the buffer name - refers to an existing file then the file is read. Otherwise - the buffer will be empty. If the buffer was already loaded - then there is no change. - If there is an existing swap file for the file of the buffer, - there will be no dialog, the buffer will be loaded anyway. - The {buf} argument is used like with |bufexists()|. - - Can also be used as a |method|: > - eval 'somename'->bufload() - -bufloaded({buf}) *bufloaded()* - The result is a Number, which is |TRUE| if a buffer called - {buf} exists and is loaded (shown in a window or hidden). - The {buf} argument is used like with |bufexists()|. - - Can also be used as a |method|: > - let loaded = 'somename'->bufloaded() - -bufname([{buf}]) *bufname()* - The result is the name of a buffer. Mostly as it is displayed - by the `:ls` command, but not using special names such as - "[No Name]". - If {buf} is omitted the current buffer is used. - If {buf} is a Number, that buffer number's name is given. - Number zero is the alternate buffer for the current window. - If {buf} is a String, it is used as a |file-pattern| to match - with the buffer names. This is always done like 'magic' is - set and 'cpoptions' is empty. When there is more than one - match an empty string is returned. - "" or "%" can be used for the current buffer, "#" for the - alternate buffer. - A full match is preferred, otherwise a match at the start, end - or middle of the buffer name is accepted. If you only want a - full match then put "^" at the start and "$" at the end of the - pattern. - Listed buffers are found first. If there is a single match - with a listed buffer, that one is returned. Next unlisted - buffers are searched for. - If the {buf} is a String, but you want to use it as a buffer - number, force it to be a Number by adding zero to it: > - :echo bufname("3" + 0) -< Can also be used as a |method|: > - echo bufnr->bufname() - -< If the buffer doesn't exist, or doesn't have a name, an empty - string is returned. > - bufname("#") alternate buffer name - bufname(3) name of buffer 3 - bufname("%") name of current buffer - bufname("file2") name of buffer where "file2" matches. -< - *bufnr()* -bufnr([{buf} [, {create}]]) - The result is the number of a buffer, as it is displayed by - the `:ls` command. For the use of {buf}, see |bufname()| - above. - If the buffer doesn't exist, -1 is returned. Or, if the - {create} argument is present and TRUE, a new, unlisted, - buffer is created and its number is returned. - bufnr("$") is the last buffer: > - :let last_buffer = bufnr("$") -< The result is a Number, which is the highest buffer number - of existing buffers. Note that not all buffers with a smaller - number necessarily exist, because ":bwipeout" may have removed - them. Use bufexists() to test for the existence of a buffer. - - Can also be used as a |method|: > - echo bufref->bufnr() - -bufwinid({buf}) *bufwinid()* - The result is a Number, which is the |window-ID| of the first - window associated with buffer {buf}. For the use of {buf}, - see |bufname()| above. If buffer {buf} doesn't exist or - there is no such window, -1 is returned. Example: > - - echo "A window containing buffer 1 is " . (bufwinid(1)) -< - Only deals with the current tab page. - - Can also be used as a |method|: > - FindBuffer()->bufwinid() - -bufwinnr({buf}) *bufwinnr()* - Like |bufwinid()| but return the window number instead of the - |window-ID|. - If buffer {buf} doesn't exist or there is no such window, -1 - is returned. Example: > - - echo "A window containing buffer 1 is " . (bufwinnr(1)) - -< The number can be used with |CTRL-W_w| and ":wincmd w" - |:wincmd|. - - Can also be used as a |method|: > - FindBuffer()->bufwinnr() - -byte2line({byte}) *byte2line()* - Return the line number that contains the character at byte - count {byte} in the current buffer. This includes the - end-of-line character, depending on the 'fileformat' option - for the current buffer. The first character has byte count - one. - Also see |line2byte()|, |go| and |:goto|. - - Can also be used as a |method|: > - GetOffset()->byte2line() - -byteidx({expr}, {nr}) *byteidx()* - Return byte index of the {nr}'th character in the String - {expr}. Use zero for the first character, it then returns - zero. - If there are no multibyte characters the returned value is - equal to {nr}. - Composing characters are not counted separately, their byte - length is added to the preceding base character. See - |byteidxcomp()| below for counting composing characters - separately. - Example : > - echo matchstr(str, ".", byteidx(str, 3)) -< will display the fourth character. Another way to do the - same: > - let s = strpart(str, byteidx(str, 3)) - echo strpart(s, 0, byteidx(s, 1)) -< Also see |strgetchar()| and |strcharpart()|. - - If there are less than {nr} characters -1 is returned. - If there are exactly {nr} characters the length of the string - in bytes is returned. - - Can also be used as a |method|: > - GetName()->byteidx(idx) - -byteidxcomp({expr}, {nr}) *byteidxcomp()* - Like byteidx(), except that a composing character is counted - as a separate character. Example: > - let s = 'e' . nr2char(0x301) - echo byteidx(s, 1) - echo byteidxcomp(s, 1) - echo byteidxcomp(s, 2) -< The first and third echo result in 3 ('e' plus composing - character is 3 bytes), the second echo results in 1 ('e' is - one byte). - Only works differently from byteidx() when 'encoding' is set to - a Unicode encoding. - - Can also be used as a |method|: > - GetName()->byteidxcomp(idx) - -call({func}, {arglist} [, {dict}]) *call()* *E699* - Call function {func} with the items in |List| {arglist} as - arguments. - {func} can either be a |Funcref| or the name of a function. - a:firstline and a:lastline are set to the cursor line. - Returns the return value of the called function. - {dict} is for functions with the "dict" attribute. It will be - used to set the local variable "self". |Dictionary-function| - - Can also be used as a |method|: > - GetFunc()->call([arg, arg], dict) - -ceil({expr}) *ceil()* - Return the smallest integral value greater than or equal to - {expr} as a |Float| (round up). - {expr} must evaluate to a |Float| or a |Number|. - Examples: > - echo ceil(1.456) -< 2.0 > - echo ceil(-5.456) -< -5.0 > - echo ceil(4.0) -< 4.0 - - Can also be used as a |method|: > - Compute()->ceil() - -changenr() *changenr()* - Return the number of the most recent change. This is the same - number as what is displayed with |:undolist| and can be used - with the |:undo| command. - When a change was made it is the number of that change. After - 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()* - 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 - with `"rpc":v:true`) If {stream} is omitted, all streams - are closed. If the channel is a pty, this will then close the - pty master, sending SIGHUP to the job process. - For a socket, there is only one stream, and {stream} should be - ommited. - -chansend({id}, {data}) *chansend()* - Send data to channel {id}. For a job, it writes it to the - stdin of the process. For the stdio channel |channel-stdio|, - it writes to Nvim's stdout. Returns the number of bytes - written if the write succeeded, 0 otherwise. - See |channel-bytes| for more information. - - {data} may be a string, string convertible, |Blob|, or a list. - If {data} is a list, the items will be joined by newlines; any - newlines in an item will be sent as NUL. To send a final - newline, include a final empty string. Example: > - :call chansend(id, ["abc", "123\n456", ""]) -< will send "abc<NL>123<NUL>456<NL>". - - chansend() writes raw data, not RPC messages. If the channel - was created with `"rpc":v:true` then the channel expects RPC - messages, use |rpcnotify()| and |rpcrequest()| instead. - - -char2nr({string} [, {utf8}]) *char2nr()* - Return number value of the first char in {string}. - Examples: > - char2nr(" ") returns 32 - char2nr("ABC") returns 65 - char2nr("á") returns 225 - char2nr("á"[0]) returns 195 - char2nr("\<M-x>") returns 128 -< Non-ASCII characters are always treated as UTF-8 characters. - {utf8} is ignored, it exists only for backwards-compatibility. - A combining character is a separate character. - |nr2char()| does the opposite. - - Can also be used as a |method|: > - GetChar()->char2nr() -< - *charidx()* -charidx({string}, {idx} [, {countcc}]) - Return the character index of the byte at {idx} in {string}. - The index of the first character is zero. - If there are no multibyte characters the returned value is - equal to {idx}. - When {countcc} is omitted or |FALSE|, then composing characters - are not counted separately, their byte length is - added to the preceding base character. - When {countcc} is |TRUE|, then composing characters are - counted as separate characters. - Returns -1 if the arguments are invalid or if {idx} is greater - than the index of the last byte in {string}. An error is - given if the first argument is not a string, the second - argument is not a number or when the third argument is present - and is not zero or one. - See |byteidx()| and |byteidxcomp()| for getting the byte index - from the character index. - Examples: > - echo charidx('áb́ć', 3) returns 1 - echo charidx('áb́ć', 6, 1) returns 4 - echo charidx('áb́ć', 16) returns -1 -< - Can also be used as a |method|: > - GetName()->charidx(idx) - -chdir({dir}) *chdir()* - Change the current working directory to {dir}. The scope of - the directory change depends on the directory of the current - window: - - If the current window has a window-local directory - (|:lcd|), then changes the window local directory. - - Otherwise, if the current tabpage has a local - directory (|:tcd|) then changes the tabpage local - directory. - - Otherwise, changes the global directory. - If successful, returns the previous working directory. Pass - this to another chdir() to restore the directory. - On failure, returns an empty string. - - Example: > - let save_dir = chdir(newdir) - if save_dir - " ... do some work - call chdir(save_dir) - endif -< -cindent({lnum}) *cindent()* - Get the amount of indent for line {lnum} according the C - indenting rules, as with 'cindent'. - The indent is counted in spaces, the value of 'tabstop' is - relevant. {lnum} is used just like in |getline()|. - When {lnum} is invalid -1 is returned. - See |C-indenting|. - - Can also be used as a |method|: > - GetLnum()->cindent() - -clearmatches([{win}]) *clearmatches()* - Clears all matches previously defined for the current window - by |matchadd()| and the |:match| commands. - If {win} is specified, use the window with this number or - window ID instead of the current window. - - Can also be used as a |method|: > - GetWin()->clearmatches() -< - *col()* -col({expr}) The result is a Number, which is the byte index of the column - position given with {expr}. The accepted positions are: - . the cursor position - $ the end of the cursor line (the result is the - number of bytes in the cursor line plus one) - 'x position of mark x (if the mark is not set, 0 is - returned) - v In Visual mode: the start of the Visual area (the - cursor is the end). When not in Visual mode - returns the cursor position. Differs from |'<| in - that it's updated right away. - Additionally {expr} can be [lnum, col]: a |List| with the line - and column number. Most useful when the column is "$", to get - the last column of a specific line. When "lnum" or "col" is - out of range then col() returns zero. - To get the line number use |line()|. To get both use - |getpos()|. - For the screen column position use |virtcol()|. - Note that only marks in the current file can be used. - Examples: > - col(".") column of cursor - col("$") length of cursor line plus one - col("'t") column of mark t - col("'" . markname) column of mark markname -< The first column is 1. 0 is returned for an error. - For an uppercase mark the column may actually be in another - buffer. - For the cursor position, when 'virtualedit' is active, the - column is one higher if the cursor is after the end of the - line. This can be used to obtain the column in Insert mode: > - :imap <F2> <C-O>:let save_ve = &ve<CR> - \<C-O>:set ve=all<CR> - \<C-O>:echo col(".") . "\n" <Bar> - \let &ve = save_ve<CR> - -< Can also be used as a |method|: > - GetPos()->col() -< - -complete({startcol}, {matches}) *complete()* *E785* - Set the matches for Insert mode completion. - Can only be used in Insert mode. You need to use a mapping - with CTRL-R = (see |i_CTRL-R|). It does not work after CTRL-O - or with an expression mapping. - {startcol} is the byte offset in the line where the completed - text start. The text up to the cursor is the original text - that will be replaced by the matches. Use col('.') for an - empty string. "col('.') - 1" will replace one character by a - match. - {matches} must be a |List|. Each |List| item is one match. - See |complete-items| for the kind of items that are possible. - "longest" in 'completeopt' is ignored. - Note that the after calling this function you need to avoid - inserting anything that would cause completion to stop. - The match can be selected with CTRL-N and CTRL-P as usual with - Insert mode completion. The popup menu will appear if - specified, see |ins-completion-menu|. - Example: > - inoremap <F5> <C-R>=ListMonths()<CR> - - func! ListMonths() - call complete(col('.'), ['January', 'February', 'March', - \ 'April', 'May', 'June', 'July', 'August', 'September', - \ 'October', 'November', 'December']) - return '' - endfunc -< This isn't very useful, but it shows how it works. Note that - an empty string is returned to avoid a zero being inserted. - - Can also be used as a |method|, the base is passed as the - second argument: > - GetMatches()->complete(col('.')) - -complete_add({expr}) *complete_add()* - Add {expr} to the list of matches. Only to be used by the - function specified with the 'completefunc' option. - Returns 0 for failure (empty string or out of memory), - 1 when the match was added, 2 when the match was already in - the list. - See |complete-functions| for an explanation of {expr}. It is - the same as one item in the list that 'omnifunc' would return. - - Can also be used as a |method|: > - GetMoreMatches()->complete_add() - -complete_check() *complete_check()* - Check for a key typed while looking for completion matches. - This is to be used when looking for matches takes some time. - Returns |TRUE| when searching for matches is to be aborted, - zero otherwise. - Only to be used by the function specified with the - 'completefunc' option. - - -complete_info([{what}]) *complete_info()* - Returns a |Dictionary| with information about Insert mode - completion. See |ins-completion|. - The items are: - mode Current completion mode name string. - See |complete_info_mode| for the values. - pum_visible |TRUE| if popup menu is visible. - See |pumvisible()|. - items List of completion matches. Each item is a - dictionary containing the entries "word", - "abbr", "menu", "kind", "info" and "user_data". - See |complete-items|. - selected Selected item index. First index is zero. - Index is -1 if no item is selected (showing - typed text only, or the last completion after - no item is selected when using the <Up> or - <Down> keys) - inserted Inserted string. [NOT IMPLEMENT YET] - - *complete_info_mode* - mode values are: - "" Not in completion mode - "keyword" Keyword completion |i_CTRL-X_CTRL-N| - "ctrl_x" Just pressed CTRL-X |i_CTRL-X| - "scroll" Scrolling with |i_CTRL-X_CTRL-E| or - |i_CTRL-X_CTRL-Y| - "whole_line" Whole lines |i_CTRL-X_CTRL-L| - "files" File names |i_CTRL-X_CTRL-F| - "tags" Tags |i_CTRL-X_CTRL-]| - "path_defines" Definition completion |i_CTRL-X_CTRL-D| - "path_patterns" Include completion |i_CTRL-X_CTRL-I| - "dictionary" Dictionary |i_CTRL-X_CTRL-K| - "thesaurus" Thesaurus |i_CTRL-X_CTRL-T| - "cmdline" Vim Command line |i_CTRL-X_CTRL-V| - "function" User defined completion |i_CTRL-X_CTRL-U| - "omni" Omni completion |i_CTRL-X_CTRL-O| - "spell" Spelling suggestions |i_CTRL-X_s| - "eval" |complete()| completion - "unknown" Other internal modes - - If the optional {what} list argument is supplied, then only - the items listed in {what} are returned. Unsupported items in - {what} are silently ignored. - - To get the position and size of the popup menu, see - |pum_getpos()|. It's also available in |v:event| during the - |CompleteChanged| event. - - Examples: > - " Get all items - call complete_info() - " Get only 'mode' - call complete_info(['mode']) - " Get only 'mode' and 'pum_visible' - call complete_info(['mode', 'pum_visible']) - -< Can also be used as a |method|: > - GetItems()->complete_info() -< - *confirm()* -confirm({msg} [, {choices} [, {default} [, {type}]]]) - Confirm() offers the user a dialog, from which a choice can be - made. It returns the number of the choice. For the first - choice this is 1. - - {msg} is displayed in a dialog with {choices} as the - alternatives. When {choices} is missing or empty, "&OK" is - used (and translated). - {msg} is a String, use '\n' to include a newline. Only on - some systems the string is wrapped when it doesn't fit. - - {choices} is a String, with the individual choices separated - by '\n', e.g. > - confirm("Save changes?", "&Yes\n&No\n&Cancel") -< The letter after the '&' is the shortcut key for that choice. - Thus you can type 'c' to select "Cancel". The shortcut does - not need to be the first letter: > - confirm("file has been modified", "&Save\nSave &All") -< For the console, the first letter of each choice is used as - the default shortcut key. Case is ignored. - - The optional {type} String argument gives the type of dialog. - It can be one of these values: "Error", "Question", "Info", - "Warning" or "Generic". Only the first character is relevant. - When {type} is omitted, "Generic" is used. - - The optional {type} argument gives the type of dialog. This - is only used for the icon of the Win32 GUI. It can be one of - these values: "Error", "Question", "Info", "Warning" or - "Generic". Only the first character is relevant. - When {type} is omitted, "Generic" is used. - - If the user aborts the dialog by pressing <Esc>, CTRL-C, - or another valid interrupt key, confirm() returns 0. - - An example: > - :let choice = confirm("What do you want?", "&Apples\n&Oranges\n&Bananas", 2) - :if choice == 0 - : echo "make up your mind!" - :elseif choice == 3 - : echo "tasteful" - :else - : echo "I prefer bananas myself." - :endif -< In a GUI dialog, buttons are used. The layout of the buttons - depends on the 'v' flag in 'guioptions'. If it is included, - the buttons are always put vertically. Otherwise, confirm() - tries to put the buttons in one horizontal line. If they - don't fit, a vertical layout is used anyway. For some systems - the horizontal layout is always used. - - Can also be used as a |method|in: > - BuildMessage()->confirm("&Yes\n&No") -< - *copy()* -copy({expr}) Make a copy of {expr}. For Numbers and Strings this isn't - different from using {expr} directly. - When {expr} is a |List| a shallow copy is created. This means - that the original |List| can be changed without changing the - copy, and vice versa. But the items are identical, thus - changing an item changes the contents of both |Lists|. - A |Dictionary| is copied in a similar way as a |List|. - Also see |deepcopy()|. - Can also be used as a |method|: > - mylist->copy() - -cos({expr}) *cos()* - Return the cosine of {expr}, measured in radians, as a |Float|. - {expr} must evaluate to a |Float| or a |Number|. - Examples: > - :echo cos(100) -< 0.862319 > - :echo cos(-4.01) -< -0.646043 - - Can also be used as a |method|: > - Compute()->cos() - -cosh({expr}) *cosh()* - Return the hyperbolic cosine of {expr} as a |Float| in the range - [1, inf]. - {expr} must evaluate to a |Float| or a |Number|. - Examples: > - :echo cosh(0.5) -< 1.127626 > - :echo cosh(-0.5) -< -1.127626 - - Can also be used as a |method|: > - Compute()->cosh() - -count({comp}, {expr} [, {ic} [, {start}]]) *count()* - Return the number of times an item with value {expr} appears - in |String|, |List| or |Dictionary| {comp}. - - If {start} is given then start with the item with this index. - {start} can only be used with a |List|. - - When {ic} is given and it's |TRUE| then case is ignored. - - When {comp} is a string then the number of not overlapping - occurrences of {expr} is returned. Zero is returned when - {expr} is an empty string. - - Can also be used as a |method|: > - mylist->count(val) -< - *cscope_connection()* -cscope_connection([{num} , {dbpath} [, {prepend}]]) - Checks for the existence of a |cscope| connection. If no - parameters are specified, then the function returns: - 0, if there are no cscope connections; - 1, if there is at least one cscope connection. - - If parameters are specified, then the value of {num} - determines how existence of a cscope connection is checked: - - {num} Description of existence check - ----- ------------------------------ - 0 Same as no parameters (e.g., "cscope_connection()"). - 1 Ignore {prepend}, and use partial string matches for - {dbpath}. - 2 Ignore {prepend}, and use exact string matches for - {dbpath}. - 3 Use {prepend}, use partial string matches for both - {dbpath} and {prepend}. - 4 Use {prepend}, use exact string matches for both - {dbpath} and {prepend}. - - Note: All string comparisons are case sensitive! - - Examples. Suppose we had the following (from ":cs show"): > - - # pid database name prepend path - 0 27664 cscope.out /usr/local -< - Invocation Return Val ~ - ---------- ---------- > - cscope_connection() 1 - cscope_connection(1, "out") 1 - cscope_connection(2, "out") 0 - cscope_connection(3, "out") 0 - cscope_connection(3, "out", "local") 1 - cscope_connection(4, "out") 0 - cscope_connection(4, "out", "local") 0 - cscope_connection(4, "cscope.out", "/usr/local") 1 -< - -ctxget([{index}]) *ctxget()* - Returns a |Dictionary| representing the |context| at {index} - from the top of the |context-stack| (see |context-dict|). - If {index} is not given, it is assumed to be 0 (i.e.: top). - -ctxpop() *ctxpop()* - Pops and restores the |context| at the top of the - |context-stack|. - -ctxpush([{types}]) *ctxpush()* - Pushes the current editor state (|context|) on the - |context-stack|. - If {types} is given and is a |List| of |String|s, it specifies - which |context-types| to include in the pushed context. - Otherwise, all context types are included. - -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|). - If {index} is not given, it is assumed to be 0 (i.e.: top). - -ctxsize() *ctxsize()* - Returns the size of the |context-stack|. - -cursor({lnum}, {col} [, {off}]) *cursor()* -cursor({list}) - Positions the cursor at the column (byte count) {col} in the - line {lnum}. The first column is one. - - When there is one argument {list} this is used as a |List| - with two, three or four item: - [{lnum}, {col}] - [{lnum}, {col}, {off}] - [{lnum}, {col}, {off}, {curswant}] - This is like the return value of |getpos()| or |getcurpos()|, - but without the first item. - - Does not change the jumplist. - If {lnum} is greater than the number of lines in the buffer, - the cursor will be positioned at the last line in the buffer. - If {lnum} is zero, the cursor will stay in the current line. - If {col} is greater than the number of bytes in the line, - the cursor will be positioned at the last character in the - line. - If {col} is zero, the cursor will stay in the current column. - If {curswant} is given it is used to set the preferred column - for vertical movement. Otherwise {col} is used. - - When 'virtualedit' is used {off} specifies the offset in - screen columns from the start of the character. E.g., a - position within a <Tab> or after the last character. - Returns 0 when the position could be set, -1 otherwise. - - Can also be used as a |method|: > - GetCursorPos()->cursor() - -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 - that the original |List| can be changed without changing the - copy, and vice versa. When an item is a |List|, a copy for it - is made, recursively. Thus changing an item in the copy does - not change the contents of the original |List|. - - When {noref} is omitted or zero a contained |List| or - |Dictionary| is only copied once. All references point to - this single copy. With {noref} set to 1 every occurrence of a - |List| or |Dictionary| results in a new copy. This also means - that a cyclic reference causes deepcopy() to fail. - *E724* - Nesting is possible up to 100 levels. When there is an item - that refers back to a higher level making a deep copy with - {noref} set to 1 will fail. - Also see |copy()|. - - Can also be used as a |method|: > - GetObject()->deepcopy() - -delete({fname} [, {flags}]) *delete()* - Without {flags} or with {flags} empty: Deletes the file by the - name {fname}. This also works when {fname} is a symbolic link. - A symbolic link itself is deleted, not what it points to. - - When {flags} is "d": Deletes the directory by the name - {fname}. This fails when directory {fname} is not empty. - - When {flags} is "rf": Deletes the directory by the name - {fname} and everything in it, recursively. BE CAREFUL! - Note: on MS-Windows it is not possible to delete a directory - that is being used. - - The result is a Number, which is 0/false if the delete - operation was successful and -1/true when the deletion failed - or partly failed. - - Can also be used as a |method|: > - GetName()->delete() - -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. - - This function works only for loaded buffers. First call - |bufload()| if needed. - - For the use of {buf}, see |bufname()| above. - - {first} and {last} are used like with |setline()|. Note that - when using |line()| this refers to the current buffer. Use "$" - to refer to the last line in buffer {buf}. - - Can also be used as a |method|: > - GetBuffer()->deletebufline(1) -< -dictwatcheradd({dict}, {pattern}, {callback}) *dictwatcheradd()* - Adds a watcher to a dictionary. A dictionary watcher is - identified by three components: - - - A dictionary({dict}); - - A key pattern({pattern}). - - A function({callback}). - - After this is called, every change on {dict} and on keys - matching {pattern} will result in {callback} being invoked. - - For example, to watch all global variables: > - silent! call dictwatcherdel(g:, '*', 'OnDictChanged') - function! OnDictChanged(d,k,z) - echomsg string(a:k) string(a:z) - endfunction - call dictwatcheradd(g:, '*', 'OnDictChanged') -< - For now {pattern} only accepts very simple patterns that can - contain a '*' at the end of the string, in which case it will - match every key that begins with the substring before the '*'. - That means if '*' is not the last character of {pattern}, only - keys that are exactly equal as {pattern} will be matched. - - The {callback} receives three arguments: - - - The dictionary being watched. - - The key which changed. - - A dictionary containing the new and old values for the key. - - The type of change can be determined by examining the keys - present on the third argument: - - - If contains both `old` and `new`, the key was updated. - - If it contains only `new`, the key was added. - - If it contains only `old`, the key was deleted. - - This function can be used by plugins to implement options with - validation and parsing logic. - -dictwatcherdel({dict}, {pattern}, {callback}) *dictwatcherdel()* - Removes a watcher added with |dictwatcheradd()|. All three - arguments must match the ones passed to |dictwatcheradd()| in - order for the watcher to be successfully deleted. - - *did_filetype()* -did_filetype() Returns |TRUE| when autocommands are being executed and the - FileType event has been triggered at least once. Can be used - to avoid triggering the FileType event again in the scripts - that detect the file type. |FileType| - Returns |FALSE| when `:setf FALLBACK` was used. - When editing another file, the counter is reset, thus this - really checks if the FileType event has been triggered for the - current buffer. This allows an autocommand that starts - editing another buffer to set 'filetype' and load a syntax - file. - -diff_filler({lnum}) *diff_filler()* - Returns the number of filler lines above line {lnum}. - These are the lines that were inserted at this point in - another diff'ed window. These filler lines are shown in the - display but don't exist in the buffer. - {lnum} is used like with |getline()|. Thus "." is the current - line, "'m" mark m, etc. - Returns 0 if the current window is not in diff mode. - - Can also be used as a |method|: > - GetLnum()->diff_filler() - -diff_hlID({lnum}, {col}) *diff_hlID()* - Returns the highlight ID for diff mode at line {lnum} column - {col} (byte index). When the current line does not have a - diff change zero is returned. - {lnum} is used like with |getline()|. Thus "." is the current - line, "'m" mark m, etc. - {col} is 1 for the leftmost column, {lnum} is 1 for the first - line. - The highlight ID can be used with |synIDattr()| to obtain - syntax information about the highlighting. - - Can also be used as a |method|: > - GetLnum()->diff_hlID(col) - -empty({expr}) *empty()* - Return the Number 1 if {expr} is empty, zero otherwise. - - A |List| or |Dictionary| is empty when it does not have any - items. - - A |String| is empty when its length is zero. - - A |Number| and |Float| are empty when their value is zero. - - |v:false| and |v:null| are empty, |v:true| is not. - - A |Blob| is empty when its length is zero. - - Can also be used as a |method|: > - mylist->empty() - -environ() *environ()* - Return all of environment variables as dictionary. You can - check if an environment variable exists like this: > - :echo has_key(environ(), 'HOME') -< Note that the variable name may be CamelCase; to ignore case - use this: > - :echo index(keys(environ()), 'HOME', 0, 1) != -1 - -escape({string}, {chars}) *escape()* - Escape the characters in {chars} that occur in {string} with a - backslash. Example: > - :echo escape('c:\program files\vim', ' \') -< results in: > - c:\\program\ files\\vim -< Also see |shellescape()| and |fnameescape()|. - - Can also be used as a |method|: > - GetText()->escape(' \') -< - *eval()* -eval({string}) Evaluate {string} and return the result. Especially useful to - turn the result of |string()| back into the original value. - This works for Numbers, Floats, Strings, Blobs and composites - of them. Also works for |Funcref|s that refer to existing - functions. - - Can also be used as a |method|: > - argv->join()->eval() - -eventhandler() *eventhandler()* - Returns 1 when inside an event handler. That is that Vim got - interrupted while waiting for the user to type a character, - e.g., when dropping a file on Vim. This means interactive - commands cannot be used. Otherwise zero is returned. - -executable({expr}) *executable()* - This function checks if an executable with the name {expr} - exists. {expr} must be the name of the program without any - arguments. - executable() uses the value of $PATH and/or the normal - searchpath for programs. *PATHEXT* - On MS-Windows the ".exe", ".bat", etc. can optionally be - included. Then the extensions in $PATHEXT are tried. Thus if - "foo.exe" does not exist, "foo.exe.bat" can be found. If - $PATHEXT is not set then ".exe;.com;.bat;.cmd" is used. A dot - by itself can be used in $PATHEXT to try using the name - without an extension. When 'shell' looks like a Unix shell, - then the name is also tried without adding an extension. - On MS-Windows it only checks if the file exists and is not a - directory, not if it's really executable. - On Windows an executable in the same directory as Vim is - always found (it is added to $PATH at |startup|). - The result is a Number: - 1 exists - 0 does not exist - -1 not implemented on this system - |exepath()| can be used to get the full path of an executable. - - Can also be used as a |method|: > - GetCommand()->executable() - -execute({command} [, {silent}]) *execute()* - Execute {command} and capture its output. - If {command} is a |String|, returns {command} output. - If {command} is a |List|, returns concatenated outputs. - Examples: > - echo execute('echon "foo"') -< foo > - echo execute(['echon "foo"', 'echon "bar"']) -< foobar - - The optional {silent} argument can have these values: - "" no `:silent` used - "silent" `:silent` used - "silent!" `:silent!` used - The default is "silent". Note that with "silent!", unlike - `:redir`, error messages are dropped. - - To get a list of lines use |split()| on the result: > - split(execute('args'), "\n") - -< This function is not available in the |sandbox|. - Note: If nested, an outer execute() will not observe output of - the inner calls. - Note: Text attributes (highlights) are not captured. - To execute a command in another window than the current one - use `win_execute()`. - - Can also be used as a |method|: > - GetCommand()->execute() - -exepath({expr}) *exepath()* - Returns the full path of {expr} if it is an executable and - given as a (partial or full) path or is found in $PATH. - Returns empty string otherwise. - If {expr} starts with "./" the |current-directory| is used. - - Can also be used as a |method|: > - GetCommand()->exepath() -< - *exists()* -exists({expr}) The result is a Number, which is |TRUE| if {expr} is - defined, zero otherwise. - - For checking for a supported feature use |has()|. - For checking if a file exists use |filereadable()|. - - The {expr} argument is a string, which contains one of these: - &option-name Vim option (only checks if it exists, - not if it really works) - +option-name Vim option that works. - $ENVNAME environment variable (could also be - done by comparing with an empty - string) - *funcname built-in function (see |functions|) - or user defined function (see - |user-function|). Also works for a - variable that is a Funcref. - varname internal variable (see - |internal-variables|). Also works - for |curly-braces-names|, |Dictionary| - entries, |List| items, etc. Beware - that evaluating an index may cause an - error message for an invalid - expression. E.g.: > - :let l = [1, 2, 3] - :echo exists("l[5]") -< 0 > - :echo exists("l[xx]") -< E121: Undefined variable: xx - 0 - :cmdname Ex command: built-in command, user - command or command modifier |:command|. - Returns: - 1 for match with start of a command - 2 full match with a command - 3 matches several user commands - To check for a supported command - always check the return value to be 2. - :2match The |:2match| command. - :3match The |:3match| command. - #event autocommand defined for this event - #event#pattern autocommand defined for this event and - pattern (the pattern is taken - literally and compared to the - autocommand patterns character by - character) - #group autocommand group exists - #group#event autocommand defined for this group and - event. - #group#event#pattern - autocommand defined for this group, - event and pattern. - ##event autocommand for this event is - supported. - - Examples: > - exists("&mouse") - exists("$HOSTNAME") - exists("*strftime") - exists("*s:MyFunc") - exists("bufcount") - exists(":Make") - exists("#CursorHold") - exists("#BufReadPre#*.gz") - exists("#filetypeindent") - exists("#filetypeindent#FileType") - exists("#filetypeindent#FileType#*") - exists("##ColorScheme") -< There must be no space between the symbol (&/$/*/#) and the - name. - There must be no extra characters after the name, although in - a few cases this is ignored. That may become more strict in - the future, thus don't count on it! - Working example: > - exists(":make") -< NOT working example: > - exists(":make install") - -< Note that the argument must be a string, not the name of the - variable itself. For example: > - exists(bufcount) -< This doesn't check for existence of the "bufcount" variable, - but gets the value of "bufcount", and checks if that exists. - - Can also be used as a |method|: > - Varname()->exists() - -exp({expr}) *exp()* - Return the exponential of {expr} as a |Float| in the range - [0, inf]. - {expr} must evaluate to a |Float| or a |Number|. - Examples: > - :echo exp(2) -< 7.389056 > - :echo exp(-1) -< 0.367879 - - Can also be used as a |method|: > - Compute()->exp() - -debugbreak({pid}) *debugbreak()* - Specifically used to interrupt a program being debugged. It - will cause process {pid} to get a SIGTRAP. Behavior for other - processes is undefined. See |terminal-debugger|. - {Sends a SIGINT to a process {pid} other than MS-Windows} - - Can also be used as a |method|: > - GetPid()->debugbreak() - -expand({string} [, {nosuf} [, {list}]]) *expand()* - Expand wildcards and the following special keywords in - {string}. 'wildignorecase' applies. - - If {list} is given and it is |TRUE|, a List will be returned. - Otherwise the result is a String and when there are several - matches, they are separated by <NL> characters. - - If the expansion fails, the result is an empty string. A name - for a non-existing file is not included, unless {string} does - not start with '%', '#' or '<', see below. - - When {string} starts with '%', '#' or '<', the expansion is - done like for the |cmdline-special| variables with their - associated modifiers. Here is a short overview: - - % current file name - # alternate file name - #n alternate file name n - <cfile> file name under the cursor - <afile> autocmd file name - <abuf> autocmd buffer number (as a String!) - <amatch> autocmd matched name - <sfile> sourced script file or function name - <slnum> sourced script line number or function - line number - <sflnum> script file line number, also when in - a function - <SID> "<SNR>123_" where "123" is the - current script ID |<SID>| - <cword> word under the cursor - <cWORD> WORD under the cursor - <client> the {clientid} of the last received - message |server2client()| - Modifiers: - :p expand to full path - :h head (last path component removed) - :t tail (last path component only) - :r root (one extension removed) - :e extension only - - Example: > - :let &tags = expand("%:p:h") . "/tags" -< Note that when expanding a string that starts with '%', '#' or - '<', any following text is ignored. This does NOT work: > - :let doesntwork = expand("%:h.bak") -< Use this: > - :let doeswork = expand("%:h") . ".bak" -< Also note that expanding "<cfile>" and others only returns the - referenced file name without further expansion. If "<cfile>" - is "~/.cshrc", you need to do another expand() to have the - "~/" expanded into the path of the home directory: > - :echo expand(expand("<cfile>")) -< - There cannot be white space between the variables and the - following modifier. The |fnamemodify()| function can be used - to modify normal file names. - - When using '%' or '#', and the current or alternate file name - is not defined, an empty string is used. Using "%:p" in a - buffer with no name, results in the current directory, with a - '/' added. - - When {string} does not start with '%', '#' or '<', it is - expanded like a file name is expanded on the command line. - 'suffixes' and 'wildignore' are used, unless the optional - {nosuf} argument is given and it is |TRUE|. - Names for non-existing files are included. The "**" item can - be used to search in a directory tree. For example, to find - all "README" files in the current directory and below: > - :echo expand("**/README") -< - expand() can also be used to expand variables and environment - variables that are only known in a shell. But this can be - slow, because a shell may be used to do the expansion. See - |expr-env-expand|. - The expanded variable is still handled like a list of file - names. When an environment variable cannot be expanded, it is - left unchanged. Thus ":echo expand('$FOOBAR')" results in - "$FOOBAR". - - See |glob()| for finding existing files. See |system()| for - getting the raw output of an external command. - - Can also be used as a |method|: > - Getpattern()->expand() - -expandcmd({string}) *expandcmd()* - Expand special items in String {string} like what is done for - an Ex command such as `:edit`. This expands special keywords, - like with |expand()|, and environment variables, anywhere in - {string}. "~user" and "~/path" are only expanded at the - start. - Returns the expanded string. Example: > - :echo expandcmd('make %<.o') - -< Can also be used as a |method|: > - GetCommand()->expandcmd() -< -extend({expr1}, {expr2} [, {expr3}]) *extend()* - {expr1} and {expr2} must be both |Lists| or both - |Dictionaries|. - - If they are |Lists|: Append {expr2} to {expr1}. - If {expr3} is given insert the items of {expr2} before the - item with index {expr3} in {expr1}. When {expr3} is zero - insert before the first item. When {expr3} is equal to - len({expr1}) then {expr2} is appended. - Examples: > - :echo sort(extend(mylist, [7, 5])) - :call extend(mylist, [2, 3], 1) -< When {expr1} is the same List as {expr2} then the number of - items copied is equal to the original length of the List. - E.g., when {expr3} is 1 you get N new copies of the first item - (where N is the original length of the List). - Use |add()| to concatenate one item to a list. To concatenate - two lists into a new list use the + operator: > - :let newlist = [1, 2, 3] + [4, 5] -< - If they are |Dictionaries|: - Add all entries from {expr2} to {expr1}. - If a key exists in both {expr1} and {expr2} then {expr3} is - used to decide what to do: - {expr3} = "keep": keep the value of {expr1} - {expr3} = "force": use the value of {expr2} - {expr3} = "error": give an error message *E737* - When {expr3} is omitted then "force" is assumed. - - {expr1} is changed when {expr2} is not empty. If necessary - make a copy of {expr1} first. - {expr2} remains unchanged. - When {expr1} is locked and {expr2} is not empty the operation - fails. - Returns {expr1}. - - Can also be used as a |method|: > - mylist->extend(otherlist) - -feedkeys({string} [, {mode}]) *feedkeys()* - Characters in {string} are queued for processing as if they - come from a mapping or were typed by the user. - - By default the string is added to the end of the typeahead - buffer, thus if a mapping is still being executed the - characters come after them. Use the 'i' flag to insert before - other characters, they will be executed next, before any - characters from a mapping. - - The function does not wait for processing of keys contained in - {string}. - - To include special keys into {string}, use double-quotes - and "\..." notation |expr-quote|. For example, - feedkeys("\<CR>") simulates pressing of the <Enter> key. But - feedkeys('\<CR>') pushes 5 characters. - The |<Ignore>| keycode may be used to exit the - wait-for-character without doing anything. - - {mode} is a String, which can contain these character flags: - 'm' Remap keys. This is default. If {mode} is absent, - keys are remapped. - 'n' Do not remap keys. - 't' Handle keys as if typed; otherwise they are handled as - if coming from a mapping. This matters for undo, - opening folds, etc. - 'i' Insert the string instead of appending (see above). - 'x' Execute commands until typeahead is empty. This is - similar to using ":normal!". You can call feedkeys() - several times without 'x' and then one time with 'x' - (possibly with an empty {string}) to execute all the - typeahead. Note that when Vim ends in Insert mode it - will behave as if <Esc> is typed, to avoid getting - stuck, waiting for a character to be typed before the - script continues. - Note that if you manage to call feedkeys() while - executing commands, thus calling it recursively, then - all typehead will be consumed by the last call. - '!' When used with 'x' will not end Insert mode. Can be - used in a test when a timer is set to exit Insert mode - a little later. Useful for testing CursorHoldI. - - Return value is always 0. - - Can also be used as a |method|: > - GetInput()->feedkeys() - -filereadable({file}) *filereadable()* - The result is a Number, which is |TRUE| when a file with the - name {file} exists, and can be read. If {file} doesn't exist, - or is a directory, the result is |FALSE|. {file} is any - expression, which is used as a String. - If you don't care about the file being readable you can use - |glob()|. - {file} is used as-is, you may want to expand wildcards first: > - echo filereadable('~/.vimrc') - 0 - echo filereadable(expand('~/.vimrc')) - 1 - -< Can also be used as a |method|: > - GetName()->filereadable() - -filewritable({file}) *filewritable()* - The result is a Number, which is 1 when a file with the - name {file} exists, and can be written. If {file} doesn't - exist, or is not writable, the result is 0. If {file} is a - directory, and we can write to it, the result is 2. - - Can also be used as a |method|: > - GetName()->filewriteable() - -filter({expr1}, {expr2}) *filter()* - {expr1} must be a |List|, |Blob|, or a |Dictionary|. - For each item in {expr1} evaluate {expr2} and when the result - is zero remove the item from the |List| or |Dictionary|. For a - |Blob| each byte is removed. - - {expr2} must be a |string| or |Funcref|. - - If {expr2} is a |string|, inside {expr2} |v:val| has the value - of the current item. For a |Dictionary| |v:key| has the key - of the current item and for a |List| |v:key| has the index of - the current item. For a |Blob| |v:key| has the index of the - current byte. - - Examples: > - call filter(mylist, 'v:val !~ "OLD"') -< Removes the items where "OLD" appears. > - call filter(mydict, 'v:key >= 8') -< Removes the items with a key below 8. > - call filter(var, 0) -< Removes all the items, thus clears the |List| or |Dictionary|. - - Note that {expr2} is the result of expression and is then - used as an expression again. Often it is good to use a - |literal-string| to avoid having to double backslashes. - - If {expr2} is a |Funcref| it must take two arguments: - 1. the key or the index of the current item. - 2. the value of the current item. - The function must return |TRUE| if the item should be kept. - Example that keeps the odd items of a list: > - func Odd(idx, val) - return a:idx % 2 == 1 - endfunc - call filter(mylist, function('Odd')) -< It is shorter when using a |lambda|: > - call filter(myList, {idx, val -> idx * val <= 42}) -< If you do not use "val" you can leave it out: > - call filter(myList, {idx -> idx % 2 == 1}) -< - The operation is done in-place. If you want a |List| or - |Dictionary| to remain unmodified make a copy first: > - :let l = filter(copy(mylist), 'v:val =~ "KEEP"') - -< Returns {expr1}, the |List|, |Blob| or |Dictionary| that was - filtered. When an error is encountered while evaluating - {expr2} no further items in {expr1} are processed. When - {expr2} is a Funcref errors inside a function are ignored, - unless it was defined with the "abort" flag. - - Can also be used as a |method|: > - mylist->filter(expr2) - -finddir({name} [, {path} [, {count}]]) *finddir()* - Find directory {name} in {path}. Supports both downwards and - upwards recursive directory searches. See |file-searching| - for the syntax of {path}. - - Returns the path of the first found match. When the found - directory is below the current directory a relative path is - returned. Otherwise a full path is returned. - If {path} is omitted or empty then 'path' is used. - - If the optional {count} is given, find {count}'s occurrence of - {name} in {path} instead of the first one. - When {count} is negative return all the matches in a |List|. - - This is quite similar to the ex-command `:find`. - - Can also be used as a |method|: > - GetName()->finddir() - -findfile({name} [, {path} [, {count}]]) *findfile()* - Just like |finddir()|, but find a file instead of a directory. - Uses 'suffixesadd'. - Example: > - :echo findfile("tags.vim", ".;") -< Searches from the directory of the current file upwards until - it finds the file "tags.vim". - - Can also be used as a |method|: > - GetName()->findfile() - -flatten({list} [, {maxdepth}]) *flatten()* - Flatten {list} up to {maxdepth} levels. Without {maxdepth} - the result is a |List| without nesting, as if {maxdepth} is - a very large number. - The {list} is changed in place, make a copy first if you do - not want that. - *E900* - {maxdepth} means how deep in nested lists changes are made. - {list} is not modified when {maxdepth} is 0. - {maxdepth} must be positive number. - - If there is an error the number zero is returned. - - Example: > - :echo flatten([1, [2, [3, 4]], 5]) -< [1, 2, 3, 4, 5] > - :echo flatten([1, [2, [3, 4]], 5], 1) -< [1, 2, [3, 4], 5] - - Can also be used as a |method|: > - mylist->flatten() -< -float2nr({expr}) *float2nr()* - Convert {expr} to a Number by omitting the part after the - decimal point. - {expr} must evaluate to a |Float| or a Number. - When the value of {expr} is out of range for a |Number| the - result is truncated to 0x7fffffff or -0x7fffffff (or when - 64-bit Number support is enabled, 0x7fffffffffffffff or - -0x7fffffffffffffff). NaN results in -0x80000000 (or when - 64-bit Number support is enabled, -0x8000000000000000). - Examples: > - echo float2nr(3.95) -< 3 > - echo float2nr(-23.45) -< -23 > - echo float2nr(1.0e100) -< 2147483647 (or 9223372036854775807) > - echo float2nr(-1.0e150) -< -2147483647 (or -9223372036854775807) > - echo float2nr(1.0e-100) -< 0 - - Can also be used as a |method|: > - Compute()->float2nr() - -floor({expr}) *floor()* - Return the largest integral value less than or equal to - {expr} as a |Float| (round down). - {expr} must evaluate to a |Float| or a |Number|. - Examples: > - echo floor(1.856) -< 1.0 > - echo floor(-5.456) -< -6.0 > - echo floor(4.0) -< 4.0 - - Can also be used as a |method|: > - Compute()->floor() - -fmod({expr1}, {expr2}) *fmod()* - Return the remainder of {expr1} / {expr2}, even if the - division is not representable. Returns {expr1} - i * {expr2} - for some integer i such that if {expr2} is non-zero, the - result has the same sign as {expr1} and magnitude less than - the magnitude of {expr2}. If {expr2} is zero, the value - returned is zero. The value returned is a |Float|. - {expr1} and {expr2} must evaluate to a |Float| or a |Number|. - Examples: > - :echo fmod(12.33, 1.22) -< 0.13 > - :echo fmod(-12.33, 1.22) -< -0.13 - - Can also be used as a |method|: > - Compute()->fmod(1.22) - -fnameescape({string}) *fnameescape()* - Escape {string} for use as file name command argument. All - characters that have a special meaning, such as '%' and '|' - are escaped with a backslash. - For most systems the characters escaped are - " \t\n*?[{`$\\%#'\"|!<". For systems where a backslash - appears in a filename, it depends on the value of 'isfname'. - A leading '+' and '>' is also escaped (special after |:edit| - and |:write|). And a "-" by itself (special after |:cd|). - Example: > - :let fname = '+some str%nge|name' - :exe "edit " . fnameescape(fname) -< results in executing: > - edit \+some\ str\%nge\|name -< - Can also be used as a |method|: > - GetName()->fnameescape() - -fnamemodify({fname}, {mods}) *fnamemodify()* - Modify file name {fname} according to {mods}. {mods} is a - string of characters like it is used for file names on the - command line. See |filename-modifiers|. - Example: > - :echo fnamemodify("main.c", ":p:h") -< results in: > - /home/mool/vim/vim/src -< If {mods} is empty then {fname} is returned. - Note: Environment variables don't work in {fname}, use - |expand()| first then. - - Can also be used as a |method|: > - GetName()->fnamemodify(':p:h') - -foldclosed({lnum}) *foldclosed()* - The result is a Number. If the line {lnum} is in a closed - fold, the result is the number of the first line in that fold. - If the line {lnum} is not in a closed fold, -1 is returned. - {lnum} is used like with |getline()|. Thus "." is the current - line, "'m" mark m, etc. - - Can also be used as a |method|: > - GetLnum()->foldclosed() - -foldclosedend({lnum}) *foldclosedend()* - The result is a Number. If the line {lnum} is in a closed - fold, the result is the number of the last line in that fold. - If the line {lnum} is not in a closed fold, -1 is returned. - {lnum} is used like with |getline()|. Thus "." is the current - line, "'m" mark m, etc. - - Can also be used as a |method|: > - GetLnum()->foldclosedend() - -foldlevel({lnum}) *foldlevel()* - The result is a Number, which is the foldlevel of line {lnum} - in the current buffer. For nested folds the deepest level is - returned. If there is no fold at line {lnum}, zero is - returned. It doesn't matter if the folds are open or closed. - When used while updating folds (from 'foldexpr') -1 is - returned for lines where folds are still to be updated and the - foldlevel is unknown. As a special case the level of the - previous line is usually available. - {lnum} is used like with |getline()|. Thus "." is the current - line, "'m" mark m, etc. - - Can also be used as a |method|: > - GetLnum()->foldlevel() -< - *foldtext()* -foldtext() Returns a String, to be displayed for a closed fold. This is - the default function used for the 'foldtext' option and should - only be called from evaluating 'foldtext'. It uses the - |v:foldstart|, |v:foldend| and |v:folddashes| variables. - The returned string looks like this: > - +-- 45 lines: abcdef -< The number of leading dashes depends on the foldlevel. The - "45" is the number of lines in the fold. "abcdef" is the text - in the first non-blank line of the fold. Leading white space, - "//" or "/*" and the text from the 'foldmarker' and - 'commentstring' options is removed. - When used to draw the actual foldtext, the rest of the line - will be filled with the fold char from the 'fillchars' - setting. - -foldtextresult({lnum}) *foldtextresult()* - Returns the text that is displayed for the closed fold at line - {lnum}. Evaluates 'foldtext' in the appropriate context. - When there is no closed fold at {lnum} an empty string is - returned. - {lnum} is used like with |getline()|. Thus "." is the current - line, "'m" mark m, etc. - Useful when exporting folded text, e.g., to HTML. - - Can also be used as a |method|: > - GetLnum()->foldtextresult() -< - *foreground()* -foreground() Move the Vim window to the foreground. Useful when sent from - a client to a Vim server. |remote_send()| - On Win32 systems this might not work, the OS does not always - allow a window to bring itself to the foreground. Use - |remote_foreground()| instead. - {only in the Win32 GUI and console version} - -fullcommand({name}) *fullcommand()* - Get the full command name from a short abbreviated command - name; see |20.2| for details on command abbreviations. - - The string argument {name} may start with a `:` and can - include a [range], these are skipped and not returned. - Returns an empty string if a command doesn't exist or if it's - ambiguous (for user-defined commands). - - For example `fullcommand('s')`, `fullcommand('sub')`, - `fullcommand(':%substitute')` all return "substitute". - - Can also be used as a |method|: > - GetName()->fullcommand() -< - *funcref()* -funcref({name} [, {arglist}] [, {dict}]) - Just like |function()|, but the returned Funcref will lookup - the function by reference, not by name. This matters when the - function {name} is redefined later. - - Unlike |function()|, {name} must be an existing user function. - Also for autoloaded functions. {name} cannot be a builtin - function. - - Can also be used as a |method|: > - GetFuncname()->funcref([arg]) -< - *function()* *E700* *E922* *E923* -function({name} [, {arglist}] [, {dict}]) - Return a |Funcref| variable that refers to function {name}. - {name} can be a user defined function or an internal function. - - {name} can also be a Funcref or a partial. When it is a - partial the dict stored in it will be used and the {dict} - argument is not allowed. E.g.: > - let FuncWithArg = function(dict.Func, [arg]) - let Broken = function(dict.Func, [arg], dict) -< - When using the Funcref the function will be found by {name}, - also when it was redefined later. Use |funcref()| to keep the - same function. - - When {arglist} or {dict} is present this creates a partial. - That means the argument list and/or the dictionary is stored in - the Funcref and will be used when the Funcref is called. - - The arguments are passed to the function in front of other - arguments, but after any argument from |method|. Example: > - func Callback(arg1, arg2, name) - ... - let Partial = function('Callback', ['one', 'two']) - ... - call Partial('name') -< Invokes the function as with: > - call Callback('one', 'two', 'name') - -< The Dictionary is only useful when calling a "dict" function. - In that case the {dict} is passed in as "self". Example: > - function Callback() dict - echo "called for " . self.name - endfunction - ... - let context = {"name": "example"} - let Func = function('Callback', context) - ... - call Func() " will echo: called for example - -< The argument list and the Dictionary can be combined: > - function Callback(arg1, count) dict - ... - let context = {"name": "example"} - let Func = function('Callback', ['one'], context) - ... - call Func(500) -< Invokes the function as with: > - call context.Callback('one', 500) -< - Can also be used as a |method|: > - GetFuncname()->function([arg]) - -garbagecollect([{atexit}]) *garbagecollect()* - Cleanup unused |Lists| and |Dictionaries| that have circular - references. - - There is hardly ever a need to invoke this function, as it is - automatically done when Vim runs out of memory or is waiting - for the user to press a key after 'updatetime'. Items without - circular references are always freed when they become unused. - This is useful if you have deleted a very big |List| and/or - |Dictionary| with circular references in a script that runs - for a long time. - - When the optional {atexit} argument is one, garbage - collection will also be done when exiting Vim, if it wasn't - done before. This is useful when checking for memory leaks. - - The garbage collection is not done immediately but only when - it's safe to perform. This is when waiting for the user to - type a character. - -get({list}, {idx} [, {default}]) *get()* - Get item {idx} from |List| {list}. When this item is not - available return {default}. Return zero when {default} is - omitted. - Can also be used as a |method|: > - mylist->get(idx) -get({blob}, {idx} [, {default}]) - Get byte {idx} from |Blob| {blob}. When this byte is not - available return {default}. Return -1 when {default} is - omitted. -get({dict}, {key} [, {default}]) - Get item with key {key} from |Dictionary| {dict}. When this - item is not available return {default}. Return zero when - {default} is omitted. Useful example: > - let val = get(g:, 'var_name', 'default') -< This gets the value of g:var_name if it exists, and uses - 'default' when it does not exist. -get({func}, {what}) - Get item {what} from Funcref {func}. Possible values for - {what} are: - "name" The function name - "func" The function - "dict" The dictionary - "args" The list with arguments - - *getbufinfo()* -getbufinfo([{buf}]) -getbufinfo([{dict}]) - Get information about buffers as a List of Dictionaries. - - Without an argument information about all the buffers is - returned. - - When the argument is a |Dictionary| only the buffers matching - the specified criteria are returned. The following keys can - be specified in {dict}: - buflisted include only listed buffers. - bufloaded include only loaded buffers. - bufmodified include only modified buffers. - - Otherwise, {buf} specifies a particular buffer to return - information for. For the use of {buf}, see |bufname()| - above. If the buffer is found the returned List has one item. - Otherwise the result is an empty list. - - Each returned List item is a dictionary with the following - entries: - bufnr Buffer number. - changed TRUE if the buffer is modified. - changedtick Number of changes made to the buffer. - hidden TRUE if the buffer is hidden. - lastused Timestamp in seconds, like - |localtime()|, when the buffer was - last used. - listed TRUE if the buffer is listed. - lnum Line number used for the buffer when - opened in the current window. - Only valid if the buffer has been - displayed in the window in the past. - If you want the line number of the - last known cursor position in a given - window, use |line()|: > - :echo line('.', {winid}) -< - linecount Number of lines in the buffer (only - valid when loaded) - loaded TRUE if the buffer is loaded. - name Full path to the file in the buffer. - signs List of signs placed in the buffer. - Each list item is a dictionary with - the following fields: - id sign identifier - lnum line number - name sign name - variables A reference to the dictionary with - buffer-local variables. - windows List of |window-ID|s that display this - buffer - - Examples: > - for buf in getbufinfo() - echo buf.name - endfor - for buf in getbufinfo({'buflisted':1}) - if buf.changed - .... - endif - endfor -< - To get buffer-local options use: > - getbufvar({bufnr}, '&option_name') -< - Can also be used as a |method|: > - GetBufnr()->getbufinfo() -< - *getbufline()* -getbufline({buf}, {lnum} [, {end}]) - Return a |List| with the lines starting from {lnum} to {end} - (inclusive) in the buffer {buf}. If {end} is omitted, a - |List| with only the line {lnum} is returned. - - For the use of {buf}, see |bufname()| above. - - For {lnum} and {end} "$" can be used for the last line of the - buffer. Otherwise a number must be used. - - When {lnum} is smaller than 1 or bigger than the number of - lines in the buffer, an empty |List| is returned. - - When {end} is greater than the number of lines in the buffer, - it is treated as {end} is set to the number of lines in the - buffer. When {end} is before {lnum} an empty |List| is - returned. - - This function works only for loaded buffers. For unloaded and - non-existing buffers, an empty |List| is returned. - - Example: > - :let lines = getbufline(bufnr("myfile"), 1, "$") - -< Can also be used as a |method|: > - GetBufnr()->getbufline(lnum) - -getbufvar({buf}, {varname} [, {def}]) *getbufvar()* - The result is the value of option or local buffer variable - {varname} in buffer {buf}. Note that the name without "b:" - must be used. - The {varname} argument is a string. - When {varname} is empty returns a |Dictionary| with all the - buffer-local variables. - When {varname} is equal to "&" returns a |Dictionary| with all - the buffer-local options. - Otherwise, when {varname} starts with "&" returns the value of - a buffer-local option. - This also works for a global or buffer-local option, but it - doesn't work for a global variable, window-local variable or - window-local option. - For the use of {buf}, see |bufname()| above. - When the buffer or variable doesn't exist {def} or an empty - string is returned, there is no error message. - Examples: > - :let bufmodified = getbufvar(1, "&mod") - :echo "todo myvar = " . getbufvar("todo", "myvar") - -< Can also be used as a |method|: > - GetBufnr()->getbufvar(varname) -< -getchangelist([{buf}]) *getchangelist()* - Returns the |changelist| for the buffer {buf}. For the use - of {buf}, see |bufname()| above. If buffer {buf} doesn't - exist, an empty list is returned. - - The returned list contains two entries: a list with the change - locations and the current position in the list. Each - entry in the change list is a dictionary with the following - entries: - col column number - coladd column offset for 'virtualedit' - lnum line number - If buffer {buf} is the current buffer, then the current - position refers to the position in the list. For other - buffers, it is set to the length of the list. - - Can also be used as a |method|: > - GetBufnr()->getchangelist() - -getchar([expr]) *getchar()* - Get a single character from the user or input stream. - If [expr] is omitted, wait until a character is available. - If [expr] is 0, only get a character when one is available. - Return zero otherwise. - If [expr] is 1, only check if a character is available, it is - not consumed. Return zero if no character available. - If you prefer always getting a string use |getcharstr()|. - - Without [expr] and when [expr] is 0 a whole character or - special key is returned. If it is a single character, the - result is a number. Use nr2char() to convert it to a String. - Otherwise a String is returned with the encoded character. - For a special key it's a String with a sequence of bytes - starting with 0x80 (decimal: 128). This is the same value as - the String "\<Key>", e.g., "\<Left>". The returned value is - also a String when a modifier (shift, control, alt) was used - that is not included in the character. - - When [expr] is 0 and Esc is typed, there will be a short delay - while Vim waits to see if this is the start of an escape - sequence. - - When [expr] is 1 only the first byte is returned. For a - one-byte character it is the character itself as a number. - Use nr2char() to convert it to a String. - - Use getcharmod() to obtain any additional modifiers. - - When the user clicks a mouse button, the mouse event will be - returned. The position can then be found in |v:mouse_col|, - |v:mouse_lnum|, |v:mouse_winid| and |v:mouse_win|. - |getmousepos()| can also be used. Mouse move events will be - ignored. - This example positions the mouse as it would normally happen: > - let c = getchar() - if c == "\<LeftMouse>" && v:mouse_win > 0 - exe v:mouse_win . "wincmd w" - exe v:mouse_lnum - exe "normal " . v:mouse_col . "|" - endif -< - There is no prompt, you will somehow have to make clear to the - user that a character has to be typed. The screen is not - redrawn, e.g. when resizing the window. - - There is no mapping for the character. - Key codes are replaced, thus when the user presses the <Del> - key you get the code for the <Del> key, not the raw character - sequence. Examples: > - getchar() == "\<Del>" - getchar() == "\<S-Left>" -< This example redefines "f" to ignore case: > - :nmap f :call FindChar()<CR> - :function FindChar() - : let c = nr2char(getchar()) - : while col('.') < col('$') - 1 - : normal l - : if getline('.')[col('.') - 1] ==? c - : break - : endif - : endwhile - :endfunction -< -getcharmod() *getcharmod()* - The result is a Number which is the state of the modifiers for - the last obtained character with getchar() or in another way. - These values are added together: - 2 shift - 4 control - 8 alt (meta) - 16 meta (when it's different from ALT) - 32 mouse double click - 64 mouse triple click - 96 mouse quadruple click (== 32 + 64) - 128 command (Macintosh only) - Only the modifiers that have not been included in the - character itself are obtained. Thus Shift-a results in "A" - without a modifier. - -getcharsearch() *getcharsearch()* - Return the current character search information as a {dict} - with the following entries: - - char character previously used for a character - search (|t|, |f|, |T|, or |F|); empty string - if no character search has been performed - forward direction of character search; 1 for forward, - 0 for backward - until type of character search; 1 for a |t| or |T| - character search, 0 for an |f| or |F| - character search - - This can be useful to always have |;| and |,| search - forward/backward regardless of the direction of the previous - character search: > - :nnoremap <expr> ; getcharsearch().forward ? ';' : ',' - :nnoremap <expr> , getcharsearch().forward ? ',' : ';' -< Also see |setcharsearch()|. - - -getcharstr([expr]) *getcharstr()* - Get a single character from the user or input stream as a - string. - If [expr] is omitted, wait until a character is available. - If [expr] is 0 or false, only get a character when one is - available. Return an empty string otherwise. - If [expr] is 1 or true, only check if a character is - available, it is not consumed. Return an empty string - if no character is available. - Otherwise this works like |getchar()|, except that a number - result is converted to a string. - - -getcmdline() *getcmdline()* - Return the current command-line. Only works when the command - line is being edited, thus requires use of |c_CTRL-\_e| or - |c_CTRL-R_=|. - Example: > - :cmap <F7> <C-\>eescape(getcmdline(), ' \')<CR> -< Also see |getcmdtype()|, |getcmdpos()| and |setcmdpos()|. - Returns an empty string when entering a password or using - |inputsecret()|. - -getcmdpos() *getcmdpos()* - Return the position of the cursor in the command line as a - byte count. The first column is 1. - Only works when editing the command line, thus requires use of - |c_CTRL-\_e| or |c_CTRL-R_=| or an expression mapping. - Returns 0 otherwise. - Also see |getcmdtype()|, |setcmdpos()| and |getcmdline()|. - -getcmdtype() *getcmdtype()* - Return the current command-line type. Possible return values - are: - : normal Ex command - > debug mode command |debug-mode| - / forward search command - ? backward search command - @ |input()| command - - |:insert| or |:append| command - = |i_CTRL-R_=| - Only works when editing the command line, thus requires use of - |c_CTRL-\_e| or |c_CTRL-R_=| or an expression mapping. - Returns an empty string otherwise. - Also see |getcmdpos()|, |setcmdpos()| and |getcmdline()|. - -getcmdwintype() *getcmdwintype()* - Return the current |command-line-window| type. Possible return - values are the same as |getcmdtype()|. Returns an empty string - when not in the command-line window. - -getcompletion({pat}, {type} [, {filtered}]) *getcompletion()* - Return a list of command-line completion matches. The String - {type} argument specifies what for. The following completion - types are supported: - - arglist file names in argument list - augroup autocmd groups - buffer buffer names - behave :behave suboptions - cmdline |cmdline-completion| result - color color schemes - command Ex command - compiler compilers - cscope |:cscope| suboptions - diff_buffer |:diffget| and |:diffput| completion - dir directory names - environment environment variable names - event autocommand events - expression Vim expression - file file and directory names - file_in_path file and directory names in |'path'| - filetype filetype names |'filetype'| - function function name - help help subjects - highlight highlight groups - history :history suboptions - locale locale names (as output of locale -a) - mapclear buffer argument - mapping mapping name - menu menus - messages |:messages| suboptions - option options - packadd optional package |pack-add| names - shellcmd Shell command - sign |:sign| suboptions - syntax syntax file names |'syntax'| - syntime |:syntime| suboptions - tag tags - tag_listfiles tags, file names - user user names - var user variables - - If {pat} is an empty string, then all the matches are - returned. Otherwise only items matching {pat} are returned. - See |wildcards| for the use of special characters in {pat}. - - If the optional {filtered} flag is set to 1, then 'wildignore' - is applied to filter the results. Otherwise all the matches - are returned. The 'wildignorecase' option always applies. - - If {type} is "cmdline", then the |cmdline-completion| result is - returned. For example, to complete the possible values after - a ":call" command: > - echo getcompletion('call ', 'cmdline') -< - If there are no matches, an empty list is returned. An - invalid value for {type} produces an error. - - Can also be used as a |method|: > - GetPattern()->getcompletion('color') -< - *getcurpos()* -getcurpos() Get the position of the cursor. This is like getpos('.'), but - includes an extra "curswant" in the list: - [0, lnum, col, off, curswant] ~ - The "curswant" number is the preferred column when moving the - cursor vertically. Also see |getpos()|. - The first "bufnum" item is always zero. - - This can be used to save and restore the cursor position: > - let save_cursor = getcurpos() - MoveTheCursorAround - call setpos('.', save_cursor) -< Note that this only works within the window. See - |winrestview()| for restoring more state. - -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 - ignored. - Tabs and windows are identified by their respective numbers, - 0 means current tab or window. Missing tab number implies 0. - Thus the following are equivalent: > - getcwd(0) - getcwd(0, 0) -< If {winnr} is -1 it is ignored, only the tab is resolved. - {winnr} can be the window number or the |window-ID|. - If both {winnr} and {tabnr} are -1 the global working - directory is returned. - Throw error if the arguments are invalid. |E5000| |E5001| |E5002| - - Can also be used as a |method|: > - GetWinnr()->getcwd() - -getenv({name}) *getenv()* - Return the value of environment variable {name}. The {name} - argument is a string, without a leading '$'. Example: > - myHome = getenv('HOME') - -< When the variable does not exist |v:null| is returned. That - is different from a variable set to an empty string. - See also |expr-env|. - - Can also be used as a |method|: > - GetVarname()->getenv() - -getfontname([{name}]) *getfontname()* - Without an argument returns the name of the normal font being - used. Like what is used for the Normal highlight group - |hl-Normal|. - With an argument a check is done whether String {name} is a - valid font name. If not then an empty string is returned. - Otherwise the actual font name is returned, or {name} if the - GUI does not support obtaining the real name. - Only works when the GUI is running, thus not in your vimrc or - gvimrc file. Use the |GUIEnter| autocommand to use this - function just after the GUI has started. - -getfperm({fname}) *getfperm()* - The result is a String, which is the read, write, and execute - permissions of the given file {fname}. - If {fname} does not exist or its directory cannot be read, an - empty string is returned. - The result is of the form "rwxrwxrwx", where each group of - "rwx" flags represent, in turn, the permissions of the owner - of the file, the group the file belongs to, and other users. - If a user does not have a given permission the flag for this - is replaced with the string "-". Examples: > - :echo getfperm("/etc/passwd") - :echo getfperm(expand("~/.config/nvim/init.vim")) -< This will hopefully (from a security point of view) display - the string "rw-r--r--" or even "rw-------". - - Can also be used as a |method|: > - GetFilename()->getfperm() -< - For setting permissions use |setfperm()|. - -getfsize({fname}) *getfsize()* - The result is a Number, which is the size in bytes of the - given file {fname}. - If {fname} is a directory, 0 is returned. - If the file {fname} can't be found, -1 is returned. - If the size of {fname} is too big to fit in a Number then -2 - is returned. - - Can also be used as a |method|: > - GetFilename()->getfsize() - -getftime({fname}) *getftime()* - The result is a Number, which is the last modification time of - the given file {fname}. The value is measured as seconds - since 1st Jan 1970, and may be passed to strftime(). See also - |localtime()| and |strftime()|. - If the file {fname} can't be found -1 is returned. - - Can also be used as a |method|: > - GetFilename()->getftime() - -getftype({fname}) *getftype()* - The result is a String, which is a description of the kind of - file of the given file {fname}. - If {fname} does not exist an empty string is returned. - Here is a table over different kinds of files and their - results: - Normal file "file" - Directory "dir" - Symbolic link "link" - Block device "bdev" - Character device "cdev" - Socket "socket" - FIFO "fifo" - All other "other" - Example: > - getftype("/home") -< Note that a type such as "link" will only be returned on - systems that support it. On some systems only "dir" and - "file" are returned. - - Can also be used as a |method|: > - GetFilename()->getftype() - -getjumplist([{winnr} [, {tabnr}]]) *getjumplist()* - Returns the |jumplist| for the specified window. - - Without arguments use the current window. - With {winnr} only use this window in the current tab page. - {winnr} can also be a |window-ID|. - With {winnr} and {tabnr} use the window in the specified tab - page. - - The returned list contains two entries: a list with the jump - locations and the last used jump position number in the list. - Each entry in the jump location list is a dictionary with - the following entries: - bufnr buffer number - col column number - coladd column offset for 'virtualedit' - filename filename if available - lnum line number - - Can also be used as a |method|: > - GetWinnr()->getjumplist() - -< *getline()* -getline({lnum} [, {end}]) - Without {end} the result is a String, which is line {lnum} - from the current buffer. Example: > - getline(1) -< When {lnum} is a String that doesn't start with a - digit, |line()| is called to translate the String into a Number. - To get the line under the cursor: > - getline(".") -< When {lnum} is a number smaller than 1 or bigger than the - number of lines in the buffer, an empty string is returned. - - When {end} is given the result is a |List| where each item is - a line from the current buffer in the range {lnum} to {end}, - including line {end}. - {end} is used in the same way as {lnum}. - Non-existing lines are silently omitted. - When {end} is before {lnum} an empty |List| is returned. - Example: > - :let start = line('.') - :let end = search("^$") - 1 - :let lines = getline(start, end) - -< Can also be used as a |method|: > - ComputeLnum()->getline() - -< To get lines from another buffer see |getbufline()| - -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. - - For a location list window, the displayed location list is - returned. For an invalid window number {nr}, an empty list is - returned. Otherwise, same as |getqflist()|. - - If the optional {what} dictionary argument is supplied, then - returns the items listed in {what} as a dictionary. Refer to - |getqflist()| for the supported items in {what}. - If {what} contains 'filewinid', then returns the id of the - window used to display files from the location list. This - field is applicable only when called from a location list - window. See |location-list-file-window| for more details. - - Returns a |Dictionary| with default values if there is no - location list for the window {nr}. - Returns an empty Dictionary if window {nr} does not exist. - - Examples (See also |getqflist-examples|): > - :echo getloclist(3, {'all': 0}) - :echo getloclist(5, {'filewinid': 0}) - - -getmarklist([{buf}]) *getmarklist()* - Without the {buf} argument returns a |List| with information - about all the global marks. |mark| - - If the optional {buf} argument is specified, returns the - local marks defined in buffer {buf}. For the use of {buf}, - see |bufname()|. - - Each item in the returned List is a |Dict| with the following: - mark name of the mark prefixed by "'" - pos a |List| with the position of the mark: - [bufnum, lnum, col, off] - Refer to |getpos()| for more information. - file file name - - Refer to |getpos()| for getting information about a specific - mark. - - Can also be used as a |method|: > - GetBufnr()->getmarklist() - -getmatches([{win}]) *getmatches()* - Returns a |List| with all matches previously defined for the - current window by |matchadd()| and the |:match| commands. - |getmatches()| is useful in combination with |setmatches()|, - as |setmatches()| can restore a list of matches saved by - |getmatches()|. - If {win} is specified, use the window with this number or - window ID instead of the current window. - Example: > - :echo getmatches() -< [{'group': 'MyGroup1', 'pattern': 'TODO', - 'priority': 10, 'id': 1}, {'group': 'MyGroup2', - 'pattern': 'FIXME', 'priority': 10, 'id': 2}] > - :let m = getmatches() - :call clearmatches() - :echo getmatches() -< [] > - :call setmatches(m) - :echo getmatches() -< [{'group': 'MyGroup1', 'pattern': 'TODO', - 'priority': 10, 'id': 1}, {'group': 'MyGroup2', - 'pattern': 'FIXME', 'priority': 10, 'id': 2}] > - :unlet m -< -getmousepos() *getmousepos()* - Returns a Dictionary with the last known position of the - mouse. This can be used in a mapping for a mouse click. The - items are: - screenrow screen row - screencol screen column - winid Window ID of the click - winrow row inside "winid" - wincol column inside "winid" - line text line inside "winid" - column text column inside "winid" - All numbers are 1-based. - - If not over a window, e.g. when in the command line, then only - "screenrow" and "screencol" are valid, the others are zero. - - When on the status line below a window or the vertical - separater right of a window, the "line" and "column" values - are zero. - - When the position is after the text then "column" is the - length of the text in bytes plus one. - - If the mouse is over a focusable floating window then that - window is used. - - When using |getchar()| the Vim variables |v:mouse_lnum|, - |v:mouse_col| and |v:mouse_winid| also provide these values. - - *getpid()* -getpid() Return a Number which is the process ID of the Vim process. - This is a unique number, until Vim exits. - - *getpos()* -getpos({expr}) Get the position for String {expr}. For possible values of - {expr} see |line()|. For getting the cursor position see - |getcurpos()|. - The result is a |List| with four numbers: - [bufnum, lnum, col, off] - "bufnum" is zero, unless a mark like '0 or 'A is used, then it - is the buffer number of the mark. - "lnum" and "col" are the position in the buffer. The first - column is 1. - The "off" number is zero, unless 'virtualedit' is used. Then - it is the offset in screen columns from the start of the - character. E.g., a position within a <Tab> or after the last - character. - Note that for '< and '> Visual mode matters: when it is "V" - (visual line mode) the column of '< is zero and the column of - '> is a large number. - The column number in the returned List is the byte position - within the line. - The column number can be very large, e.g. 2147483647, in which - case it means "after the end of the line". - This can be used to save and restore the position of a mark: > - let save_a_mark = getpos("'a") - ... - call setpos("'a", save_a_mark) -< Also see |getcurpos()| and |setpos()|. - - Can also be used as a |method|: > - GetMark()->getpos() - -getqflist([{what}]) *getqflist()* - Returns a |List| with all the current quickfix errors. Each - list item is a dictionary with these entries: - bufnr number of buffer that has the file name, use - bufname() to get the name - module module name - lnum line number in the buffer (first line is 1) - end_lnum - end of line number if the item is multiline - col column number (first column is 1) - end_col end of column number if the item has range - vcol |TRUE|: "col" is visual column - |FALSE|: "col" is byte index - nr error number - pattern search pattern used to locate the error - text description of the error - type type of the error, 'E', '1', etc. - valid |TRUE|: recognized error message - - When there is no error list or it's empty, an empty list is - returned. Quickfix list entries with a non-existing buffer - number are returned with "bufnr" set to zero (Note: some - functions accept buffer number zero for the alternate buffer, - you may need to explicitly check for zero). - - Useful application: Find pattern matches in multiple files and - do something with them: > - :vimgrep /theword/jg *.c - :for d in getqflist() - : echo bufname(d.bufnr) ':' d.lnum '=' d.text - :endfor -< - If the optional {what} dictionary argument is supplied, then - returns only the items listed in {what} as a dictionary. The - following string items are supported in {what}: - changedtick get the total number of changes made - to the list |quickfix-changedtick| - context get the |quickfix-context| - efm errorformat to use when parsing "lines". If - not present, then the 'errorformat' option - value is used. - id get information for the quickfix list with - |quickfix-ID|; zero means the id for the - current list or the list specified by "nr" - idx get information for the quickfix entry at this - index in the list specified by 'id' or 'nr'. - If set to zero, then uses the current entry. - See |quickfix-index| - items quickfix list entries - lines parse a list of lines using 'efm' and return - the resulting entries. Only a |List| type is - accepted. The current quickfix list is not - modified. See |quickfix-parse|. - nr get information for this quickfix list; zero - means the current quickfix list and "$" means - the last quickfix list - size number of entries in the quickfix list - title get the list title |quickfix-title| - winid get the quickfix |window-ID| - all all of the above quickfix properties - Non-string items in {what} are ignored. To get the value of a - particular item, set it to zero. - If "nr" is not present then the current quickfix list is used. - If both "nr" and a non-zero "id" are specified, then the list - specified by "id" is used. - To get the number of lists in the quickfix stack, set "nr" to - "$" in {what}. The "nr" value in the returned dictionary - contains the quickfix stack size. - When "lines" is specified, all the other items except "efm" - are ignored. The returned dictionary contains the entry - "items" with the list of entries. - - The returned dictionary contains the following entries: - changedtick total number of changes made to the - list |quickfix-changedtick| - context quickfix list context. See |quickfix-context| - If not present, set to "". - id quickfix list ID |quickfix-ID|. If not - present, set to 0. - idx index of the quickfix entry in the list. If not - present, set to 0. - items quickfix list entries. If not present, set to - an empty list. - nr quickfix list number. If not present, set to 0 - size number of entries in the quickfix list. If not - present, set to 0. - title quickfix list title text. If not present, set - to "". - winid quickfix |window-ID|. If not present, set to 0 - - Examples (See also |getqflist-examples|): > - :echo getqflist({'all': 1}) - :echo getqflist({'nr': 2, 'title': 1}) - :echo getqflist({'lines' : ["F1:10:L10"]}) -< -getreg([{regname} [, 1 [, {list}]]]) *getreg()* - The result is a String, which is the contents of register - {regname}. Example: > - :let cliptext = getreg('*') -< When register {regname} was not set the result is an empty - string. - The {regname} argument must be a string. - - getreg('=') returns the last evaluated value of the expression - register. (For use in maps.) - getreg('=', 1) returns the expression itself, so that it can - be restored with |setreg()|. For other registers the extra - argument is ignored, thus you can always give it. - - If {list} is present and |TRUE|, the result type is changed - to |List|. Each list item is one text line. Use it if you care - about zero bytes possibly present inside register: without - third argument both NLs and zero bytes are represented as NLs - (see |NL-used-for-Nul|). - When the register was not set an empty list is returned. - - If {regname} is not specified, |v:register| is used. - - Can also be used as a |method|: > - GetRegname()->getreg() - -getreginfo([{regname}]) *getreginfo()* - Returns detailed information about register {regname} as a - Dictionary with the following entries: - regcontents List of lines contained in register - {regname}, like - |getreg|({regname}, 1, 1). - regtype the type of register {regname}, as in - |getregtype()|. - isunnamed Boolean flag, v:true if this register - is currently pointed to by the unnamed - register. - points_to for the unnamed register, gives the - single letter name of the register - currently pointed to (see |quotequote|). - For example, after deleting a line - with `dd`, this field will be "1", - which is the register that got the - deleted text. - - The {regname} argument is a string. If {regname} is invalid - or not set, an empty Dictionary will be returned. - If {regname} is not specified, |v:register| is used. - The returned Dictionary can be passed to |setreg()|. - - Can also be used as a |method|: > - GetRegname()->getreginfo() - -getregtype([{regname}]) *getregtype()* - The result is a String, which is type of register {regname}. - The value will be one of: - "v" for |charwise| text - "V" for |linewise| text - "<CTRL-V>{width}" for |blockwise-visual| text - "" for an empty or unknown register - <CTRL-V> is one character with value 0x16. - The {regname} argument is a string. If {regname} is not - specified, |v:register| is used. - - Can also be used as a |method|: > - GetRegname()->getregtype() - -gettabinfo([{tabnr}]) *gettabinfo()* - If {tabnr} is not specified, then information about all the - tab pages is returned as a |List|. Each List item is a - |Dictionary|. Otherwise, {tabnr} specifies the tab page - number and information about that one is returned. If the tab - page does not exist an empty List is returned. - - Each List item is a |Dictionary| with the following entries: - tabnr tab page number. - variables a reference to the dictionary with - tabpage-local variables - windows List of |window-ID|s in the tab page. - - Can also be used as a |method|: > - GetTabnr()->gettabinfo() - -gettabvar({tabnr}, {varname} [, {def}]) *gettabvar()* - Get the value of a tab-local variable {varname} in tab page - {tabnr}. |t:var| - Tabs are numbered starting with one. - The {varname} argument is a string. When {varname} is empty a - dictionary with all tab-local variables is returned. - Note that the name without "t:" must be used. - When the tab or variable doesn't exist {def} or an empty - string is returned, there is no error message. - - Can also be used as a |method|: > - GetTabnr()->gettabvar(varname) - -gettabwinvar({tabnr}, {winnr}, {varname} [, {def}]) *gettabwinvar()* - Get the value of window-local variable {varname} in window - {winnr} in tab page {tabnr}. - The {varname} argument is a string. When {varname} is empty a - dictionary with all window-local variables is returned. - When {varname} is equal to "&" get the values of all - window-local options in a |Dictionary|. - Otherwise, when {varname} starts with "&" get the value of a - window-local option. - Note that {varname} must be the name without "w:". - Tabs are numbered starting with one. For the current tabpage - use |getwinvar()|. - {winnr} can be the window number or the |window-ID|. - When {winnr} is zero the current window is used. - This also works for a global option, buffer-local option and - window-local option, but it doesn't work for a global variable - or buffer-local variable. - When the tab, window or variable doesn't exist {def} or an - empty string is returned, there is no error message. - Examples: > - :let list_is_on = gettabwinvar(1, 2, '&list') - :echo "myvar = " . gettabwinvar(3, 1, 'myvar') -< - To obtain all window-local variables use: > - gettabwinvar({tabnr}, {winnr}, '&') - -< Can also be used as a |method|: > - GetTabnr()->gettabwinvar(winnr, varname) - -gettagstack([{winnr}]) *gettagstack()* - The result is a Dict, which is the tag stack of window {winnr}. - {winnr} can be the window number or the |window-ID|. - When {winnr} is not specified, the current window is used. - When window {winnr} doesn't exist, an empty Dict is returned. - - The returned dictionary contains the following entries: - curidx Current index in the stack. When at - top of the stack, set to (length + 1). - Index of bottom of the stack is 1. - items List of items in the stack. Each item - is a dictionary containing the - entries described below. - length Number of entries in the stack. - - Each item in the stack is a dictionary with the following - entries: - bufnr buffer number of the current jump - from cursor position before the tag jump. - See |getpos()| for the format of the - returned list. - matchnr current matching tag number. Used when - multiple matching tags are found for a - name. - tagname name of the tag - - See |tagstack| for more information about the tag stack. - - Can also be used as a |method|: > - GetWinnr()->gettagstack() - -getwininfo([{winid}]) *getwininfo()* - Returns information about windows as a |List| with Dictionaries. - - If {winid} is given Information about the window with that ID - is returned, as a |List| with one item. If the window does not - exist the result is an empty list. - - Without {winid} information about all the windows in all the - tab pages is returned. - - Each List item is a |Dictionary| with the following entries: - botline last complete displayed buffer line - bufnr number of buffer in the window - height window height (excluding winbar) - loclist 1 if showing a location list - quickfix 1 if quickfix or location list window - terminal 1 if a terminal window - tabnr tab page number - topline first displayed buffer line - variables a reference to the dictionary with - window-local variables - width window width - winbar 1 if the window has a toolbar, 0 - otherwise - wincol leftmost screen column of the window; - "col" from |win_screenpos()| - textoff number of columns occupied by any - 'foldcolumn', 'signcolumn' and line - number in front of the text - winid |window-ID| - winnr window number - winrow topmost screen line of the window; - "row" from |win_screenpos()| - - Can also be used as a |method|: > - GetWinnr()->getwininfo() - -getwinpos([{timeout}]) *getwinpos()* - The result is a |List| with two numbers, the result of - |getwinposx()| and |getwinposy()| combined: - [x-pos, y-pos] - {timeout} can be used to specify how long to wait in msec for - a response from the terminal. When omitted 100 msec is used. - - Use a longer time for a remote terminal. - When using a value less than 10 and no response is received - within that time, a previously reported position is returned, - if available. This can be used to poll for the position and - do some work in the meantime: > - while 1 - let res = getwinpos(1) - if res[0] >= 0 - break - endif - " Do some work here - endwhile -< - Can also be used as a |method|: > - GetTimeout()->getwinpos() -< - *getwinposx()* -getwinposx() The result is a Number, which is the X coordinate in pixels of - the left hand side of the GUI Vim window. The result will be - -1 if the information is not available. - The value can be used with `:winpos`. - - *getwinposy()* -getwinposy() The result is a Number, which is the Y coordinate in pixels of - the top of the GUI Vim window. The result will be -1 if the - information is not available. - The value can be used with `:winpos`. - -getwinvar({winnr}, {varname} [, {def}]) *getwinvar()* - Like |gettabwinvar()| for the current tabpage. - Examples: > - :let list_is_on = getwinvar(2, '&list') - :echo "myvar = " . getwinvar(1, 'myvar') - -< Can also be used as a |method|: > - GetWinnr()->getwinvar(varname) -< -glob({expr} [, {nosuf} [, {list} [, {alllinks}]]]) *glob()* - Expand the file wildcards in {expr}. See |wildcards| for the - use of special characters. - - Unless the optional {nosuf} argument is given and is |TRUE|, - the 'suffixes' and 'wildignore' options apply: Names matching - one of the patterns in 'wildignore' will be skipped and - 'suffixes' affect the ordering of matches. - 'wildignorecase' always applies. - - When {list} is present and it is |TRUE| the result is a |List| - with all matching files. The advantage of using a List is, - you also get filenames containing newlines correctly. - Otherwise the result is a String and when there are several - matches, they are separated by <NL> characters. - - If the expansion fails, the result is an empty String or List. - - You can also use |readdir()| if you need to do complicated - things, such as limiting the number of matches. - - A name for a non-existing file is not included. A symbolic - link is only included if it points to an existing file. - However, when the {alllinks} argument is present and it is - |TRUE| then all symbolic links are included. - - For most systems backticks can be used to get files names from - any external command. Example: > - :let tagfiles = glob("`find . -name tags -print`") - :let &tags = substitute(tagfiles, "\n", ",", "g") -< The result of the program inside the backticks should be one - item per line. Spaces inside an item are allowed. - - See |expand()| for expanding special Vim variables. See - |system()| for getting the raw output of an external command. - - Can also be used as a |method|: > - GetExpr()->glob() - -glob2regpat({string}) *glob2regpat()* - Convert a file pattern, as used by glob(), into a search - pattern. The result can be used to match with a string that - is a file name. E.g. > - if filename =~ glob2regpat('Make*.mak') -< This is equivalent to: > - if filename =~ '^Make.*\.mak$' -< When {string} is an empty string the result is "^$", match an - empty string. - Note that the result depends on the system. On MS-Windows - a backslash usually means a path separator. - - Can also be used as a |method|: > - GetExpr()->glob2regpat() -< *globpath()* -globpath({path}, {expr} [, {nosuf} [, {list} [, {allinks}]]]) - Perform glob() for String {expr} on all directories in {path} - and concatenate the results. Example: > - :echo globpath(&rtp, "syntax/c.vim") -< - {path} is a comma-separated list of directory names. Each - directory name is prepended to {expr} and expanded like with - |glob()|. A path separator is inserted when needed. - To add a comma inside a directory name escape it with a - backslash. Note that on MS-Windows a directory may have a - trailing backslash, remove it if you put a comma after it. - If the expansion fails for one of the directories, there is no - error message. - - Unless the optional {nosuf} argument is given and is |TRUE|, - the 'suffixes' and 'wildignore' options apply: Names matching - one of the patterns in 'wildignore' will be skipped and - 'suffixes' affect the ordering of matches. - - When {list} is present and it is |TRUE| the result is a |List| - with all matching files. The advantage of using a List is, you - also get filenames containing newlines correctly. Otherwise - the result is a String and when there are several matches, - they are separated by <NL> characters. Example: > - :echo globpath(&rtp, "syntax/c.vim", 0, 1) -< - {allinks} is used as with |glob()|. - - The "**" item can be used to search in a directory tree. - For example, to find all "README.txt" files in the directories - in 'runtimepath' and below: > - :echo globpath(&rtp, "**/README.txt") -< Upwards search and limiting the depth of "**" is not - supported, thus using 'path' will not always work properly. - - Can also be used as a |method|, the base is passed as the - second argument: > - GetExpr()->globpath(&rtp) -< - *has()* -has({feature}) Returns 1 if {feature} is supported, 0 otherwise. The - {feature} argument is a feature name like "nvim-0.2.1" or - "win32", see below. See also |exists()|. - - If the code has a syntax error, then Nvim may skip the rest - of the line and miss |:endif|. > - if has('feature') | let x = this->breaks->without->the->feature | endif -< - Put |:if| and |:endif| on separate lines to avoid the - syntax error. > - if has('feature') - let x = this->breaks->without->the->feature - endif -< - Vim's compile-time feature-names (prefixed with "+") are not - recognized because Nvim is always compiled with all possible - features. |feature-compile| - - Feature names can be: - 1. Nvim version. For example the "nvim-0.2.1" feature means - that Nvim is version 0.2.1 or later: > - :if has("nvim-0.2.1") - -< 2. Runtime condition or other pseudo-feature. For example the - "win32" feature checks if the current system is Windows: > - :if has("win32") -< *feature-list* - List of supported pseudo-feature names: - acl |ACL| support - bsd BSD system (not macOS, use "mac" for that). - iconv Can use |iconv()| for conversion. - +shellslash Can use backslashes in filenames (Windows) - clipboard |clipboard| provider is available. - fname_case Case in file names matters (for Darwin and MS-Windows - this is not present). - mac MacOS system. - nvim This is Nvim. - python2 Legacy Vim |python2| interface. |has-python| - python3 Legacy Vim |python3| interface. |has-python| - pythonx Legacy Vim |python_x| interface. |has-pythonx| - ttyin input is a terminal (tty) - ttyout output is a terminal (tty) - unix Unix system. - *vim_starting* True during |startup|. - win32 Windows system (32 or 64 bit). - win64 Windows system (64 bit). - wsl WSL (Windows Subsystem for Linux) system - - *has-patch* - 3. Vim patch. For example the "patch123" feature means that - Vim patch 123 at the current |v:version| was included: > - :if v:version > 602 || v:version == 602 && has("patch148") - -< 4. Vim version. For example the "patch-7.4.237" feature means - that Nvim is Vim-compatible to version 7.4.237 or later. > - :if has("patch-7.4.237") - - -has_key({dict}, {key}) *has_key()* - The result is a Number, which is TRUE if |Dictionary| {dict} - has an entry with key {key}. FALSE otherwise. The {key} - argument is a string. - - Can also be used as a |method|: > - mydict->has_key(key) - -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. - - Tabs and windows are identified by their respective numbers, - 0 means current tab or window. Missing argument implies 0. - Thus the following are equivalent: > - haslocaldir() - haslocaldir(0) - haslocaldir(0, 0) -< With {winnr} use that window in the current tabpage. - With {winnr} and {tabnr} use the window in that tabpage. - {winnr} can be the window number or the |window-ID|. - If {winnr} is -1 it is ignored, only the tab is resolved. - Throw error if the arguments are invalid. |E5000| |E5001| |E5002| - - Can also be used as a |method|: > - GetWinnr()->haslocaldir() - -hasmapto({what} [, {mode} [, {abbr}]]) *hasmapto()* - The result is a Number, which is TRUE if there is a mapping - that contains {what} in somewhere in the rhs (what it is - mapped to) and this mapping exists in one of the modes - indicated by {mode}. - The arguments {what} and {mode} are strings. - When {abbr} is there and it is |TRUE| use abbreviations - instead of mappings. Don't forget to specify Insert and/or - Command-line mode. - Both the global mappings and the mappings local to the current - buffer are checked for a match. - If no matching mapping is found FALSE is returned. - The following characters are recognized in {mode}: - n Normal mode - v Visual and Select mode - x Visual mode - s Select mode - o Operator-pending mode - i Insert mode - l Language-Argument ("r", "f", "t", etc.) - c Command-line mode - When {mode} is omitted, "nvo" is used. - - This function is useful to check if a mapping already exists - to a function in a Vim script. Example: > - :if !hasmapto('\ABCdoit') - : map <Leader>d \ABCdoit - :endif -< This installs the mapping to "\ABCdoit" only if there isn't - already a mapping to "\ABCdoit". - - Can also be used as a |method|: > - GetRHS()->hasmapto() - -histadd({history}, {item}) *histadd()* - Add the String {item} to the history {history} which can be - one of: *hist-names* - "cmd" or ":" command line history - "search" or "/" search pattern history - "expr" or "=" typed expression history - "input" or "@" input line history - "debug" or ">" debug command history - empty the current or last used history - The {history} string does not need to be the whole name, one - character is sufficient. - If {item} does already exist in the history, it will be - shifted to become the newest entry. - The result is a Number: TRUE if the operation was successful, - otherwise FALSE is returned. - - Example: > - :call histadd("input", strftime("%Y %b %d")) - :let date=input("Enter date: ") -< This function is not available in the |sandbox|. - - Can also be used as a |method|, the base is passed as the - second argument: > - GetHistory()->histadd('search') - -histdel({history} [, {item}]) *histdel()* - Clear {history}, i.e. delete all its entries. See |hist-names| - for the possible values of {history}. - - If the parameter {item} evaluates to a String, it is used as a - regular expression. All entries matching that expression will - be removed from the history (if there are any). - Upper/lowercase must match, unless "\c" is used |/\c|. - If {item} evaluates to a Number, it will be interpreted as - an index, see |:history-indexing|. The respective entry will - be removed if it exists. - - The result is TRUE for a successful operation, otherwise FALSE - is returned. - - Examples: - Clear expression register history: > - :call histdel("expr") -< - Remove all entries starting with "*" from the search history: > - :call histdel("/", '^\*') -< - The following three are equivalent: > - :call histdel("search", histnr("search")) - :call histdel("search", -1) - :call histdel("search", '^'.histget("search", -1).'$') -< - To delete the last search pattern and use the last-but-one for - the "n" command and 'hlsearch': > - :call histdel("search", -1) - :let @/ = histget("search", -1) -< - Can also be used as a |method|: > - GetHistory()->histdel() - -histget({history} [, {index}]) *histget()* - The result is a String, the entry with Number {index} from - {history}. See |hist-names| for the possible values of - {history}, and |:history-indexing| for {index}. If there is - no such entry, an empty String is returned. When {index} is - omitted, the most recent item from the history is used. - - Examples: - Redo the second last search from history. > - :execute '/' . histget("search", -2) - -< Define an Ex command ":H {num}" that supports re-execution of - the {num}th entry from the output of |:history|. > - :command -nargs=1 H execute histget("cmd", 0+<args>) -< - Can also be used as a |method|: > - GetHistory()->histget() - -histnr({history}) *histnr()* - The result is the Number of the current entry in {history}. - See |hist-names| for the possible values of {history}. - If an error occurred, -1 is returned. - - Example: > - :let inp_index = histnr("expr") - -< Can also be used as a |method|: > - GetHistory()->histnr() -< -hlexists({name}) *hlexists()* - The result is a Number, which is TRUE if a highlight group - called {name} exists. This is when the group has been - defined in some way. Not necessarily when highlighting has - been defined for it, it may also have been used for a syntax - item. - - Can also be used as a |method|: > - GetName()->hlexists() -< - *hlID()* -hlID({name}) The result is a Number, which is the ID of the highlight group - with name {name}. When the highlight group doesn't exist, - zero is returned. - This can be used to retrieve information about the highlight - group. For example, to get the background color of the - "Comment" group: > - :echo synIDattr(synIDtrans(hlID("Comment")), "bg") -< - Can also be used as a |method|: > - GetName()->hlID() - -hostname() *hostname()* - The result is a String, which is the name of the machine on - which Vim is currently running. Machine names greater than - 256 characters long are truncated. - -iconv({string}, {from}, {to}) *iconv()* - The result is a String, which is the text {string} converted - from encoding {from} to encoding {to}. - When the conversion completely fails an empty string is - returned. When some characters could not be converted they - are replaced with "?". - The encoding names are whatever the iconv() library function - can accept, see ":!man 3 iconv". - Note that Vim uses UTF-8 for all Unicode encodings, conversion - from/to UCS-2 is automatically changed to use UTF-8. You - cannot use UCS-2 in a string anyway, because of the NUL bytes. - - Can also be used as a |method|: > - GetText()->iconv('latin1', 'utf-8') -< - *indent()* -indent({lnum}) The result is a Number, which is indent of line {lnum} in the - current buffer. The indent is counted in spaces, the value - of 'tabstop' is relevant. {lnum} is used just like in - |getline()|. - When {lnum} is invalid -1 is returned. - - Can also be used as a |method|: > - GetLnum()->indent() - -index({object}, {expr} [, {start} [, {ic}]]) *index()* - If {object} is a |List| return the lowest index where the item - has a value equal to {expr}. There is no automatic - conversion, so the String "4" is different from the Number 4. - And the Number 4 is different from the Float 4.0. The value - of 'ignorecase' is not used here, case always matters. - - If {object} is a |Blob| return the lowest index where the byte - value is equal to {expr}. - - If {start} is given then start looking at the item with index - {start} (may be negative for an item relative to the end). - When {ic} is given and it is |TRUE|, ignore case. Otherwise - case must match. - -1 is returned when {expr} is not found in {object}. - Example: > - :let idx = index(words, "the") - :if index(numbers, 123) >= 0 - -< Can also be used as a |method|: > - GetObject()->index(what) - -input({prompt} [, {text} [, {completion}]]) *input()* -input({opts}) - The result is a String, which is whatever the user typed on - the command-line. The {prompt} argument is either a prompt - string, or a blank string (for no prompt). A '\n' can be used - in the prompt to start a new line. - - In the second form it accepts a single dictionary with the - following keys, any of which may be omitted: - - Key Default Description ~ - prompt "" Same as {prompt} in the first form. - default "" Same as {text} in the first form. - completion nothing Same as {completion} in the first form. - cancelreturn "" The value returned when the dialog is - cancelled. - highlight nothing Highlight handler: |Funcref|. - - The highlighting set with |:echohl| is used for the prompt. - The input is entered just like a command-line, with the same - editing commands and mappings. There is a separate history - for lines typed for input(). - Example: > - :if input("Coffee or beer? ") == "beer" - : echo "Cheers!" - :endif -< - If the optional {text} argument is present and not empty, this - is used for the default reply, as if the user typed this. - Example: > - :let color = input("Color? ", "white") - -< The optional {completion} argument specifies the type of - completion supported for the input. Without it completion is - not performed. The supported completion types are the same as - that can be supplied to a user-defined command using the - "-complete=" argument. Refer to |:command-completion| for - more information. Example: > - let fname = input("File: ", "", "file") - -< *input()-highlight* *E5400* *E5402* - The optional `highlight` key allows specifying function which - will be used for highlighting user input. This function - receives user input as its only argument and must return - a list of 3-tuples [hl_start_col, hl_end_col + 1, hl_group] - where - hl_start_col is the first highlighted column, - hl_end_col is the last highlighted column (+ 1!), - hl_group is |:hi| group used for highlighting. - *E5403* *E5404* *E5405* *E5406* - Both hl_start_col and hl_end_col + 1 must point to the start - of the multibyte character (highlighting must not break - multibyte characters), hl_end_col + 1 may be equal to the - input length. Start column must be in range [0, len(input)), - end column must be in range (hl_start_col, len(input)], - sections must be ordered so that next hl_start_col is greater - then or equal to previous hl_end_col. - - Example (try some input with parentheses): > - highlight RBP1 guibg=Red ctermbg=red - highlight RBP2 guibg=Yellow ctermbg=yellow - highlight RBP3 guibg=Green ctermbg=green - highlight RBP4 guibg=Blue ctermbg=blue - let g:rainbow_levels = 4 - function! RainbowParens(cmdline) - let ret = [] - let i = 0 - let lvl = 0 - while i < len(a:cmdline) - if a:cmdline[i] is# '(' - call add(ret, [i, i + 1, 'RBP' . ((lvl % g:rainbow_levels) + 1)]) - let lvl += 1 - elseif a:cmdline[i] is# ')' - let lvl -= 1 - call add(ret, [i, i + 1, 'RBP' . ((lvl % g:rainbow_levels) + 1)]) - endif - let i += 1 - endwhile - return ret - endfunction - call input({'prompt':'>','highlight':'RainbowParens'}) -< - Highlight function is called at least once for each new - displayed input string, before command-line is redrawn. It is - expected that function is pure for the duration of one input() - call, i.e. it produces the same output for the same input, so - output may be memoized. Function is run like under |:silent| - modifier. If the function causes any errors, it will be - skipped for the duration of the current input() call. - - Highlighting is disabled if command-line contains arabic - characters. - - NOTE: This function must not be used in a startup file, for - the versions that only run in GUI mode (e.g., the Win32 GUI). - Note: When input() is called from within a mapping it will - consume remaining characters from that mapping, because a - mapping is handled like the characters were typed. - Use |inputsave()| before input() and |inputrestore()| - after input() to avoid that. Another solution is to avoid - that further characters follow in the mapping, e.g., by using - |:execute| or |:normal|. - - Example with a mapping: > - :nmap \x :call GetFoo()<CR>:exe "/" . Foo<CR> - :function GetFoo() - : call inputsave() - : let g:Foo = input("enter search pattern: ") - : call inputrestore() - :endfunction - -< Can also be used as a |method|: > - GetPrompt()->input() - -inputlist({textlist}) *inputlist()* - {textlist} must be a |List| of strings. This |List| is - displayed, one string per line. The user will be prompted to - enter a number, which is returned. - The user can also select an item by clicking on it with the - mouse, if the mouse is enabled in the command line ('mouse' is - "a" or includes "c"). For the first string 0 is returned. - When clicking above the first item a negative number is - returned. When clicking on the prompt one more than the - length of {textlist} is returned. - Make sure {textlist} has less than 'lines' entries, otherwise - it won't work. It's a good idea to put the entry number at - the start of the string. And put a prompt in the first item. - Example: > - let color = inputlist(['Select color:', '1. red', - \ '2. green', '3. blue']) - -< Can also be used as a |method|: > - GetChoices()->inputlist() - -inputrestore() *inputrestore()* - Restore typeahead that was saved with a previous |inputsave()|. - Should be called the same number of times inputsave() is - called. Calling it more often is harmless though. - Returns TRUE when there is nothing to restore, FALSE otherwise. - -inputsave() *inputsave()* - Preserve typeahead (also from mappings) and clear it, so that - a following prompt gets input from the user. Should be - followed by a matching inputrestore() after the prompt. Can - be used several times, in which case there must be just as - many inputrestore() calls. - Returns TRUE when out of memory, FALSE otherwise. - -inputsecret({prompt} [, {text}]) *inputsecret()* - This function acts much like the |input()| function with but - two exceptions: - a) the user's response will be displayed as a sequence of - asterisks ("*") thereby keeping the entry secret, and - b) the user's response will not be recorded on the input - |history| stack. - The result is a String, which is whatever the user actually - typed on the command-line in response to the issued prompt. - NOTE: Command-line completion is not supported. - - Can also be used as a |method|: > - GetPrompt()->inputsecret() - -insert({object}, {item} [, {idx}]) *insert()* - When {object} is a |List| or a |Blob| insert {item} at the start - of it. - - If {idx} is specified insert {item} before the item with index - {idx}. If {idx} is zero it goes before the first item, just - like omitting {idx}. A negative {idx} is also possible, see - |list-index|. -1 inserts just before the last item. - - Returns the resulting |List| or |Blob|. Examples: > - :let mylist = insert([2, 3, 5], 1) - :call insert(mylist, 4, -1) - :call insert(mylist, 6, len(mylist)) -< The last example can be done simpler with |add()|. - Note that when {item} is a |List| it is inserted as a single - item. Use |extend()| to concatenate |Lists|. - - Can also be used as a |method|: > - mylist->insert(item) - -interrupt() *interrupt()* - Interrupt script execution. It works more or less like the - user typing CTRL-C, most commands won't execute and control - returns to the user. This is useful to abort execution - from lower down, e.g. in an autocommand. Example: > - :function s:check_typoname(file) - : if fnamemodify(a:file, ':t') == '[' - : echomsg 'Maybe typo' - : call interrupt() - : endif - :endfunction - :au BufWritePre * call s:check_typoname(expand('<amatch>')) - -invert({expr}) *invert()* - Bitwise invert. The argument is converted to a number. A - List, Dict or Float argument causes an error. Example: > - :let bits = invert(bits) -< Can also be used as a |method|: > - :let bits = bits->invert() - -isdirectory({directory}) *isdirectory()* - The result is a Number, which is |TRUE| when a directory - with the name {directory} exists. If {directory} doesn't - exist, or isn't a directory, the result is |FALSE|. {directory} - is any expression, which is used as a String. - - Can also be used as a |method|: > - GetName()->isdirectory() - -isinf({expr}) *isinf()* - Return 1 if {expr} is a positive infinity, or -1 a negative - infinity, otherwise 0. > - :echo isinf(1.0 / 0.0) -< 1 > - :echo isinf(-1.0 / 0.0) -< -1 - - Can also be used as a |method|: > - Compute()->isinf() - -islocked({expr}) *islocked()* *E786* - The result is a Number, which is |TRUE| when {expr} is the - name of a locked variable. - The string argument {expr} must be the name of a variable, - |List| item or |Dictionary| entry, not the variable itself! - Example: > - :let alist = [0, ['a', 'b'], 2, 3] - :lockvar 1 alist - :echo islocked('alist') " 1 - :echo islocked('alist[1]') " 0 - -< When {expr} is a variable that does not exist you get an error - message. Use |exists()| to check for existence. - - Can also be used as a |method|: > - GetName()->islocked() - -id({expr}) *id()* - Returns a |String| which is a unique identifier of the - container type (|List|, |Dict|, |Blob| and |Partial|). It is - guaranteed that for the mentioned types `id(v1) ==# id(v2)` - returns true iff `type(v1) == type(v2) && v1 is v2`. - Note that |v:_null_string|, |v:_null_list|, |v:_null_dict| and - |v:_null_blob| have the same `id()` with different types - because they are internally represented as NULL pointers. - `id()` returns a hexadecimal representanion of the pointers to - the containers (i.e. like `0x994a40`), same as `printf("%p", - {expr})`, but it is advised against counting on the exact - format of the return value. - - It is not guaranteed that `id(no_longer_existing_container)` - will not be equal to some other `id()`: new containers may - reuse identifiers of the garbage-collected ones. - -items({dict}) *items()* - Return a |List| with all the key-value pairs of {dict}. Each - |List| item is a list with two items: the key of a {dict} - entry and the value of this entry. The |List| is in arbitrary - order. Also see |keys()| and |values()|. - Example: > - for [key, value] in items(mydict) - echo key . ': ' . value - endfor - -< Can also be used as a |method|: > - mydict->items() - -isnan({expr}) *isnan()* - Return |TRUE| if {expr} is a float with value NaN. > - echo isnan(0.0 / 0.0) -< 1 - - Can also be used as a |method|: > - Compute()->isnan() - -jobpid({job}) *jobpid()* - Return the PID (process id) of |job-id| {job}. - -jobresize({job}, {width}, {height}) *jobresize()* - Resize the pseudo terminal window of |job-id| {job} to {width} - columns and {height} rows. - Fails if the job was not started with `"pty":v:true`. - -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: > - :call jobstart(split(&shell) + split(&shellcmdflag) + ['{cmd}']) -< (See |shell-unquoting| for details.) - - Example: > - :call jobstart('nvim -h', {'on_stdout':{j,d,e->append(line('.'),d)}}) -< - Returns |job-id| on success, 0 on invalid arguments (or job - table is full), -1 if {cmd}[0] or 'shell' is not executable. - The returned job-id is a valid |channel-id| representing the - job's stdio streams. Use |chansend()| (or |rpcnotify()| and - |rpcrequest()| if "rpc" was enabled) to send data to stdin and - |chanclose()| to close the streams without stopping the job. - - See |job-control| and |RPC|. - - NOTE: on Windows if {cmd} is a List: - - cmd[0] must be an executable (not a "built-in"). If it is - in $PATH it can be called by name, without an extension: > - :call jobstart(['ping', 'neovim.io']) -< If it is a full or partial path, extension is required: > - :call jobstart(['System32\ping.exe', 'neovim.io']) -< - {cmd} is collapsed to a string of quoted args as expected - by CommandLineToArgvW https://msdn.microsoft.com/bb776391 - unless cmd[0] is some form of "cmd.exe". - - *jobstart-options* - {opts} is a dictionary with these keys: - clear_env: (boolean) `env` defines the job environment - exactly, instead of merging current environment. - cwd: (string, default=|current-directory|) Working - directory of the job. - detach: (boolean) Detach the job process: it will not be - killed when Nvim exits. If the process exits - before Nvim, `on_exit` will be invoked. - env: (dict) Map of environment variable name:value - pairs extending (or replacing if |clear_env|) - the current environment. - height: (number) Height of the `pty` terminal. - |on_exit|: (function) Callback invoked when the job exits. - |on_stdout|: (function) Callback invoked when the job emits - stdout data. - |on_stderr|: (function) Callback invoked when the job emits - stderr data. - overlapped: (boolean) Set FILE_FLAG_OVERLAPPED for the - standard input/output passed to the child process. - Normally you do not need to set this. - (Only available on MS-Windows, On other - platforms, this option is silently ignored.) - pty: (boolean) Connect the job to a new pseudo - terminal, and its streams to the master file - descriptor. Then `on_stderr` is ignored, - `on_stdout` receives all output. - rpc: (boolean) Use |msgpack-rpc| to communicate with - the job over stdio. Then `on_stdout` is ignored, - but `on_stderr` can still be used. - stderr_buffered: (boolean) Collect data until EOF (stream closed) - before invoking `on_stderr`. |channel-buffered| - stdout_buffered: (boolean) Collect data until EOF (stream - closed) before invoking `on_stdout`. |channel-buffered| - stdin: (string) Either "pipe" (default) to connect the - job's stdin to a channel or "null" to disconnect - stdin. - width: (number) Width of the `pty` terminal. - - {opts} is passed as |self| dictionary to the callback; the - caller may set other keys to pass application-specific data. - - Returns: - - |channel-id| on success - - 0 on invalid arguments - - -1 if {cmd}[0] is not executable. - See also |job-control|, |channel|, |msgpack-rpc|. - -jobstop({id}) *jobstop()* - Stop |job-id| {id} by sending SIGTERM to the job process. If - the process does not terminate after a timeout then SIGKILL - will be sent. When the job terminates its |on_exit| handler - (if any) will be invoked. - See |job-control|. - - Returns 1 for valid job id, 0 for invalid id, including jobs have - exited or stopped. - -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. - {timeout} is the maximum waiting time in milliseconds. If - omitted or -1, wait forever. - - Timeout of 0 can be used to check the status of a job: > - let running = jobwait([{job-id}], 0)[0] == -1 -< - During jobwait() callbacks for jobs not in the {jobs} list may - be invoked. The screen will not redraw unless |:redraw| is - invoked by a callback. - - Returns a list of len({jobs}) integers, where each integer is - the status of the corresponding job: - Exit-code, if the job exited - -1 if the timeout was exceeded - -2 if the job was interrupted (by |CTRL-C|) - -3 if the job-id is invalid - -join({list} [, {sep}]) *join()* - Join the items in {list} together into one String. - When {sep} is specified it is put in between the items. If - {sep} is omitted a single space is used. - Note that {sep} is not added at the end. You might want to - add it there too: > - let lines = join(mylist, "\n") . "\n" -< String items are used as-is. |Lists| and |Dictionaries| are - converted into a string like with |string()|. - The opposite function is |split()|. - - Can also be used as a |method|: > - mylist->join() - -json_decode({expr}) *json_decode()* - Convert {expr} from JSON object. Accepts |readfile()|-style - list as the input, as well as regular string. May output any - Vim value. In the following cases it will output - |msgpack-special-dict|: - 1. Dictionary contains duplicate key. - 2. Dictionary contains empty key. - 3. String contains NUL byte. Two special dictionaries: for - dictionary and for string will be emitted in case string - with NUL byte was a dictionary key. - - Note: function treats its input as UTF-8 always. The JSON - standard allows only a few encodings, of which UTF-8 is - recommended and the only one required to be supported. - Non-UTF-8 characters are an error. - - Can also be used as a |method|: > - ReadObject()->json_decode() - -json_encode({expr}) *json_encode()* - Convert {expr} into a JSON string. Accepts - |msgpack-special-dict| as the input. Will not convert - |Funcref|s, mappings with non-string keys (can be created as - |msgpack-special-dict|), values with self-referencing - containers, strings which contain non-UTF-8 characters, - pseudo-UTF-8 strings which contain codepoints reserved for - surrogate pairs (such strings are not valid UTF-8 strings). - Non-printable characters are converted into "\u1234" escapes - or special escapes like "\t", other are dumped as-is. - |Blob|s are converted to arrays of the individual bytes. - - Can also be used as a |method|: > - GetObject()->json_encode() - -keys({dict}) *keys()* - Return a |List| with all the keys of {dict}. The |List| is in - arbitrary order. Also see |items()| and |values()|. - - Can also be used as a |method|: > - mydict->keys() - -< *len()* *E701* -len({expr}) The result is a Number, which is the length of the argument. - When {expr} is a String or a Number the length in bytes is - used, as with |strlen()|. - When {expr} is a |List| the number of items in the |List| is - returned. - When {expr} is a |Blob| the number of bytes is returned. - When {expr} is a |Dictionary| the number of entries in the - |Dictionary| is returned. - Otherwise an error is given. - - Can also be used as a |method|: > - mylist->len() - -< *libcall()* *E364* *E368* -libcall({libname}, {funcname}, {argument}) - Call function {funcname} in the run-time library {libname} - with single argument {argument}. - This is useful to call functions in a library that you - especially made to be used with Vim. Since only one argument - is possible, calling standard library functions is rather - limited. - The result is the String returned by the function. If the - function returns NULL, this will appear as an empty string "" - to Vim. - If the function returns a number, use libcallnr()! - If {argument} is a number, it is passed to the function as an - int; if {argument} is a string, it is passed as a - null-terminated string. - - libcall() allows you to write your own 'plug-in' extensions to - Vim without having to recompile the program. It is NOT a - means to call system functions! If you try to do so Vim will - very probably crash. - - For Win32, the functions you write must be placed in a DLL - and use the normal C calling convention (NOT Pascal which is - used in Windows System DLLs). The function must take exactly - one parameter, either a character pointer or a long integer, - and must return a character pointer or NULL. The character - pointer returned must point to memory that will remain valid - after the function has returned (e.g. in static data in the - DLL). If it points to allocated memory, that memory will - leak away. Using a static buffer in the function should work, - it's then freed when the DLL is unloaded. - - WARNING: If the function returns a non-valid pointer, Vim may - crash! This also happens if the function returns a number, - because Vim thinks it's a pointer. - For Win32 systems, {libname} should be the filename of the DLL - without the ".DLL" suffix. A full path is only required if - the DLL is not in the usual places. - For Unix: When compiling your own plugins, remember that the - object code must be compiled as position-independent ('PIC'). - Examples: > - :echo libcall("libc.so", "getenv", "HOME") - -< Can also be used as a |method|, the base is passed as the - third argument: > - GetValue()->libcall("libc.so", "getenv") -< - *libcallnr()* -libcallnr({libname}, {funcname}, {argument}) - Just like |libcall()|, but used for a function that returns an - int instead of a string. - Examples: > - :echo libcallnr("/usr/lib/libc.so", "getpid", "") - :call libcallnr("libc.so", "printf", "Hello World!\n") - :call libcallnr("libc.so", "sleep", 10) -< - Can also be used as a |method|, the base is passed as the - third argument: > - GetValue()->libcallnr("libc.so", "printf") -< -line({expr} [, {winid}]) *line()* - The result is a Number, which is the line number of the file - position given with {expr}. The {expr} argument is a string. - The accepted positions are: - . the cursor position - $ the last line in the current buffer - 'x position of mark x (if the mark is not set, 0 is - returned) - w0 first line visible in current window (one if the - display isn't updated, e.g. in silent Ex mode) - w$ last line visible in current window (this is one - less than "w0" if no lines are visible) - v In Visual mode: the start of the Visual area (the - cursor is the end). When not in Visual mode - returns the cursor position. Differs from |'<| in - that it's updated right away. - Note that a mark in another file can be used. The line number - then applies to another buffer. - To get the column number use |col()|. To get both use - |getpos()|. - With the optional {winid} argument the values are obtained for - that window instead of the current window. - Examples: > - line(".") line number of the cursor - line(".", winid) idem, in window "winid" - line("'t") line number of mark t - line("'" . marker) line number of mark marker -< - Can also be used as a |method|: > - GetValue()->line() - -line2byte({lnum}) *line2byte()* - Return the byte count from the start of the buffer for line - {lnum}. This includes the end-of-line character, depending on - the 'fileformat' option for the current buffer. The first - line returns 1. UTF-8 encoding is used, 'fileencoding' is - ignored. This can also be used to get the byte count for the - line just below the last line: > - line2byte(line("$") + 1) -< This is the buffer size plus one. If 'fileencoding' is empty - it is the file size plus one. {lnum} is used like with - |getline()|. When {lnum} is invalid -1 is returned. - Also see |byte2line()|, |go| and |:goto|. - - Can also be used as a |method|: > - GetLnum()->line2byte() - -lispindent({lnum}) *lispindent()* - Get the amount of indent for line {lnum} according the lisp - indenting rules, as with 'lisp'. - The indent is counted in spaces, the value of 'tabstop' is - relevant. {lnum} is used just like in |getline()|. - When {lnum} is invalid, -1 is returned. - - Can also be used as a |method|: > - GetLnum()->lispindent() - -list2str({list} [, {utf8}]) *list2str()* - Convert each number in {list} to a character string can - concatenate them all. Examples: > - list2str([32]) returns " " - list2str([65, 66, 67]) returns "ABC" -< The same can be done (slowly) with: > - join(map(list, {nr, val -> nr2char(val)}), '') -< |str2list()| does the opposite. - - UTF-8 encoding is always used, {utf8} option has no effect, - and exists only for backwards-compatibility. - With UTF-8 composing characters work as expected: > - list2str([97, 769]) returns "á" -< - Can also be used as a |method|: > - GetList()->list2str() - -localtime() *localtime()* - Return the current time, measured as seconds since 1st Jan - 1970. See also |strftime()|, |strptime()| and |getftime()|. - - -log({expr}) *log()* - Return the natural logarithm (base e) of {expr} as a |Float|. - {expr} must evaluate to a |Float| or a |Number| in the range - (0, inf]. - Examples: > - :echo log(10) -< 2.302585 > - :echo log(exp(5)) -< 5.0 - - Can also be used as a |method|: > - Compute()->log() - -log10({expr}) *log10()* - Return the logarithm of Float {expr} to base 10 as a |Float|. - {expr} must evaluate to a |Float| or a |Number|. - Examples: > - :echo log10(1000) -< 3.0 > - :echo log10(0.01) -< -2.0 - - Can also be used as a |method|: > - Compute()->log10() - -luaeval({expr}[, {expr}]) - Evaluate Lua expression {expr} and return its result converted - to Vim data structures. See |lua-eval| for more details. - - Can also be used as a |method|: > - GetExpr()->luaeval() - -map({expr1}, {expr2}) *map()* - {expr1} must be a |List|, |Blob| or |Dictionary|. - Replace each item in {expr1} with the result of evaluating - {expr2}. For a |Blob| each byte is replaced. - - {expr2} must be a |string| or |Funcref|. - - If {expr2} is a |string|, inside {expr2} |v:val| has the value - of the current item. For a |Dictionary| |v:key| has the key - of the current item and for a |List| |v:key| has the index of - the current item. For a |Blob| |v:key| has the index of the - current byte. - Example: > - :call map(mylist, '"> " . v:val . " <"') -< This puts "> " before and " <" after each item in "mylist". - - Note that {expr2} is the result of an expression and is then - used as an expression again. Often it is good to use a - |literal-string| to avoid having to double backslashes. You - still have to double ' quotes - - If {expr2} is a |Funcref| it is called with two arguments: - 1. The key or the index of the current item. - 2. the value of the current item. - The function must return the new value of the item. Example - that changes each value by "key-value": > - func KeyValue(key, val) - return a:key . '-' . a:val - endfunc - call map(myDict, function('KeyValue')) -< It is shorter when using a |lambda|: > - call map(myDict, {key, val -> key . '-' . val}) -< If you do not use "val" you can leave it out: > - call map(myDict, {key -> 'item: ' . key}) -< If you do not use "key" you can use a short name: > - call map(myDict, {_, val -> 'item: ' . val}) -< - The operation is done in-place. If you want a |List| or - |Dictionary| to remain unmodified make a copy first: > - :let tlist = map(copy(mylist), ' v:val . "\t"') - -< Returns {expr1}, the |List|, |Blob| or |Dictionary| that was - filtered. When an error is encountered while evaluating - {expr2} no further items in {expr1} are processed. When - {expr2} is a Funcref errors inside a function are ignored, - unless it was defined with the "abort" flag. - - Can also be used as a |method|: > - mylist->map(expr2) - -maparg({name} [, {mode} [, {abbr} [, {dict}]]]) *maparg()* - When {dict} is omitted or zero: Return the rhs of mapping - {name} in mode {mode}. The returned String has special - characters translated like in the output of the ":map" command - listing. - - When there is no mapping for {name}, an empty String is - returned. When the mapping for {name} is empty, then "<Nop>" - is returned. - - The {name} can have special key names, like in the ":map" - command. - - {mode} can be one of these strings: - "n" Normal - "v" Visual (including Select) - "o" Operator-pending - "i" Insert - "c" Cmd-line - "s" Select - "x" Visual - "l" langmap |language-mapping| - "t" Terminal - "" Normal, Visual and Operator-pending - When {mode} is omitted, the modes for "" are used. - - When {abbr} is there and it is |TRUE| use abbreviations - instead of mappings. - - When {dict} is there and it is |TRUE| return a dictionary - containing all the information of the mapping with the - following items: - "lhs" The {lhs} of the mapping. - "rhs" The {rhs} of the mapping as typed. - "silent" 1 for a |:map-silent| mapping, else 0. - "noremap" 1 if the {rhs} of the mapping is not remappable. - "script" 1 if mapping was defined with <script>. - "expr" 1 for an expression mapping (|:map-<expr>|). - "buffer" 1 for a buffer local mapping (|:map-local|). - "mode" Modes for which the mapping is defined. In - addition to the modes mentioned above, these - characters will be used: - " " Normal, Visual and Operator-pending - "!" Insert and Commandline mode - (|mapmode-ic|) - "sid" The script local ID, used for <sid> mappings - (|<SID>|). - "lnum" The line number in "sid", zero if unknown. - "nowait" Do not wait for other, longer mappings. - (|:map-<nowait>|). - - The mappings local to the current buffer are checked first, - then the global mappings. - This function can be used to map a key even when it's already - mapped, and have it do the original mapping too. Sketch: > - exe 'nnoremap <Tab> ==' . maparg('<Tab>', 'n') - -< Can also be used as a |method|: > - GetKey()->maparg('n') - -mapcheck({name} [, {mode} [, {abbr}]]) *mapcheck()* - Check if there is a mapping that matches with {name} in mode - {mode}. See |maparg()| for {mode} and special names in - {name}. - When {abbr} is there and it is non-zero use abbreviations - instead of mappings. - A match happens with a mapping that starts with {name} and - with a mapping which is equal to the start of {name}. - - matches mapping "a" "ab" "abc" ~ - mapcheck("a") yes yes yes - mapcheck("abc") yes yes yes - mapcheck("ax") yes no no - mapcheck("b") no no no - - The difference with maparg() is that mapcheck() finds a - mapping that matches with {name}, while maparg() only finds a - mapping for {name} exactly. - When there is no mapping that starts with {name}, an empty - String is returned. If there is one, the RHS of that mapping - is returned. If there are several mappings that start with - {name}, the RHS of one of them is returned. This will be - "<Nop>" if the RHS is empty. - The mappings local to the current buffer are checked first, - then the global mappings. - This function can be used to check if a mapping can be added - without being ambiguous. Example: > - :if mapcheck("_vv") == "" - : map _vv :set guifont=7x13<CR> - :endif -< This avoids adding the "_vv" mapping when there already is a - mapping for "_v" or for "_vvv". - - Can also be used as a |method|: > - GetKey()->mapcheck('n') - -match({expr}, {pat} [, {start} [, {count}]]) *match()* - When {expr} is a |List| then this returns the index of the - first item where {pat} matches. Each item is used as a - String, |Lists| and |Dictionaries| are used as echoed. - - Otherwise, {expr} is used as a String. The result is a - Number, which gives the index (byte offset) in {expr} where - {pat} matches. - - A match at the first character or |List| item returns zero. - If there is no match -1 is returned. - - For getting submatches see |matchlist()|. - Example: > - :echo match("testing", "ing") " results in 4 - :echo match([1, 'x'], '\a') " results in 1 -< See |string-match| for how {pat} is used. - *strpbrk()* - Vim doesn't have a strpbrk() function. But you can do: > - :let sepidx = match(line, '[.,;: \t]') -< *strcasestr()* - Vim doesn't have a strcasestr() function. But you can add - "\c" to the pattern to ignore case: > - :let idx = match(haystack, '\cneedle') -< - If {start} is given, the search starts from byte index - {start} in a String or item {start} in a |List|. - The result, however, is still the index counted from the - first character/item. Example: > - :echo match("testing", "ing", 2) -< result is again "4". > - :echo match("testing", "ing", 4) -< result is again "4". > - :echo match("testing", "t", 2) -< result is "3". - For a String, if {start} > 0 then it is like the string starts - {start} bytes later, thus "^" will match at {start}. Except - when {count} is given, then it's like matches before the - {start} byte are ignored (this is a bit complicated to keep it - backwards compatible). - For a String, if {start} < 0, it will be set to 0. For a list - the index is counted from the end. - If {start} is out of range ({start} > strlen({expr}) for a - String or {start} > len({expr}) for a |List|) -1 is returned. - - When {count} is given use the {count}'th match. When a match - is found in a String the search for the next one starts one - character further. Thus this example results in 1: > - echo match("testing", "..", 0, 2) -< In a |List| the search continues in the next item. - Note that when {count} is added the way {start} works changes, - see above. - - See |pattern| for the patterns that are accepted. - The 'ignorecase' option is used to set the ignore-caseness of - the pattern. 'smartcase' is NOT used. The matching is always - done like 'magic' is set and 'cpoptions' is empty. - Note that a match at the start is preferred, thus when the - pattern is using "*" (any number of matches) it tends to find - zero matches at the start instead of a number of matches - further down in the text. - - Can also be used as a |method|: > - GetText()->match('word') - GetList()->match('word') -< - *matchadd()* *E798* *E799* *E801* *E957* -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 - match using |matchdelete()|. The ID is bound to the window. - Matching is case sensitive and magic, unless case sensitivity - or magicness are explicitly overridden in {pattern}. The - 'magic', 'smartcase' and 'ignorecase' options are not used. - The "Conceal" value is special, it causes the match to be - concealed. - - The optional {priority} argument assigns a priority to the - match. A match with a high priority will have its - highlighting overrule that of a match with a lower priority. - A priority is specified as an integer (negative numbers are no - exception). If the {priority} argument is not specified, the - default priority is 10. The priority of 'hlsearch' is zero, - hence all matches with a priority greater than zero will - overrule it. Syntax highlighting (see 'syntax') is a separate - mechanism, and regardless of the chosen priority a match will - always overrule syntax highlighting. - - The optional {id} argument allows the request for a specific - match ID. If a specified ID is already taken, an error - message will appear and the match will not be added. An ID - is specified as a positive integer (zero excluded). IDs 1, 2 - and 3 are reserved for |:match|, |:2match| and |:3match|, - respectively. If the {id} argument is not specified or -1, - |matchadd()| automatically chooses a free ID. - - The optional {dict} argument allows for further custom - values. Currently this is used to specify a match specific - conceal character that will be shown for |hl-Conceal| - highlighted matches. The dict can have the following members: - - conceal Special character to show instead of the - match (only for |hl-Conceal| highlighed - matches, see |:syn-cchar|) - window Instead of the current window use the - window with this number or window ID. - - The number of matches is not limited, as it is the case with - the |:match| commands. - - Example: > - :highlight MyGroup ctermbg=green guibg=green - :let m = matchadd("MyGroup", "TODO") -< Deletion of the pattern: > - :call matchdelete(m) - -< A list of matches defined by |matchadd()| and |:match| are - available from |getmatches()|. All matches can be deleted in - one operation by |clearmatches()|. - - Can also be used as a |method|: > - GetGroup()->matchadd('TODO') -< - *matchaddpos()* -matchaddpos({group}, {pos} [, {priority} [, {id} [, {dict}]]]) - Same as |matchadd()|, but requires a list of positions {pos} - instead of a pattern. This command is faster than |matchadd()| - because it does not require to handle regular expressions and - sets buffer line boundaries to redraw screen. It is supposed - to be used when fast match additions and deletions are - required, for example to highlight matching parentheses. - *E5030* *E5031* - {pos} is a list of positions. Each position can be one of - these: - - A number. This whole line will be highlighted. The first - line has number 1. - - A list with one number, e.g., [23]. The whole line with this - number will be highlighted. - - A list with two numbers, e.g., [23, 11]. The first number is - the line number, the second one is the column number (first - column is 1, the value must correspond to the byte index as - |col()| would return). The character at this position will - be highlighted. - - A list with three numbers, e.g., [23, 11, 3]. As above, but - the third number gives the length of the highlight in bytes. - - Entries with zero and negative line numbers are silently - ignored, as well as entries with negative column numbers and - lengths. - - The maximum number of positions in {pos} is 8. - - Example: > - :highlight MyGroup ctermbg=green guibg=green - :let m = matchaddpos("MyGroup", [[23, 24], 34]) -< Deletion of the pattern: > - :call matchdelete(m) - -< Matches added by |matchaddpos()| are returned by - |getmatches()|. - - Can also be used as a |method|: > - GetGroup()->matchaddpos([23, 11]) - -matcharg({nr}) *matcharg()* - Selects the {nr} match item, as set with a |:match|, - |:2match| or |:3match| command. - Return a |List| with two elements: - The name of the highlight group used - The pattern used. - When {nr} is not 1, 2 or 3 returns an empty |List|. - When there is no match item set returns ['', '']. - This is useful to save and restore a |:match|. - Highlighting matches using the |:match| commands are limited - to three matches. |matchadd()| does not have this limitation. - - Can also be used as a |method|: > - GetMatch()->matcharg() - -matchdelete({id} [, {win}]) *matchdelete()* *E802* *E803* - Deletes a match with ID {id} previously defined by |matchadd()| - or one of the |:match| commands. Returns 0 if successful, - otherwise -1. See example for |matchadd()|. All matches can - be deleted in one operation by |clearmatches()|. - If {win} is specified, use the window with this number or - window ID instead of the current window. - - Can also be used as a |method|: > - GetMatch()->matchdelete() - -matchend({expr}, {pat} [, {start} [, {count}]]) *matchend()* - Same as |match()|, but return the index of first character - after the match. Example: > - :echo matchend("testing", "ing") -< results in "7". - *strspn()* *strcspn()* - Vim doesn't have a strspn() or strcspn() function, but you can - do it with matchend(): > - :let span = matchend(line, '[a-zA-Z]') - :let span = matchend(line, '[^a-zA-Z]') -< Except that -1 is returned when there are no matches. - - The {start}, if given, has the same meaning as for |match()|. > - :echo matchend("testing", "ing", 2) -< results in "7". > - :echo matchend("testing", "ing", 5) -< result is "-1". - When {expr} is a |List| the result is equal to |match()|. - - Can also be used as a |method|: > - GetText()->matchend('word') - -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 - return. Following items are submatches, like "\1", "\2", etc. - in |:substitute|. When an optional submatch didn't match an - empty string is used. Example: > - echo matchlist('acd', '\(a\)\?\(b\)\?\(c\)\?\(.*\)') -< Results in: ['acd', 'a', '', 'c', 'd', '', '', '', '', ''] - When there is no match an empty list is returned. - - You can pass in a List, but that is not very useful. - - Can also be used as a |method|: > - GetText()->matchlist('word') - -matchstr({expr}, {pat} [, {start} [, {count}]]) *matchstr()* - Same as |match()|, but return the matched string. Example: > - :echo matchstr("testing", "ing") -< results in "ing". - When there is no match "" is returned. - The {start}, if given, has the same meaning as for |match()|. > - :echo matchstr("testing", "ing", 2) -< results in "ing". > - :echo matchstr("testing", "ing", 5) -< result is "". - When {expr} is a |List| then the matching item is returned. - The type isn't changed, it's not necessarily a String. - - Can also be used as a |method|: > - GetText()->matchstr('word') - -matchstrpos({expr}, {pat} [, {start} [, {count}]]) *matchstrpos()* - Same as |matchstr()|, but return the matched string, the start - position and the end position of the match. Example: > - :echo matchstrpos("testing", "ing") -< results in ["ing", 4, 7]. - When there is no match ["", -1, -1] is returned. - The {start}, if given, has the same meaning as for |match()|. > - :echo matchstrpos("testing", "ing", 2) -< results in ["ing", 4, 7]. > - :echo matchstrpos("testing", "ing", 5) -< result is ["", -1, -1]. - When {expr} is a |List| then the matching item, the index - of first item where {pat} matches, the start position and the - end position of the match are returned. > - :echo matchstrpos([1, '__x'], '\a') -< result is ["x", 1, 2, 3]. - The type isn't changed, it's not necessarily a String. - - Can also be used as a |method|: > - GetText()->matchstrpos('word') -< - *max()* -max({expr}) Return the maximum value of all items in {expr}. - {expr} can be a |List| or a |Dictionary|. For a Dictionary, - it returns the maximum of all values in the Dictionary. - If {expr} is neither a List nor a Dictionary, or one of the - items in {expr} cannot be used as a Number this results in - an error. An empty |List| or |Dictionary| results in zero. - - Can also be used as a |method|: > - mylist->max() - -menu_get({path} [, {modes}]) *menu_get()* - Returns a |List| of |Dictionaries| describing |menus| (defined - by |:menu|, |:amenu|, …), including |hidden-menus|. - - {path} matches a menu by name, or all menus if {path} is an - empty string. Example: > - :echo menu_get('File','') - :echo menu_get('') -< - {modes} is a string of zero or more modes (see |maparg()| or - |creating-menus| for the list of modes). "a" means "all". - - Example: > - nnoremenu &Test.Test inormal - inoremenu Test.Test insert - vnoremenu Test.Test x - echo menu_get("") - -< returns something like this: > - - [ { - "hidden": 0, - "name": "Test", - "priority": 500, - "shortcut": 84, - "submenus": [ { - "hidden": 0, - "mappings": { - i": { - "enabled": 1, - "noremap": 1, - "rhs": "insert", - "sid": 1, - "silent": 0 - }, - n": { ... }, - s": { ... }, - v": { ... } - }, - "name": "Test", - "priority": 500, - "shortcut": 0 - } ] - } ] -< - - *min()* -min({expr}) Return the minimum value of all items in {expr}. - {expr} can be a |List| or a |Dictionary|. For a Dictionary, - it returns the minimum of all values in the Dictionary. - If {expr} is neither a List nor a Dictionary, or one of the - items in {expr} cannot be used as a Number this results in - an error. An empty |List| or |Dictionary| results in zero. - - Can also be used as a |method|: > - mylist->min() - -< *mkdir()* *E739* -mkdir({name} [, {path} [, {prot}]]) - Create directory {name}. - If {path} is "p" then intermediate directories are created as - necessary. Otherwise it must be "". - If {prot} is given it is used to set the protection bits of - the new directory. The default is 0o755 (rwxr-xr-x: r/w for - the user, readable for others). Use 0o700 to make it - unreadable for others. - - {prot} is applied for all parts of {name}. Thus if you create - /tmp/foo/bar then /tmp/foo will be created with 0700. Example: > - :call mkdir($HOME . "/tmp/foo/bar", "p", 0700) -< This function is not available in the |sandbox|. - - If you try to create an existing directory with {path} set to - "p" mkdir() will silently exit. - - The function result is a Number, which is TRUE if the call was - successful or FALSE if the directory creation failed or partly - failed. - - Can also be used as a |method|: > - GetName()->mkdir() -< - *mode()* -mode([expr]) Return a string that indicates the current mode. - If [expr] is supplied and it evaluates to a non-zero Number or - a non-empty String (|non-zero-arg|), then the full mode is - returned, otherwise only the first letter is returned. - - n Normal - no Operator-pending - nov Operator-pending (forced charwise |o_v|) - noV Operator-pending (forced linewise |o_V|) - noCTRL-V Operator-pending (forced blockwise |o_CTRL-V|) - CTRL-V is one character - niI Normal using |i_CTRL-O| in |Insert-mode| - niR Normal using |i_CTRL-O| in |Replace-mode| - niV Normal using |i_CTRL-O| in |Virtual-Replace-mode| - nt Normal in |terminal-emulator| (insert goes to - Terminal mode) - v Visual by character - vs Visual by character using |v_CTRL-O| in Select mode - V Visual by line - Vs Visual by line using |v_CTRL-O| in Select mode - CTRL-V Visual blockwise - CTRL-Vs Visual blockwise using |v_CTRL-O| in Select mode - s Select by character - S Select by line - CTRL-S Select blockwise - i Insert - ic Insert mode completion |compl-generic| - ix Insert mode |i_CTRL-X| completion - R Replace |R| - Rc Replace mode completion |compl-generic| - Rx Replace mode |i_CTRL-X| completion - Rv Virtual Replace |gR| - Rvc Virtual Replace mode completion |compl-generic| - Rvx Virtual Replace mode |i_CTRL-X| completion - c Command-line editing - cv Vim Ex mode |gQ| - r Hit-enter prompt - rm The -- more -- prompt - r? A |:confirm| query of some sort - ! Shell or external command is executing - t Terminal mode: keys go to the job - - This is useful in the 'statusline' option or when used - with |remote_expr()| In most other places it always returns - "c" or "n". - Note that in the future more modes and more specific modes may - be added. It's better not to compare the whole string but only - the leading character(s). - Also see |visualmode()|. - - Can also be used as a |method|: > - DoFull()->mode() - -msgpackdump({list} [, {type}]) *msgpackdump()* - Convert a list of VimL objects to msgpack. Returned value is a - |readfile()|-style list. When {type} contains "B", a |Blob| is - returned instead. Example: > - call writefile(msgpackdump([{}]), 'fname.mpack', 'b') -< or, using a |Blob|: > - call writefile(msgpackdump([{}], 'B'), 'fname.mpack') -< - This will write the single 0x80 byte to a `fname.mpack` file - (dictionary with zero items is represented by 0x80 byte in - messagepack). - - Limitations: *E5004* *E5005* - 1. |Funcref|s cannot be dumped. - 2. Containers that reference themselves cannot be dumped. - 3. Dictionary keys are always dumped as STR strings. - 4. Other strings and |Blob|s are always dumped as BIN strings. - 5. Points 3. and 4. do not apply to |msgpack-special-dict|s. - -msgpackparse({data}) *msgpackparse()* - Convert a |readfile()|-style list or a |Blob| to a list of - VimL objects. - Example: > - let fname = expand('~/.config/nvim/shada/main.shada') - let mpack = readfile(fname, 'b') - let shada_objects = msgpackparse(mpack) -< This will read ~/.config/nvim/shada/main.shada file to - `shada_objects` list. - - Limitations: - 1. Mapping ordering is not preserved unless messagepack - mapping is dumped using generic mapping - (|msgpack-special-map|). - 2. Since the parser aims to preserve all data untouched - (except for 1.) some strings are parsed to - |msgpack-special-dict| format which is not convenient to - use. - *msgpack-special-dict* - Some messagepack strings may be parsed to special - dictionaries. Special dictionaries are dictionaries which - - 1. Contain exactly two keys: `_TYPE` and `_VAL`. - 2. `_TYPE` key is one of the types found in |v:msgpack_types| - variable. - 3. Value for `_VAL` has the following format (Key column - contains name of the key from |v:msgpack_types|): - - Key Value ~ - nil Zero, ignored when dumping. Not returned by - |msgpackparse()| since |v:null| was introduced. - boolean One or zero. When dumping it is only checked that - value is a |Number|. Not returned by |msgpackparse()| - since |v:true| and |v:false| were introduced. - integer |List| with four numbers: sign (-1 or 1), highest two - bits, number with bits from 62nd to 31st, lowest 31 - bits. I.e. to get actual number one will need to use - code like > - _VAL[0] * ((_VAL[1] << 62) - & (_VAL[2] << 31) - & _VAL[3]) -< Special dictionary with this type will appear in - |msgpackparse()| output under one of the following - circumstances: - 1. |Number| is 32-bit and value is either above - INT32_MAX or below INT32_MIN. - 2. |Number| is 64-bit and value is above INT64_MAX. It - cannot possibly be below INT64_MIN because msgpack - C parser does not support such values. - float |Float|. This value cannot possibly appear in - |msgpackparse()| output. - string |readfile()|-style list of strings. This value will - appear in |msgpackparse()| output if string contains - zero byte or if string is a mapping key and mapping is - being represented as special dictionary for other - reasons. - binary |String|, or |Blob| if binary string contains zero - byte. This value cannot appear in |msgpackparse()| - output since blobs were introduced. - array |List|. This value cannot appear in |msgpackparse()| - output. - *msgpack-special-map* - map |List| of |List|s with two items (key and value) each. - This value will appear in |msgpackparse()| output if - parsed mapping contains one of the following keys: - 1. Any key that is not a string (including keys which - are binary strings). - 2. String with NUL byte inside. - 3. Duplicate key. - 4. Empty key. - ext |List| with two values: first is a signed integer - representing extension type. Second is - |readfile()|-style list of strings. - -nextnonblank({lnum}) *nextnonblank()* - Return the line number of the first line at or below {lnum} - that is not blank. Example: > - if getline(nextnonblank(1)) =~ "Java" -< When {lnum} is invalid or there is no non-blank line at or - below it, zero is returned. - {lnum} is used like with |getline()|. - See also |prevnonblank()|. - - Can also be used as a |method|: > - GetLnum()->nextnonblank() - -nr2char({expr} [, {utf8}]) *nr2char()* - Return a string with a single character, which has the number - value {expr}. Examples: > - nr2char(64) returns "@" - nr2char(32) returns " " -< Example for "utf-8": > - nr2char(300) returns I with bow character -< UTF-8 encoding is always used, {utf8} option has no effect, - and exists only for backwards-compatibility. - Note that a NUL character in the file is specified with - nr2char(10), because NULs are represented with newline - characters. nr2char(0) is a real NUL and terminates the - string, thus results in an empty string. - - Can also be used as a |method|: > - GetNumber()->nr2char() - -nvim_...({...}) *E5555* *nvim_...()* *eval-api* - Call nvim |api| functions. The type checking of arguments will - be stricter than for most other builtins. For instance, - if Integer is expected, a |Number| must be passed in, a - |String| will not be autoconverted. - Buffer numbers, as returned by |bufnr()| could be used as - first argument to nvim_buf_... functions. All functions - expecting an object (buffer, window or tabpage) can - also take the numerical value 0 to indicate the current - (focused) object. - -or({expr}, {expr}) *or()* - Bitwise OR on the two arguments. The arguments are converted - to a number. A List, Dict or Float argument causes an error. - Example: > - :let bits = or(bits, 0x80) -< Can also be used as a |method|: > - :let bits = bits->or(0x80) - -pathshorten({path}) *pathshorten()* - Shorten directory names in the path {path} and return the - result. The tail, the file name, is kept as-is. The other - components in the path are reduced to single letters. Leading - '~' and '.' characters are kept. Example: > - :echo pathshorten('~/.config/nvim/autoload/file1.vim') -< ~/.c/n/a/file1.vim ~ - It doesn't matter if the path exists or not. - - Can also be used as a |method|: > - GetDirectories()->pathshorten() - -perleval({expr}) *perleval()* - Evaluate |perl| expression {expr} and return its result - converted to Vim data structures. - Numbers and strings are returned as they are (strings are - copied though). - Lists are represented as Vim |List| type. - Dictionaries are represented as Vim |Dictionary| type, - non-string keys result in error. - - Note: If you want an array or hash, {expr} must return a - reference to it. - Example: > - :echo perleval('[1 .. 4]') -< [1, 2, 3, 4] - - Can also be used as a |method|: > - GetExpr()->perleval() - -pow({x}, {y}) *pow()* - Return the power of {x} to the exponent {y} as a |Float|. - {x} and {y} must evaluate to a |Float| or a |Number|. - Examples: > - :echo pow(3, 3) -< 27.0 > - :echo pow(2, 16) -< 65536.0 > - :echo pow(32, 0.20) -< 2.0 - - Can also be used as a |method|: > - Compute()->pow(3) - -prevnonblank({lnum}) *prevnonblank()* - Return the line number of the first line at or above {lnum} - that is not blank. Example: > - let ind = indent(prevnonblank(v:lnum - 1)) -< When {lnum} is invalid or there is no non-blank line at or - above it, zero is returned. - {lnum} is used like with |getline()|. - Also see |nextnonblank()|. - - Can also be used as a |method|: > - GetLnum()->prevnonblank() - -printf({fmt}, {expr1} ...) *printf()* - Return a String with {fmt}, where "%" items are replaced by - the formatted form of their respective arguments. Example: > - printf("%4d: E%d %.30s", lnum, errno, msg) -< May result in: - " 99: E42 asdfasdfasdfasdfasdfasdfasdfas" ~ - - When used as a |method| the base is passed as the second - argument: > - Compute()->printf("result: %d") - -< Often used items are: - %s string - %6S string right-aligned in 6 display cells - %6s string right-aligned in 6 bytes - %.9s string truncated to 9 bytes - %c single byte - %d decimal number - %5d decimal number padded with spaces to 5 characters - %b binary number - %08b binary number padded with zeros to at least 8 characters - %B binary number using upper case letters - %x hex number - %04x hex number padded with zeros to at least 4 characters - %X hex number using upper case letters - %o octal number - %f floating point number as 12.23, inf, -inf or nan - %F floating point number as 12.23, INF, -INF or NAN - %e floating point number as 1.23e3, inf, -inf or nan - %E floating point number as 1.23E3, INF, -INF or NAN - %g floating point number, as %f or %e depending on value - %G floating point number, as %F or %E depending on value - %% the % character itself - %p representation of the pointer to the container - - Conversion specifications start with '%' and end with the - conversion type. All other characters are copied unchanged to - the result. - - The "%" starts a conversion specification. The following - arguments appear in sequence: - - % [flags] [field-width] [.precision] type - - flags - Zero or more of the following flags: - - # The value should be converted to an "alternate - form". For c, d, and s conversions, this option - has no effect. For o conversions, the precision - of the number is increased to force the first - character of the output string to a zero (except - if a zero value is printed with an explicit - precision of zero). - For x and X conversions, a non-zero result has - the string "0x" (or "0X" for X conversions) - prepended to it. - - 0 (zero) Zero padding. For all conversions the converted - value is padded on the left with zeros rather - than blanks. If a precision is given with a - numeric conversion (d, o, x, and X), the 0 flag - is ignored. - - - A negative field width flag; the converted value - is to be left adjusted on the field boundary. - The converted value is padded on the right with - blanks, rather than on the left with blanks or - zeros. A - overrides a 0 if both are given. - - ' ' (space) A blank should be left before a positive - number produced by a signed conversion (d). - - + A sign must always be placed before a number - produced by a signed conversion. A + overrides - a space if both are used. - - field-width - An optional decimal digit string specifying a minimum - field width. If the converted value has fewer bytes - than the field width, it will be padded with spaces on - the left (or right, if the left-adjustment flag has - been given) to fill out the field width. For the S - conversion the count is in cells. - - .precision - An optional precision, in the form of a period '.' - followed by an optional digit string. If the digit - string is omitted, the precision is taken as zero. - This gives the minimum number of digits to appear for - d, o, x, and X conversions, the maximum number of - bytes to be printed from a string for s conversions, - or the maximum number of cells to be printed from a - string for S conversions. - For floating point it is the number of digits after - the decimal point. - - type - A character that specifies the type of conversion to - be applied, see below. - - A field width or precision, or both, may be indicated by an - asterisk '*' instead of a digit string. In this case, a - Number argument supplies the field width or precision. A - negative field width is treated as a left adjustment flag - followed by a positive field width; a negative precision is - treated as though it were missing. Example: > - :echo printf("%d: %.*s", nr, width, line) -< This limits the length of the text used from "line" to - "width" bytes. - - The conversion specifiers and their meanings are: - - *printf-d* *printf-b* *printf-B* *printf-o* *printf-x* *printf-X* - dbBoxX The Number argument is converted to signed decimal (d), - unsigned binary (b and B), unsigned octal (o), or - unsigned hexadecimal (x and X) notation. The letters - "abcdef" are used for x conversions; the letters - "ABCDEF" are used for X conversions. The precision, if - any, gives the minimum number of digits that must - appear; if the converted value requires fewer digits, it - is padded on the left with zeros. In no case does a - non-existent or small field width cause truncation of a - numeric field; if the result of a conversion is wider - than the field width, the field is expanded to contain - the conversion result. - The 'h' modifier indicates the argument is 16 bits. - The 'l' modifier indicates the argument is 32 bits. - The 'L' modifier indicates the argument is 64 bits. - Generally, these modifiers are not useful. They are - ignored when type is known from the argument. - - i alias for d - D alias for ld - U alias for lu - O alias for lo - - *printf-c* - c The Number argument is converted to a byte, and the - resulting character is written. - - *printf-s* - s The text of the String argument is used. If a - precision is specified, no more bytes than the number - specified are used. - If the argument is not a String type, it is - automatically converted to text with the same format - as ":echo". - *printf-S* - S The text of the String argument is used. If a - precision is specified, no more display cells than the - number specified are used. - - *printf-f* *E807* - f F The Float argument is converted into a string of the - form 123.456. The precision specifies the number of - digits after the decimal point. When the precision is - zero the decimal point is omitted. When the precision - is not specified 6 is used. A really big number - (out of range or dividing by zero) results in "inf" - or "-inf" with %f (INF or -INF with %F). - "0.0 / 0.0" results in "nan" with %f (NAN with %F). - Example: > - echo printf("%.2f", 12.115) -< 12.12 - Note that roundoff depends on the system libraries. - Use |round()| when in doubt. - - *printf-e* *printf-E* - e E The Float argument is converted into a string of the - form 1.234e+03 or 1.234E+03 when using 'E'. The - precision specifies the number of digits after the - decimal point, like with 'f'. - - *printf-g* *printf-G* - g G The Float argument is converted like with 'f' if the - value is between 0.001 (inclusive) and 10000000.0 - (exclusive). Otherwise 'e' is used for 'g' and 'E' - for 'G'. When no precision is specified superfluous - zeroes and '+' signs are removed, except for the zero - immediately after the decimal point. Thus 10000000.0 - results in 1.0e7. - - *printf-%* - % A '%' is written. No argument is converted. The - complete conversion specification is "%%". - - When a Number argument is expected a String argument is also - accepted and automatically converted. - When a Float or String argument is expected a Number argument - is also accepted and automatically converted. - Any other argument type results in an error message. - - *E766* *E767* - The number of {exprN} arguments must exactly match the number - of "%" items. If there are not sufficient or too many - arguments an error is given. Up to 18 arguments can be used. - -prompt_getprompt({buf}) *prompt_getprompt()* - Returns the effective prompt text for buffer {buf}. {buf} can - be a buffer name or number. See |prompt-buffer|. - - If the buffer doesn't exist or isn't a prompt buffer, an empty - string is returned. - - Can also be used as a |method|: > - GetBuffer()->prompt_getprompt() - -prompt_setcallback({buf}, {expr}) *prompt_setcallback()* - Set prompt callback for buffer {buf} to {expr}. When {expr} - is an empty string the callback is removed. This has only - effect if {buf} has 'buftype' set to "prompt". - - The callback is invoked when pressing Enter. The current - buffer will always be the prompt buffer. A new line for a - prompt is added before invoking the callback, thus the prompt - for which the callback was invoked will be in the last but one - line. - If the callback wants to add text to the buffer, it must - insert it above the last line, since that is where the current - prompt is. This can also be done asynchronously. - The callback is invoked with one argument, which is the text - that was entered at the prompt. This can be an empty string - if the user only typed Enter. - Example: > - call prompt_setcallback(bufnr(''), function('s:TextEntered')) - func s:TextEntered(text) - if a:text == 'exit' || a:text == 'quit' - stopinsert - close - else - call append(line('$') - 1, 'Entered: "' . a:text . '"') - " Reset 'modified' to allow the buffer to be closed. - set nomodified - endif - endfunc - -< Can also be used as a |method|: > - GetBuffer()->prompt_setcallback(callback) - -prompt_setinterrupt({buf}, {expr}) *prompt_setinterrupt()* - Set a callback for buffer {buf} to {expr}. When {expr} is an - empty string the callback is removed. This has only effect if - {buf} has 'buftype' set to "prompt". - - This callback will be invoked when pressing CTRL-C in Insert - mode. Without setting a callback Vim will exit Insert mode, - as in any buffer. - - Can also be used as a |method|: > - GetBuffer()->prompt_setinterrupt(callback) - -prompt_setprompt({buf}, {text}) *prompt_setprompt()* - Set prompt for buffer {buf} to {text}. You most likely want - {text} to end in a space. - The result is only visible if {buf} has 'buftype' set to - "prompt". Example: > - call prompt_setprompt(bufnr(''), 'command: ') -< - Can also be used as a |method|: > - GetBuffer()->prompt_setprompt('command: ') - -pum_getpos() *pum_getpos()* - If the popup menu (see |ins-completion-menu|) is not visible, - returns an empty |Dictionary|, otherwise, returns a - |Dictionary| with the following keys: - height nr of items visible - width screen cells - row top screen row (0 first row) - col leftmost screen column (0 first col) - size total nr of items - scrollbar |TRUE| if scrollbar is visible - - The values are the same as in |v:event| during |CompleteChanged|. - -pumvisible() *pumvisible()* - Returns non-zero when the popup menu is visible, zero - otherwise. See |ins-completion-menu|. - This can be used to avoid some things that would remove the - popup menu. - -py3eval({expr}) *py3eval()* - Evaluate Python expression {expr} and return its result - converted to Vim data structures. - Numbers and strings are returned as they are (strings are - copied though, Unicode strings are additionally converted to - UTF-8). - Lists are represented as Vim |List| type. - Dictionaries are represented as Vim |Dictionary| type with - keys converted to strings. - - Can also be used as a |method|: > - GetExpr()->py3eval() -< - *E858* *E859* -pyeval({expr}) *pyeval()* - Evaluate Python expression {expr} and return its result - converted to Vim data structures. - Numbers and strings are returned as they are (strings are - copied though). - Lists are represented as Vim |List| type. - Dictionaries are represented as Vim |Dictionary| type, - non-string keys result in error. - - Can also be used as a |method|: > - GetExpr()->pyeval() - -pyxeval({expr}) *pyxeval()* - Evaluate Python expression {expr} and return its result - converted to Vim data structures. - Uses Python 2 or 3, see |python_x| and 'pyxversion'. - See also: |pyeval()|, |py3eval()| - - Can also be used as a |method|: > - GetExpr()->pyxeval() -< - *E726* *E727* -range({expr} [, {max} [, {stride}]]) *range()* - Returns a |List| with Numbers: - - If only {expr} is specified: [0, 1, ..., {expr} - 1] - - If {max} is specified: [{expr}, {expr} + 1, ..., {max}] - - If {stride} is specified: [{expr}, {expr} + {stride}, ..., - {max}] (increasing {expr} with {stride} each time, not - producing a value past {max}). - When the maximum is one before the start the result is an - empty list. When the maximum is more than one before the - start this is an error. - Examples: > - range(4) " [0, 1, 2, 3] - range(2, 4) " [2, 3, 4] - range(2, 9, 3) " [2, 5, 8] - range(2, -2, -1) " [2, 1, 0, -1, -2] - range(0) " [] - range(2, 0) " error! -< - Can also be used as a |method|: > - GetExpr()->range() -< - *readdir()* -readdir({directory} [, {expr}]) - Return a list with file and directory names in {directory}. - - When {expr} is omitted all entries are included. - When {expr} is given, it is evaluated to check what to do: - If {expr} results in -1 then no further entries will - be handled. - If {expr} results in 0 then this entry will not be - added to the list. - If {expr} results in 1 then this entry will be added - to the list. - Each time {expr} is evaluated |v:val| is set to the entry name. - When {expr} is a function the name is passed as the argument. - For example, to get a list of files ending in ".txt": > - readdir(dirname, {n -> n =~ '.txt$'}) -< To skip hidden and backup files: > - readdir(dirname, {n -> n !~ '^\.\|\~$'}) - -< If you want to get a directory tree: > - function! s:tree(dir) - return {a:dir : map(readdir(a:dir), - \ {_, x -> isdirectory(x) ? - \ {x : s:tree(a:dir . '/' . x)} : x})} - endfunction - echo s:tree(".") -< - Can also be used as a |method|: > - GetDirName()->readdir() -< - *readfile()* -readfile({fname} [, {type} [, {max}]]) - Read file {fname} and return a |List|, each line of the file - as an item. Lines are broken at NL characters. Macintosh - files separated with CR will result in a single long line - (unless a NL appears somewhere). - All NUL characters are replaced with a NL character. - When {type} contains "b" binary mode is used: - - When the last line ends in a NL an extra empty list item is - added. - - No CR characters are removed. - When {type} contains "B" a |Blob| is returned with the binary - data of the file unmodified. - Otherwise: - - CR characters that appear before a NL are removed. - - Whether the last line ends in a NL or not does not matter. - - Any UTF-8 byte order mark is removed from the text. - When {max} is given this specifies the maximum number of lines - to be read. Useful if you only want to check the first ten - lines of a file: > - :for line in readfile(fname, '', 10) - : if line =~ 'Date' | echo line | endif - :endfor -< When {max} is negative -{max} lines from the end of the file - are returned, or as many as there are. - When {max} is zero the result is an empty list. - Note that without {max} the whole file is read into memory. - Also note that there is no recognition of encoding. Read a - file into a buffer if you need to. - When the file can't be opened an error message is given and - the result is an empty list. - Also see |writefile()|. - - Can also be used as a |method|: > - GetFileName()->readfile() - -reg_executing() *reg_executing()* - Returns the single letter name of the register being executed. - Returns an empty string when no register is being executed. - See |@|. - -reg_recorded() *reg_recorded()* - Returns the single letter name of the last recorded register. - Returns an empty string string when nothing was recorded yet. - See |q| and |Q|. - -reg_recording() *reg_recording()* - Returns the single letter name of the register being recorded. - Returns an empty string string when not recording. See |q|. - -reltime([{start} [, {end}]]) *reltime()* - Return an item that represents a time value. The item is a - list with items that depend on the system. - The item can be passed to |reltimestr()| to convert it to a - string or |reltimefloat()| to convert to a Float. - - Without an argument it returns the current "relative time", an - implementation-defined value meaningful only when used as an - argument to |reltime()|, |reltimestr()| and |reltimefloat()|. - - With one argument it returns the time passed since the time - specified in the argument. - With two arguments it returns the time passed between {start} - and {end}. - - The {start} and {end} arguments must be values returned by - reltime(). - - Can also be used as a |method|: > - GetStart()->reltime() -< - Note: |localtime()| returns the current (non-relative) time. - -reltimefloat({time}) *reltimefloat()* - Return a Float that represents the time value of {time}. - Unit of time is seconds. - Example: - let start = reltime() - call MyFunction() - let seconds = reltimefloat(reltime(start)) - See the note of reltimestr() about overhead. - Also see |profiling|. - If there is an error an empty string is returned - - Can also be used as a |method|: > - reltime(start)->reltimefloat() - -reltimestr({time}) *reltimestr()* - Return a String that represents the time value of {time}. - This is the number of seconds, a dot and the number of - microseconds. Example: > - let start = reltime() - call MyFunction() - echo reltimestr(reltime(start)) -< Note that overhead for the commands will be added to the time. - Leading spaces are used to make the string align nicely. You - can use split() to remove it. > - echo split(reltimestr(reltime(start)))[0] -< Also see |profiling|. - If there is an error an empty string is returned - - Can also be used as a |method|: > - reltime(start)->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"). - 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 - and the result will be the empty string. - - Variables will be evaluated in the global namespace, - independent of a function currently being active. Except - when in debug mode, then local function variables and - arguments can be evaluated. - - Examples: > - :echo remote_expr("gvim", "2+2") - :echo remote_expr("gvim1", "b:current_syntax") -< - -remote_foreground({server}) *remote_foreground()* - Move the Vim server with the name {server} to the foreground. - The {server} argument is a string. - This works like: > - remote_expr({server}, "foreground()") -< Except that on Win32 systems the client does the work, to work - around the problem that the OS doesn't always allow the server - to bring itself to the foreground. - Note: This does not restore the window if it was minimized, - like foreground() does. - This function is not available in the |sandbox|. - {only in the Win32 GUI and the Win32 console version} - - -remote_peek({serverid} [, {retvar}]) *remote_peek()* - Returns a positive number if there are available strings - from {serverid}. Copies any reply string into the variable - {retvar} if specified. {retvar} must be a string with the - name of a variable. - Returns zero if none are available. - Returns -1 if something is wrong. - See also |clientserver|. - This function is not available in the |sandbox|. - Examples: > - :let repl = "" - :echo "PEEK: ".remote_peek(id, "repl").": ".repl - -remote_read({serverid}, [{timeout}]) *remote_read()* - Return the oldest available reply from {serverid} and consume - it. Unless a {timeout} in seconds is given, it blocks until a - reply is available. - See also |clientserver|. - This function is not available in the |sandbox|. - Example: > - :echo remote_read(id) -< - *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|. - 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|. - - Note: Any errors will be reported in the server and may mess - up the display. - Examples: > - :echo remote_send("gvim", ":DropAndReply ".file, "serverid"). - \ remote_read(serverid) - - :autocmd NONE RemoteReply * - \ echo remote_read(expand("<amatch>")) - :echo remote_send("gvim", ":sleep 10 | echo ". - \ 'server2client(expand("<client>"), "HELLO")<CR>') -< - *remote_startserver()* *E941* *E942* -remote_startserver({name}) - Become the server {name}. This fails if already running as a - server, when |v:servername| is not empty. - -remove({list}, {idx} [, {end}]) *remove()* - Without {end}: Remove the item at {idx} from |List| {list} and - return the item. - With {end}: Remove items from {idx} to {end} (inclusive) and - return a |List| with these items. When {idx} points to the same - item as {end} a list with one item is returned. When {end} - points to an item before {idx} this is an error. - See |list-index| for possible values of {idx} and {end}. - Example: > - :echo "last item: " . remove(mylist, -1) - :call remove(mylist, 0, 9) - -< Can also be used as a |method|: > - mylist->remove(idx) - -remove({blob}, {idx} [, {end}]) - Without {end}: Remove the byte at {idx} from |Blob| {blob} and - return the byte. - With {end}: Remove bytes from {idx} to {end} (inclusive) and - return a |Blob| with these bytes. When {idx} points to the same - byte as {end} a |Blob| with one byte is returned. When {end} - points to a byte before {idx} this is an error. - Example: > - :echo "last byte: " . remove(myblob, -1) - :call remove(mylist, 0, 9) - -remove({dict}, {key}) - Remove the entry from {dict} with key {key} and return it. - Example: > - :echo "removed " . remove(dict, "one") -< If there is no {key} in {dict} this is an error. - - Use |delete()| to remove a file. - -rename({from}, {to}) *rename()* - Rename the file by the name {from} to the name {to}. This - should also work to move files across file systems. The - result is a Number, which is 0 if the file was renamed - successfully, and non-zero when the renaming failed. - NOTE: If {to} exists it is overwritten without warning. - This function is not available in the |sandbox|. - - Can also be used as a |method|: > - GetOldName()->rename(newname) - -repeat({expr}, {count}) *repeat()* - Repeat {expr} {count} times and return the concatenated - result. Example: > - :let separator = repeat('-', 80) -< When {count} is zero or negative the result is empty. - When {expr} is a |List| the result is {expr} concatenated - {count} times. Example: > - :let longlist = repeat(['a', 'b'], 3) -< Results in ['a', 'b', 'a', 'b', 'a', 'b']. - - Can also be used as a |method|: > - mylist->repeat(count) - -resolve({filename}) *resolve()* *E655* - On MS-Windows, when {filename} is a shortcut (a .lnk file), - returns the path the shortcut points to in a simplified form. - On Unix, repeat resolving symbolic links in all path - components of {filename} and return the simplified result. - To cope with link cycles, resolving of symbolic links is - stopped after 100 iterations. - On other systems, return the simplified {filename}. - The simplification step is done as by |simplify()|. - resolve() keeps a leading path component specifying the - current directory (provided the result is still a relative - path name) and also keeps a trailing path separator. - - Can also be used as a |method|: > - GetName()->resolve() -< - *reverse()* -reverse({object}) - Reverse the order of items in {object} in-place. - {object} can be a |List| or a |Blob|. - Returns {object}. - If you want an object to remain unmodified make a copy first: > - :let revlist = reverse(copy(mylist)) -< Can also be used as a |method|: > - mylist->reverse() - -round({expr}) *round()* - Round off {expr} to the nearest integral value and return it - as a |Float|. If {expr} lies halfway between two integral - values, then use the larger one (away from zero). - {expr} must evaluate to a |Float| or a |Number|. - Examples: > - echo round(0.456) -< 0.0 > - echo round(4.5) -< 5.0 > - echo round(-4.5) -< -5.0 - - Can also be used as a |method|: > - Compute()->round() - -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()* - 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()* - Deprecated. Replace > - :let id = rpcstart('prog', ['arg1', 'arg2']) -< with > - :let id = jobstart(['prog', 'arg1', 'arg2'], {'rpc': v:true}) - -rubyeval({expr}) *rubyeval()* - Evaluate Ruby expression {expr} and return its result - converted to Vim data structures. - Numbers, floats and strings are returned as they are (strings - are copied though). - Arrays are represented as Vim |List| type. - Hashes are represented as Vim |Dictionary| type. - Other objects are represented as strings resulted from their - "Object#to_s" method. - - Can also be used as a |method|: > - GetRubyExpr()->rubyeval() - -screenattr({row}, {col}) *screenattr()* - Like |screenchar()|, but return the attribute. This is a rather - arbitrary number that can only be used to compare to the - attribute at other positions. - - Can also be used as a |method|: > - GetRow()->screenattr(col) - -screenchar({row}, {col}) *screenchar()* - The result is a Number, which is the character at position - [row, col] on the screen. This works for every possible - screen position, also status lines, window separators and the - command line. The top left position is row one, column one - The character excludes composing characters. For double-byte - encodings it may only be the first byte. - This is mainly to be used for testing. - Returns -1 when row or col is out of range. - - Can also be used as a |method|: > - GetRow()->screenchar(col) - -screenchars({row}, {col}) *screenchars()* - The result is a List of Numbers. The first number is the same - as what |screenchar()| returns. Further numbers are - composing characters on top of the base character. - This is mainly to be used for testing. - Returns an empty List when row or col is out of range. - - Can also be used as a |method|: > - GetRow()->screenchars(col) - -screencol() *screencol()* - The result is a Number, which is the current screen column of - the cursor. The leftmost column has number 1. - This function is mainly used for testing. - - Note: Always returns the current screen column, thus if used - in a command (e.g. ":echo screencol()") it will return the - column inside the command line, which is 1 when the command is - executed. To get the cursor position in the file use one of - the following mappings: > - nnoremap <expr> GG ":echom ".screencol()."\n" - nnoremap <silent> GG :echom screencol()<CR> - noremap GG <Cmd>echom screencol()<Cr> -< -screenpos({winid}, {lnum}, {col}) *screenpos()* - The result is a Dict with the screen position of the text - character in window {winid} at buffer line {lnum} and column - {col}. {col} is a one-based byte index. - The Dict has these members: - row screen row - col first screen column - endcol last screen column - curscol cursor screen column - If the specified position is not visible, all values are zero. - The "endcol" value differs from "col" when the character - occupies more than one screen cell. E.g. for a Tab "col" can - be 1 and "endcol" can be 8. - The "curscol" value is where the cursor would be placed. For - a Tab it would be the same as "endcol", while for a double - width character it would be the same as "col". - The |conceal| feature is ignored here, the column numbers are - as if 'conceallevel' is zero. You can set the cursor to the - right position and use |screencol()| to get the value with - |conceal| taken into account. - - Can also be used as a |method|: > - GetWinid()->screenpos(lnum, col) - -screenrow() *screenrow()* - The result is a Number, which is the current screen row of the - cursor. The top line has number one. - This function is mainly used for testing. - Alternatively you can use |winline()|. - - Note: Same restrictions as with |screencol()|. - -screenstring({row}, {col}) *screenstring()* - The result is a String that contains the base character and - any composing characters at position [row, col] on the screen. - This is like |screenchars()| but returning a String with the - characters. - This is mainly to be used for testing. - Returns an empty String when row or col is out of range. - - Can also be used as a |method|: > - GetRow()->screenstring(col) - -search({pattern} [, {flags} [, {stopline} [, {timeout}]]]) *search()* - Search for regexp pattern {pattern}. The search starts at the - cursor position (you can use |cursor()| to set it). - - When a match has been found its line number is returned. - If there is no match a 0 is returned and the cursor doesn't - move. No error message is given. - - {flags} is a String, which can contain these character flags: - 'b' search Backward instead of forward - 'c' accept a match at the Cursor position - 'e' move to the End of the match - 'n' do Not move the cursor - 'p' return number of matching sub-Pattern (see below) - 's' Set the ' mark at the previous location of the cursor - 'w' Wrap around the end of the file - 'W' don't Wrap around the end of the file - 'z' start searching at the cursor column instead of Zero - If neither 'w' or 'W' is given, the 'wrapscan' option applies. - - If the 's' flag is supplied, the ' mark is set, only if the - cursor is moved. The 's' flag cannot be combined with the 'n' - flag. - - 'ignorecase', 'smartcase' and 'magic' are used. - - When the 'z' flag is not given, forward searching always - starts in column zero and then matches before the cursor are - skipped. When the 'c' flag is present in 'cpo' the next - search starts after the match. Without the 'c' flag the next - search starts one column further. This matters for - overlapping matches. - When searching backwards and the 'z' flag is given then the - search starts in column zero, thus no match in the current - line will be found (unless wrapping around the end of the - file). - - When the {stopline} argument is given then the search stops - after searching this line. This is useful to restrict the - search to a range of lines. Examples: > - let match = search('(', 'b', line("w0")) - let end = search('END', '', line("w$")) -< When {stopline} is used and it is not zero this also implies - that the search does not wrap around the end of the file. - A zero value is equal to not giving the argument. - - When the {timeout} argument is given the search stops when - more than this many milliseconds have passed. Thus when - {timeout} is 500 the search stops after half a second. - The value must not be negative. A zero value is like not - giving the argument. - - *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 - whole pattern did match. - To get the column number too use |searchpos()|. - - The cursor will be positioned at the match, unless the 'n' - flag is used. - - Example (goes over all files in the argument list): > - :let n = 1 - :while n <= argc() " loop over all files in arglist - : exe "argument " . n - : " start at the last char in the file and wrap for the - : " first search to find match at start of file - : normal G$ - : let flags = "w" - : while search("foo", flags) > 0 - : s/foo/bar/g - : let flags = "W" - : endwhile - : update " write the file if modified - : let n = n + 1 - :endwhile -< - Example for using some flags: > - :echo search('\<if\|\(else\)\|\(endif\)', 'ncpe') -< This will search for the keywords "if", "else", and "endif" - under or after the cursor. Because of the 'p' flag, it - returns 1, 2, or 3 depending on which keyword is found, or 0 - if the search fails. With the cursor on the first word of the - line: - if (foo == 0) | let foo = foo + 1 | endif ~ - the function returns 1. Without the 'c' flag, the function - finds the "endif" and returns 3. The same thing happens - without the 'e' flag if the cursor is on the "f" of "if". - The 'n' flag tells the function not to move the cursor. - - Can also be used as a |method|: > - GetPattern()->search() - -searchcount([{options}]) *searchcount()* - Get or update the last search count, like what is displayed - without the "S" flag in 'shortmess'. This works even if - 'shortmess' does contain the "S" flag. - - This returns a Dictionary. The dictionary is empty if the - previous pattern was not set and "pattern" was not specified. - - key type meaning ~ - current |Number| current position of match; - 0 if the cursor position is - before the first match - exact_match |Boolean| 1 if "current" is matched on - "pos", otherwise 0 - total |Number| total count of matches found - incomplete |Number| 0: search was fully completed - 1: recomputing was timed out - 2: max count exceeded - - For {options} see further down. - - To get the last search count when |n| or |N| was pressed, call - this function with `recompute: 0` . This sometimes returns - wrong information because |n| and |N|'s maximum count is 99. - If it exceeded 99 the result must be max count + 1 (100). If - you want to get correct information, specify `recompute: 1`: > - - " result == maxcount + 1 (100) when many matches - let result = searchcount(#{recompute: 0}) - - " Below returns correct result (recompute defaults - " to 1) - let result = searchcount() -< - The function is useful to add the count to |statusline|: > - function! LastSearchCount() abort - let result = searchcount(#{recompute: 0}) - if empty(result) - return '' - endif - if result.incomplete ==# 1 " timed out - return printf(' /%s [?/??]', @/) - elseif result.incomplete ==# 2 " max count exceeded - if result.total > result.maxcount && - \ result.current > result.maxcount - return printf(' /%s [>%d/>%d]', @/, - \ result.current, result.total) - elseif result.total > result.maxcount - return printf(' /%s [%d/>%d]', @/, - \ result.current, result.total) - endif - endif - return printf(' /%s [%d/%d]', @/, - \ result.current, result.total) - endfunction - let &statusline .= '%{LastSearchCount()}' - - " Or if you want to show the count only when - " 'hlsearch' was on - " let &statusline .= - " \ '%{v:hlsearch ? LastSearchCount() : ""}' -< - You can also update the search count, which can be useful in a - |CursorMoved| or |CursorMovedI| autocommand: > - - autocmd CursorMoved,CursorMovedI * - \ let s:searchcount_timer = timer_start( - \ 200, function('s:update_searchcount')) - function! s:update_searchcount(timer) abort - if a:timer ==# s:searchcount_timer - call searchcount(#{ - \ recompute: 1, maxcount: 0, timeout: 100}) - redrawstatus - endif - endfunction -< - This can also be used to count matched texts with specified - pattern in the current buffer using "pattern": > - - " Count '\<foo\>' in this buffer - " (Note that it also updates search count) - let result = searchcount(#{pattern: '\<foo\>'}) - - " To restore old search count by old pattern, - " search again - call searchcount() -< - {options} must be a Dictionary. It can contain: - key type meaning ~ - recompute |Boolean| if |TRUE|, recompute the count - like |n| or |N| was executed. - otherwise returns the last - result by |n|, |N|, or this - function is returned. - (default: |TRUE|) - pattern |String| recompute if this was given - and different with |@/|. - this works as same as the - below command is executed - before calling this function > - let @/ = pattern -< (default: |@/|) - timeout |Number| 0 or negative number is no - timeout. timeout milliseconds - for recomputing the result - (default: 0) - maxcount |Number| 0 or negative number is no - limit. max count of matched - text while recomputing the - result. if search exceeded - total count, "total" value - becomes `maxcount + 1` - (default: 0) - pos |List| `[lnum, col, off]` value - when recomputing the result. - this changes "current" result - value. see |cursor()|, |getpos() - (default: cursor's position) - - Can also be used as a |method|: > - GetSearchOpts()->searchcount() -< -searchdecl({name} [, {global} [, {thisblock}]]) *searchdecl()* - Search for the declaration of {name}. - - With a non-zero {global} argument it works like |gD|, find - first match in the file. Otherwise it works like |gd|, find - first match in the function. - - With a non-zero {thisblock} argument matches in a {} block - that ends before the cursor position are ignored. Avoids - finding variable declarations only valid in another scope. - - Moves the cursor to the found match. - Returns zero for success, non-zero for failure. - Example: > - if searchdecl('myvar') == 0 - echo getline('.') - endif -< - Can also be used as a |method|: > - GetName()->searchdecl() -< - *searchpair()* -searchpair({start}, {middle}, {end} [, {flags} [, {skip} - [, {stopline} [, {timeout}]]]]) - Search for the match of a nested start-end pair. This can be - used to find the "endif" that matches an "if", while other - if/endif pairs in between are ignored. - The search starts at the cursor. The default is to search - forward, include 'b' in {flags} to search backward. - If a match is found, the cursor is positioned at it and the - line number is returned. If no match is found 0 or -1 is - returned and the cursor doesn't move. No error message is - given. - - {start}, {middle} and {end} are patterns, see |pattern|. They - must not contain \( \) pairs. Use of \%( \) is allowed. When - {middle} is not empty, it is found when searching from either - direction, but only when not in a nested start-end pair. A - typical use is: > - searchpair('\<if\>', '\<else\>', '\<endif\>') -< By leaving {middle} empty the "else" is skipped. - - {flags} 'b', 'c', 'n', 's', 'w' and 'W' are used like with - |search()|. Additionally: - 'r' Repeat until no more matches found; will find the - outer pair. Implies the 'W' flag. - 'm' Return number of matches instead of line number with - the match; will be > 1 when 'r' is used. - Note: it's nearly always a good idea to use the 'W' flag, to - avoid wrapping around the end of the file. - - When a match for {start}, {middle} or {end} is found, the - {skip} expression is evaluated with the cursor positioned on - the start of the match. It should return non-zero if this - match is to be skipped. E.g., because it is inside a comment - or a string. - When {skip} is omitted or empty, every match is accepted. - When evaluating {skip} causes an error the search is aborted - and -1 returned. - {skip} can be a string, a lambda, a funcref or a partial. - Anything else makes the function fail. - - For {stopline} and {timeout} see |search()|. - - The value of 'ignorecase' is used. 'magic' is ignored, the - patterns are used like it's on. - - The search starts exactly at the cursor. A match with - {start}, {middle} or {end} at the next character, in the - direction of searching, is the first one found. Example: > - if 1 - if 2 - endif 2 - endif 1 -< When starting at the "if 2", with the cursor on the "i", and - searching forwards, the "endif 2" is found. When starting on - the character just before the "if 2", the "endif 1" will be - found. That's because the "if 2" will be found first, and - then this is considered to be a nested if/endif from "if 2" to - "endif 2". - When searching backwards and {end} is more than one character, - it may be useful to put "\zs" at the end of the pattern, so - that when the cursor is inside a match with the end it finds - the matching start. - - Example, to find the "endif" command in a Vim script: > - - :echo searchpair('\<if\>', '\<el\%[seif]\>', '\<en\%[dif]\>', 'W', - \ 'getline(".") =~ "^\\s*\""') - -< The cursor must be at or after the "if" for which a match is - to be found. Note that single-quote strings are used to avoid - having to double the backslashes. The skip expression only - catches comments at the start of a line, not after a command. - Also, a word "en" or "if" halfway through a line is considered - a match. - Another example, to search for the matching "{" of a "}": > - - :echo searchpair('{', '', '}', 'bW') - -< This works when the cursor is at or before the "}" for which a - match is to be found. To reject matches that syntax - highlighting recognized as strings: > - - :echo searchpair('{', '', '}', 'bW', - \ 'synIDattr(synID(line("."), col("."), 0), "name") =~? "string"') -< - *searchpairpos()* -searchpairpos({start}, {middle}, {end} [, {flags} [, {skip} - [, {stopline} [, {timeout}]]]]) - Same as |searchpair()|, 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 - the column position of the match. If no match is found, - returns [0, 0]. > - - :let [lnum,col] = searchpairpos('{', '', '}', 'n') -< - See |match-parens| for a bigger and more useful example. - -searchpos({pattern} [, {flags} [, {stopline} [, {timeout}]]]) *searchpos()* - 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 - the column position of the match. If no match is found, - returns [0, 0]. - Example: > - :let [lnum, col] = searchpos('mypattern', 'n') - -< When the 'p' flag is given then there is an extra item with - the sub-pattern match number |search()-sub-match|. Example: > - :let [lnum, col, submatch] = searchpos('\(\l\)\|\(\u\)', 'np') -< In this example "submatch" is 2 when a lowercase letter is - found |/\l|, 3 when an uppercase letter is found |/\u|. - - Can also be used as a |method|: > - GetPattern()->searchpos() - -server2client({clientid}, {string}) *server2client()* - Send a reply string to {clientid}. The most recent {clientid} - that sent a string can be retrieved with expand("<client>"). - Note: - Returns zero for success, -1 for failure. - This id has to be stored before the next command can be - received. I.e. before returning from the received command and - before calling any commands that waits for input. - See also |clientserver|. - Example: > - :echo server2client(expand("<client>"), "HELLO") - -< Can also be used as a |method|: > - GetClientId()->server2client(string) -< -serverlist() *serverlist()* - Returns a list of server addresses, or empty if all servers - were stopped. |serverstart()| |serverstop()| - Example: > - :echo serverlist() - -serverstart([{address}]) *serverstart()* - Opens a socket or named pipe at {address} and listens for - |RPC| messages. Clients can send |API| commands to the address - to control Nvim. Returns the address string. - - If {address} does not contain a colon ":" it is interpreted as - a named pipe or Unix domain socket path. - - Example: > - if has('win32') - call serverstart('\\.\pipe\nvim-pipe-1234') - else - call serverstart('nvim.sock') - endif -< - If {address} contains a colon ":" it is interpreted as a TCP - address where the last ":" separates the host and port. - Assigns a random port if it is empty or 0. Supports IPv4/IPv6. - - Example: > - :call serverstart('::1:12345') -< - If no address is given, it is equivalent to: > - :call serverstart(tempname()) - -< |$NVIM_LISTEN_ADDRESS| is set to {address} if not already set. - -serverstop({address}) *serverstop()* - Closes the pipe or socket at {address}. - Returns TRUE if {address} is valid, else FALSE. - If |$NVIM_LISTEN_ADDRESS| is stopped it is unset. - If |v:servername| is stopped it is set to the next available - address returned by |serverlist()|. - -setbufline({buf}, {lnum}, {text}) *setbufline()* - Set line {lnum} to {text} in buffer {buf}. This works like - |setline()| for the specified buffer. - - This function works only for loaded buffers. First call - |bufload()| if needed. - - To insert lines use |appendbufline()|. - Any text properties in {lnum} are cleared. - - {text} can be a string to set one line, or a list of strings - to set multiple lines. If the list extends below the last - line then those lines are added. - - For the use of {buf}, see |bufname()| above. - - {lnum} is used like with |setline()|. - Use "$" to refer to the last line in buffer {buf}. - When {lnum} is just below the last line the {text} will be - added below the last line. - On success 0 is returned, on failure 1 is returned. - - If {buf} is not a valid buffer or {lnum} is not valid, an - error message is given. - - Can also be used as a |method|, the base is passed as the - third argument: > - GetText()->setbufline(buf, lnum) - -setbufvar({buf}, {varname}, {val}) *setbufvar()* - Set option or local variable {varname} in buffer {buf} to - {val}. - This also works for a global or local window option, but it - doesn't work for a global or local window variable. - For a local window option the global value is unchanged. - For the use of {buf}, see |bufname()| above. - The {varname} argument is a string. - Note that the variable name without "b:" must be used. - Examples: > - :call setbufvar(1, "&mod", 1) - :call setbufvar("todo", "myvar", "foobar") -< This function is not available in the |sandbox|. - - Can also be used as a |method|, the base is passed as the - third argument: > - GetValue()->setbufvar(buf, varname) - -setcharsearch({dict}) *setcharsearch()* - Set the current character search information to {dict}, - which contains one or more of the following entries: - - char character which will be used for a subsequent - |,| or |;| command; an empty string clears the - character search - forward direction of character search; 1 for forward, - 0 for backward - until type of character search; 1 for a |t| or |T| - character search, 0 for an |f| or |F| - character search - - This can be useful to save/restore a user's character search - from a script: > - :let prevsearch = getcharsearch() - :" Perform a command which clobbers user's search - :call setcharsearch(prevsearch) -< Also see |getcharsearch()|. - - Can also be used as a |method|: > - SavedSearch()->setcharsearch() - -setcmdpos({pos}) *setcmdpos()* - Set the cursor position in the command line to byte position - {pos}. The first position is 1. - Use |getcmdpos()| to obtain the current position. - Only works while editing the command line, thus you must use - |c_CTRL-\_e|, |c_CTRL-R_=| or |c_CTRL-R_CTRL-R| with '='. For - |c_CTRL-\_e| and |c_CTRL-R_CTRL-R| with '=' the position is - set after the command line is set to the expression. For - |c_CTRL-R_=| it is set after evaluating the expression but - before inserting the resulting text. - When the number is too big the cursor is put at the end of the - line. A number smaller than one has undefined results. - Returns FALSE when successful, TRUE when not editing the - command line. - - Can also be used as a |method|: > - GetPos()->setcmdpos() - -setenv({name}, {val}) *setenv()* - Set environment variable {name} to {val}. Example: > - call setenv('HOME', '/home/myhome') - -< When {val} is |v:null| the environment variable is deleted. - See also |expr-env|. - - Can also be used as a |method|, the base is passed as the - second argument: > - GetPath()->setenv('PATH') - -setfperm({fname}, {mode}) *setfperm()* *chmod* - Set the file permissions for {fname} to {mode}. - {mode} must be a string with 9 characters. It is of the form - "rwxrwxrwx", where each group of "rwx" flags represent, in - turn, the permissions of the owner of the file, the group the - file belongs to, and other users. A '-' character means the - permission is off, any other character means on. Multi-byte - characters are not supported. - - For example "rw-r-----" means read-write for the user, - readable by the group, not accessible by others. "xx-x-----" - would do the same thing. - - Returns non-zero for success, zero for failure. - - Can also be used as a |method|: > - GetFilename()->setfperm(mode) -< - To read permissions see |getfperm()|. - -setline({lnum}, {text}) *setline()* - Set line {lnum} of the current buffer to {text}. To insert - lines use |append()|. To set lines in another buffer use - |setbufline()|. - - {lnum} is used like with |getline()|. - When {lnum} is just below the last line the {text} will be - added below the last line. - - If this succeeds, FALSE is returned. If this fails (most likely - because {lnum} is invalid) TRUE is returned. - - Example: > - :call setline(5, strftime("%c")) - -< When {text} is a |List| then line {lnum} and following lines - will be set to the items in the list. Example: > - :call setline(5, ['aaa', 'bbb', 'ccc']) -< This is equivalent to: > - :for [n, l] in [[5, 'aaa'], [6, 'bbb'], [7, 'ccc']] - : call setline(n, l) - :endfor - -< Note: The '[ and '] marks are not set. - - Can also be used as a |method|, the base is passed as the - second argument: > - GetText()->setline(lnum) - -setloclist({nr}, {list} [, {action} [, {what}]]) *setloclist()* - Create or replace or add to 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. - - For a location list window, the displayed location list is - modified. For an invalid window number {nr}, -1 is returned. - Otherwise, same as |setqflist()|. - Also see |location-list|. - - For {action} see |setqflist-action|. - - If the optional {what} dictionary argument is supplied, then - only the items listed in {what} are set. Refer to |setqflist()| - for the list of supported keys in {what}. - - Can also be used as a |method|, the base is passed as the - second argument: > - GetLoclist()->setloclist(winnr) - -setmatches({list} [, {win}]) *setmatches()* - Restores a list of matches saved by |getmatches() for the - current window|. Returns 0 if successful, otherwise -1. All - current matches are cleared before the list is restored. See - example for |getmatches()|. - If {win} is specified, use the window with this number or - window ID instead of the current window. - - Can also be used as a |method|: > - GetMatches()->setmatches() -< - *setpos()* -setpos({expr}, {list}) - Set the position for String {expr}. Possible values: - . the cursor - 'x mark x - - {list} must be a |List| with four or five numbers: - [bufnum, lnum, col, off] - [bufnum, lnum, col, off, curswant] - - "bufnum" is the buffer number. Zero can be used for the - current buffer. When setting an uppercase mark "bufnum" is - used for the mark position. For other marks it specifies the - buffer to set the mark in. You can use the |bufnr()| function - to turn a file name into a buffer number. - For setting the cursor and the ' mark "bufnum" is ignored, - since these are associated with a window, not a buffer. - Does not change the jumplist. - - "lnum" and "col" are the position in the buffer. The first - column is 1. Use a zero "lnum" to delete a mark. If "col" is - smaller than 1 then 1 is used. - - The "off" number is only used when 'virtualedit' is set. Then - it is the offset in screen columns from the start of the - character. E.g., a position within a <Tab> or after the last - character. - - The "curswant" number is only used when setting the cursor - position. It sets the preferred column for when moving the - cursor vertically. When the "curswant" number is missing the - preferred column is not set. When it is present and setting a - mark position it is not used. - - Note that for '< and '> changing the line number may result in - the marks to be effectively be swapped, so that '< is always - before '>. - - Returns 0 when the position could be set, -1 otherwise. - An error message is given if {expr} is invalid. - - Also see |getpos()| and |getcurpos()|. - - This does not restore the preferred column for moving - vertically; if you set the cursor position with this, |j| and - |k| motions will jump to previous columns! Use |cursor()| to - also set the preferred column. Also see the "curswant" key in - |winrestview()|. - - Can also be used as a |method|: > - GetPosition()->setpos('.') - -setqflist({list} [, {action} [, {what}]]) *setqflist()* - Create or replace or add to the quickfix list. - - If the optional {what} dictionary argument is supplied, then - only the items listed in {what} are set. The first {list} - argument is ignored. See below for the supported items in - {what}. - *setqflist-what* - When {what} is not present, the items in {list} are used. Each - item must be a dictionary. Non-dictionary items in {list} are - ignored. Each dictionary item can contain the following - entries: - - bufnr buffer number; must be the number of a valid - buffer - filename name of a file; only used when "bufnr" is not - present or it is invalid. - module name of a module; if given it will be used in - quickfix error window instead of the filename - lnum line number in the file - pattern search pattern used to locate the error - col column number - vcol when non-zero: "col" is visual column - when zero: "col" is byte index - nr error number - text description of the error - type single-character error type, 'E', 'W', etc. - valid recognized error message - - The "col", "vcol", "nr", "type" and "text" entries are - optional. Either "lnum" or "pattern" entry can be used to - locate a matching error line. - If the "filename" and "bufnr" entries are not present or - neither the "lnum" or "pattern" entries are present, then the - item will not be handled as an error line. - If both "pattern" and "lnum" are present then "pattern" will - be used. - If the "valid" entry is not supplied, then the valid flag is - set when "bufnr" is a valid buffer or "filename" exists. - If you supply an empty {list}, the quickfix list will be - cleared. - Note that the list is not exactly the same as what - |getqflist()| returns. - - {action} values: *setqflist-action* *E927* - 'a' The items from {list} are added to the existing - quickfix list. If there is no existing list, then a - new list is created. - - 'r' The items from the current quickfix list are replaced - with the items from {list}. This can also be used to - clear the list: > - :call setqflist([], 'r') -< - 'f' All the quickfix lists in the quickfix stack are - freed. - - If {action} is not present or is set to ' ', then a new list - is created. The new quickfix list is added after the current - quickfix list in the stack and all the following lists are - freed. To add a new quickfix list at the end of the stack, - set "nr" in {what} to "$". - - The following items can be specified in dictionary {what}: - context quickfix list context. See |quickfix-context| - efm errorformat to use when parsing text from - "lines". If this is not present, then the - 'errorformat' option value is used. - See |quickfix-parse| - id quickfix list identifier |quickfix-ID| - idx index of the current entry in the quickfix - list specified by 'id' or 'nr'. If set to '$', - then the last entry in the list is set as the - current entry. See |quickfix-index| - items list of quickfix entries. Same as the {list} - argument. - lines use 'errorformat' to parse a list of lines and - add the resulting entries to the quickfix list - {nr} or {id}. Only a |List| value is supported. - See |quickfix-parse| - nr list number in the quickfix stack; zero - means the current quickfix list and "$" means - the last quickfix list. - quickfixtextfunc - function to get the text to display in the - quickfix window. The value can be the name of - a function or a funcref or a lambda. Refer to - |quickfix-window-function| for an explanation - of how to write the function and an example. - title quickfix list title text. See |quickfix-title| - Unsupported keys in {what} are ignored. - If the "nr" item is not present, then the current quickfix list - is modified. When creating a new quickfix list, "nr" can be - set to a value one greater than the quickfix stack size. - When modifying a quickfix list, to guarantee that the correct - list is modified, "id" should be used instead of "nr" to - specify the list. - - Examples (See also |setqflist-examples|): > - :call setqflist([], 'r', {'title': 'My search'}) - :call setqflist([], 'r', {'nr': 2, 'title': 'Errors'}) - :call setqflist([], 'a', {'id':qfid, 'lines':["F1:10:L10"]}) -< - Returns zero for success, -1 for failure. - - This function can be used to create a quickfix list - independent of the 'errorformat' setting. Use a command like - `:cc 1` to jump to the first position. - - Can also be used as a |method|, the base is passed as the - second argument: > - GetErrorlist()->setqflist() -< - *setreg()* -setreg({regname}, {value} [, {options}]) - Set the register {regname} to {value}. - The {regname} argument is a string. - - {value} may be any value returned by |getreg()| or - |getreginfo()|, including a |List| or |Dict|. - If {options} contains "a" or {regname} is upper case, - then the value is appended. - - {options} can also contain a register type specification: - "c" or "v" |charwise| mode - "l" or "V" |linewise| mode - "b" or "<CTRL-V>" |blockwise-visual| mode - If a number immediately follows "b" or "<CTRL-V>" then this is - used as the width of the selection - if it is not specified - then the width of the block is set to the number of characters - in the longest line (counting a <Tab> as 1 character). - If {options} contains "u" or '"', then the unnamed register is - set to point to register {regname}. - - If {options} contains no register settings, then the default - is to use character mode unless {value} ends in a <NL> for - string {value} and linewise mode for list {value}. Blockwise - mode is never selected automatically. - Returns zero for success, non-zero for failure. - - *E883* - Note: you may not use |List| containing more than one item to - set search and expression registers. Lists containing no - items act like empty strings. - - Examples: > - :call setreg(v:register, @*) - :call setreg('*', @%, 'ac') - :call setreg('a', "1\n2\n3", 'b5') - :call setreg('"', { 'points_to': 'a'}) - -< This example shows using the functions to save and restore a - register: > - :let var_a = getreginfo() - :call setreg('a', var_a) -< or: > - :let var_a = getreg('a', 1, 1) - :let var_amode = getregtype('a') - .... - :call setreg('a', var_a, var_amode) -< Note: you may not reliably restore register value - without using the third argument to |getreg()| as without it - newlines are represented as newlines AND Nul bytes are - represented as newlines as well, see |NL-used-for-Nul|. - - You can also change the type of a register by appending - nothing: > - :call setreg('a', '', 'al') - -< Can also be used as a |method|, the base is passed as the - second argument: > - GetText()->setreg('a') - -settabvar({tabnr}, {varname}, {val}) *settabvar()* - Set tab-local variable {varname} to {val} in tab page {tabnr}. - |t:var| - The {varname} argument is a string. - Note that the variable name without "t:" must be used. - Tabs are numbered starting with one. - This function is not available in the |sandbox|. - - Can also be used as a |method|, the base is passed as the - third argument: > - GetValue()->settabvar(tab, name) - -settabwinvar({tabnr}, {winnr}, {varname}, {val}) *settabwinvar()* - Set option or local variable {varname} in window {winnr} to - {val}. - Tabs are numbered starting with one. For the current tabpage - use |setwinvar()|. - {winnr} can be the window number or the |window-ID|. - When {winnr} is zero the current window is used. - This also works for a global or local buffer option, but it - doesn't work for a global or local buffer variable. - For a local buffer option the global value is unchanged. - Note that the variable name without "w:" must be used. - Examples: > - :call settabwinvar(1, 1, "&list", 0) - :call settabwinvar(3, 2, "myvar", "foobar") -< This function is not available in the |sandbox|. - - Can also be used as a |method|, the base is passed as the - fourth argument: > - GetValue()->settabwinvar(tab, winnr, name) - -settagstack({nr}, {dict} [, {action}]) *settagstack()* - Modify the tag stack of the window {nr} using {dict}. - {nr} can be the window number or the |window-ID|. - - For a list of supported items in {dict}, refer to - |gettagstack()|. "curidx" takes effect before changing the tag - stack. - *E962* - How the tag stack is modified depends on the {action} - argument: - - If {action} is not present or is set to 'r', then the tag - stack is replaced. - - If {action} is set to 'a', then new entries from {dict} are - pushed (added) onto the tag stack. - - If {action} is set to 't', then all the entries from the - current entry in the tag stack or "curidx" in {dict} are - removed and then new entries are pushed to the stack. - - The current index is set to one after the length of the tag - stack after the modification. - - Returns zero for success, -1 for failure. - - Examples (for more examples see |tagstack-examples|): - Empty the tag stack of window 3: > - call settagstack(3, {'items' : []}) - -< Save and restore the tag stack: > - let stack = gettagstack(1003) - " do something else - call settagstack(1003, stack) - unlet stack -< - Can also be used as a |method|, the base is passed as the - second argument: > - GetStack()->settagstack(winnr) - -setwinvar({nr}, {varname}, {val}) *setwinvar()* - Like |settabwinvar()| for the current tab page. - Examples: > - :call setwinvar(1, "&list", 0) - :call setwinvar(2, "myvar", "foobar") - -< Can also be used as a |method|, the base is passed as the - third argument: > - GetValue()->setwinvar(winnr, name) - -sha256({string}) *sha256()* - Returns a String with 64 hex characters, which is the SHA256 - checksum of {string}. - - Can also be used as a |method|: > - GetText()->sha256() - -shellescape({string} [, {special}]) *shellescape()* - Escape {string} for use as a shell command argument. - - On Windows when 'shellslash' is not set, encloses {string} in - double-quotes and doubles all double-quotes within {string}. - Otherwise encloses {string} in single-quotes and replaces all - "'" with "'\''". - - If {special} is a |non-zero-arg|: - - Special items such as "!", "%", "#" and "<cword>" will be - preceded by a backslash. The backslash will be removed again - by the |:!| command. - - The <NL> character is escaped. - - If 'shell' contains "csh" in the tail: - - The "!" character will be escaped. This is because csh and - tcsh use "!" for history replacement even in single-quotes. - - The <NL> character is escaped (twice if {special} is - a |non-zero-arg|). - - If 'shell' contains "fish" in the tail, the "\" character will - be escaped because in fish it is used as an escape character - inside single quotes. - - Example of use with a |:!| command: > - :exe '!dir ' . shellescape(expand('<cfile>'), 1) -< This results in a directory listing for the file under the - cursor. Example of use with |system()|: > - :call system("chmod +w -- " . shellescape(expand("%"))) -< See also |::S|. - - Can also be used as a |method|: > - GetCommand()->shellescape() - -shiftwidth([{col}]) *shiftwidth()* - Returns the effective value of 'shiftwidth'. This is the - 'shiftwidth' value unless it is zero, in which case it is the - 'tabstop' value. To be backwards compatible in indent - plugins, use this: > - if exists('*shiftwidth') - func s:sw() - return shiftwidth() - endfunc - else - func s:sw() - return &sw - endfunc - endif -< And then use s:sw() instead of &sw. - - When there is one argument {col} this is used as column number - for which to return the 'shiftwidth' value. This matters for the - 'vartabstop' feature. If no {col} argument is given, column 1 - will be assumed. - - Can also be used as a |method|: > - GetColumn()->shiftwidth() - -sign_ functions are documented here: |sign-functions-details| - -simplify({filename}) *simplify()* - Simplify the file name as much as possible without changing - the meaning. Shortcuts (on MS-Windows) or symbolic links (on - Unix) are not resolved. If the first path component in - {filename} designates the current directory, this will be - valid for the result as well. A trailing path separator is - not removed either. On Unix "//path" is unchanged, but - "///path" is simplified to "/path" (this follows the Posix - standard). - Example: > - simplify("./dir/.././/file/") == "./file/" -< Note: The combination "dir/.." is only removed if "dir" is - a searchable directory or does not exist. On Unix, it is also - removed when "dir" is a symbolic link within the same - directory. In order to resolve all the involved symbolic - links before simplifying the path name, use |resolve()|. - - Can also be used as a |method|: > - GetName()->simplify() - -sin({expr}) *sin()* - Return the sine of {expr}, measured in radians, as a |Float|. - {expr} must evaluate to a |Float| or a |Number|. - Examples: > - :echo sin(100) -< -0.506366 > - :echo sin(-4.01) -< 0.763301 - - Can also be used as a |method|: > - Compute()->sin() - -sinh({expr}) *sinh()* - Return the hyperbolic sine of {expr} as a |Float| in the range - [-inf, inf]. - {expr} must evaluate to a |Float| or a |Number|. - Examples: > - :echo sinh(0.5) -< 0.521095 > - :echo sinh(-0.9) -< -1.026517 - - Can also be used as a |method|: > - Compute()->sinh() - -sockconnect({mode}, {address} [, {opts}]) *sockconnect()* - Connect a socket to an address. If {mode} is "pipe" then - {address} should be the path of a named pipe. If {mode} is - "tcp" then {address} should be of the form "host:port" where - the host should be an ip adderess or host name, and port the - port number. - - Returns a |channel| ID. Close the socket with |chanclose()|. - Use |chansend()| to send data over a bytes socket, and - |rpcrequest()| and |rpcnotify()| to communicate with a RPC - socket. - - {opts} is an optional dictionary with these keys: - |on_data| : callback invoked when data was read from socket - data_buffered : read socket data in |channel-buffered| mode. - rpc : If set, |msgpack-rpc| will be used to communicate - over the socket. - Returns: - - The channel ID on success (greater than zero) - - 0 on invalid arguments or connection failure. - -sort({list} [, {func} [, {dict}]]) *sort()* *E702* - Sort the items in {list} in-place. Returns {list}. - - If you want a list to remain unmodified make a copy first: > - :let sortedlist = sort(copy(mylist)) - -< When {func} is omitted, is empty or zero, then sort() uses the - string representation of each item to sort on. Numbers sort - after Strings, |Lists| after Numbers. For sorting text in the - current buffer use |:sort|. - - When {func} is given and it is '1' or 'i' then case is - ignored. - - When {func} is given and it is 'l' then the current collation - locale is used for ordering. Implementation details: strcoll() - is used to compare strings. See |:language| check or set the - collation locale. |v:collate| can also be used to check the - current locale. Sorting using the locale typically ignores - case. Example: > - " ö is sorted similarly to o with English locale. - :language collate en_US.UTF8 - :echo sort(['n', 'o', 'O', 'ö', 'p', 'z'], 'l') -< ['n', 'o', 'O', 'ö', 'p', 'z'] ~ -> - " ö is sorted after z with Swedish locale. - :language collate sv_SE.UTF8 - :echo sort(['n', 'o', 'O', 'ö', 'p', 'z'], 'l') -< ['n', 'o', 'O', 'p', 'z', 'ö'] ~ - This does not work properly on Mac. - - When {func} is given and it is 'n' then all items will be - sorted numerical (Implementation detail: this uses the - strtod() function to parse numbers, Strings, Lists, Dicts and - Funcrefs will be considered as being 0). - - When {func} is given and it is 'N' then all items will be - sorted numerical. This is like 'n' but a string containing - digits will be used as the number they represent. - - When {func} is given and it is 'f' then all items will be - sorted numerical. All values must be a Number or a Float. - - When {func} is a |Funcref| or a function name, this function - is called to compare items. The function is invoked with two - items as argument and must return zero if they are equal, 1 or - bigger if the first one sorts after the second one, -1 or - smaller if the first one sorts before the second one. - - {dict} is for functions with the "dict" attribute. It will be - used to set the local variable "self". |Dictionary-function| - - The sort is stable, items which compare equal (as number or as - string) will keep their relative position. E.g., when sorting - on numbers, text strings will sort next to each other, in the - same order as they were originally. - - Can also be used as a |method|: > - mylist->sort() - -< Also see |uniq()|. - - Example: > - func MyCompare(i1, i2) - return a:i1 == a:i2 ? 0 : a:i1 > a:i2 ? 1 : -1 - endfunc - eval mylist->sort("MyCompare") -< A shorter compare version for this specific simple case, which - ignores overflow: > - func MyCompare(i1, i2) - return a:i1 - a:i2 - endfunc -< For a simple expression you can use a lambda: > - eval mylist->sort({i1, i2 -> i1 - i2}) -< - *soundfold()* -soundfold({word}) - Return the sound-folded equivalent of {word}. Uses the first - language in 'spelllang' for the current window that supports - soundfolding. 'spell' must be set. When no sound folding is - possible the {word} is returned unmodified. - This can be used for making spelling suggestions. Note that - the method can be quite slow. - - Can also be used as a |method|: > - GetWord()->soundfold() -< - *spellbadword()* -spellbadword([{sentence}]) - Without argument: The result is the badly spelled word under - or after the cursor. The cursor is moved to the start of the - bad word. When no bad word is found in the cursor line the - result is an empty string and the cursor doesn't move. - - With argument: The result is the first word in {sentence} that - is badly spelled. If there are no spelling mistakes the - result is an empty string. - - The return value is a list with two items: - - The badly spelled word or an empty string. - - The type of the spelling error: - "bad" spelling mistake - "rare" rare word - "local" word only valid in another region - "caps" word should start with Capital - Example: > - echo spellbadword("the quik brown fox") -< ['quik', 'bad'] ~ - - The spelling information for the current window and the value - of 'spelllang' are used. - - Can also be used as a |method|: > - GetText()->spellbadword() -< - *spellsuggest()* -spellsuggest({word} [, {max} [, {capital}]]) - Return a |List| with spelling suggestions to replace {word}. - When {max} is given up to this number of suggestions are - returned. Otherwise up to 25 suggestions are returned. - - When the {capital} argument is given and it's non-zero only - suggestions with a leading capital will be given. Use this - after a match with 'spellcapcheck'. - - {word} can be a badly spelled word followed by other text. - This allows for joining two words that were split. The - suggestions also include the following text, thus you can - replace a line. - - {word} may also be a good word. Similar words will then be - returned. {word} itself is not included in the suggestions, - although it may appear capitalized. - - The spelling information for the current window is used. The - values of 'spelllang' and 'spellsuggest' are used. - - Can also be used as a |method|: > - GetWord()->spellsuggest() - -split({string} [, {pattern} [, {keepempty}]]) *split()* - Make a |List| out of {string}. When {pattern} is omitted or - empty each white-separated sequence of characters becomes an - item. - Otherwise the string is split where {pattern} matches, - removing the matched characters. 'ignorecase' is not used - here, add \c to ignore case. |/\c| - When the first or last item is empty it is omitted, unless the - {keepempty} argument is given and it's non-zero. - Other empty items are kept when {pattern} matches at least one - character or when {keepempty} is non-zero. - Example: > - :let words = split(getline('.'), '\W\+') -< To split a string in individual characters: > - :for c in split(mystring, '\zs') -< If you want to keep the separator you can also use '\zs' at - the end of the pattern: > - :echo split('abc:def:ghi', ':\zs') -< ['abc:', 'def:', 'ghi'] ~ - Splitting a table where the first element can be empty: > - :let items = split(line, ':', 1) -< The opposite function is |join()|. - - Can also be used as a |method|: > - GetString()->split() - -sqrt({expr}) *sqrt()* - Return the non-negative square root of Float {expr} as a - |Float|. - {expr} must evaluate to a |Float| or a |Number|. When {expr} - is negative the result is NaN (Not a Number). - Examples: > - :echo sqrt(100) -< 10.0 > - :echo sqrt(-4.01) -< nan - "nan" may be different, it depends on system libraries. - - Can also be used as a |method|: > - Compute()->sqrt() - -stdioopen({opts}) *stdioopen()* - With |--headless| this opens stdin and stdout as a |channel|. - May be called only once. See |channel-stdio|. stderr is not - handled by this function, see |v:stderr|. - - Close the stdio handles with |chanclose()|. Use |chansend()| - to send data to stdout, and |rpcrequest()| and |rpcnotify()| - to communicate over RPC. - - {opts} is a dictionary with these keys: - |on_stdin| : callback invoked when stdin is written to. - stdin_buffered : read stdin in |channel-buffered| mode. - rpc : If set, |msgpack-rpc| will be used to communicate - over stdio - Returns: - - |channel-id| on success (value is always 1) - - 0 on invalid arguments - - -stdpath({what}) *stdpath()* *E6100* - Returns |standard-path| locations of various default files and - directories. - - {what} Type Description ~ - cache String Cache directory. Arbitrary temporary - storage for plugins, etc. - config String User configuration directory. The - |init.vim| is stored here. - config_dirs List Additional configuration directories. - data String User data directory. The |shada-file| - is stored here. - data_dirs List Additional data directories. - - Example: > - :echo stdpath("config") - - -str2float({string} [, {quoted}]) *str2float()* - Convert String {string} to a Float. This mostly works the - same as when using a floating point number in an expression, - see |floating-point-format|. But it's a bit more permissive. - E.g., "1e40" is accepted, while in an expression you need to - write "1.0e40". The hexadecimal form "0x123" is also - accepted, but not others, like binary or octal. - When {quoted} is present and non-zero then embedded single - quotes before the dot are ignored, thus "1'000.0" is a - thousand. - Text after the number is silently ignored. - The decimal point is always '.', no matter what the locale is - set to. A comma ends the number: "12,345.67" is converted to - 12.0. You can strip out thousands separators with - |substitute()|: > - let f = str2float(substitute(text, ',', '', 'g')) -< - Can also be used as a |method|: > - let f = text->substitute(',', '', 'g')->str2float() - -str2list({string} [, {utf8}]) *str2list()* - Return a list containing the number values which represent - each character in String {string}. Examples: > - str2list(" ") returns [32] - str2list("ABC") returns [65, 66, 67] -< |list2str()| does the opposite. - - UTF-8 encoding is always used, {utf8} option has no effect, - and exists only for backwards-compatibility. - With UTF-8 composing characters are handled properly: > - str2list("á") returns [97, 769] - -< Can also be used as a |method|: > - GetString()->str2list() - -str2nr({string} [, {base}]) *str2nr()* - Convert string {string} to a number. - {base} is the conversion base, it can be 2, 8, 10 or 16. - When {quoted} is present and non-zero then embedded single - quotes are ignored, thus "1'000'000" is a million. - - When {base} is omitted base 10 is used. This also means that - a leading zero doesn't cause octal conversion to be used, as - with the default String to Number conversion. Example: > - let nr = str2nr('0123') -< - When {base} is 16 a leading "0x" or "0X" is ignored. With a - different base the result will be zero. Similarly, when - {base} is 8 a leading "0", "0o" or "0O" is ignored, and when - {base} is 2 a leading "0b" or "0B" is ignored. - Text after the number is silently ignored. - - Can also be used as a |method|: > - GetText()->str2nr() - -strcharpart({src}, {start} [, {len}]) *strcharpart()* - Like |strpart()| but using character index and length instead - of byte index and length. Composing characters are counted - separately. - When a character index is used where a character does not - exist it is assumed to be one character. For example: > - strcharpart('abc', -1, 2) -< results in 'a'. - - Can also be used as a |method|: > - GetText()->strcharpart(5) - -strchars({string} [, {skipcc}]) *strchars()* - The result is a Number, which is the number of characters - in String {string}. - When {skipcc} is omitted or zero, composing characters are - counted separately. - When {skipcc} set to 1, Composing characters are ignored. - Also see |strlen()|, |strdisplaywidth()| and |strwidth()|. - - {skipcc} is only available after 7.4.755. For backward - compatibility, you can define a wrapper function: > - if has("patch-7.4.755") - function s:strchars(str, skipcc) - return strchars(a:str, a:skipcc) - endfunction - else - function s:strchars(str, skipcc) - if a:skipcc - return strlen(substitute(a:str, ".", "x", "g")) - else - return strchars(a:str) - endif - endfunction - endif -< - Can also be used as a |method|: > - GetText()->strchars() - -strdisplaywidth({string} [, {col}]) *strdisplaywidth()* - The result is a Number, which is the number of display cells - String {string} occupies on the screen when it starts at {col} - (first column is zero). When {col} is omitted zero is used. - Otherwise it is the screen column where to start. This - matters for Tab characters. - The option settings of the current window are used. This - matters for anything that's displayed differently, such as - 'tabstop' and 'display'. - When {string} contains characters with East Asian Width Class - Ambiguous, this function's return value depends on 'ambiwidth'. - Also see |strlen()|, |strwidth()| and |strchars()|. - - Can also be used as a |method|: > - GetText()->strdisplaywidth() - -strftime({format} [, {time}]) *strftime()* - The result is a String, which is a formatted date and time, as - specified by the {format} string. The given {time} is used, - or the current time if no time is given. The accepted - {format} depends on your system, thus this is not portable! - See the manual page of the C function strftime() for the - format. The maximum length of the result is 80 characters. - See also |localtime()|, |getftime()| and |strptime()|. - The language can be changed with the |:language| command. - Examples: > - :echo strftime("%c") Sun Apr 27 11:49:23 1997 - :echo strftime("%Y %b %d %X") 1997 Apr 27 11:53:25 - :echo strftime("%y%m%d %T") 970427 11:53:55 - :echo strftime("%H:%M") 11:55 - :echo strftime("%c", getftime("file.c")) - Show mod time of file.c. - -< Can also be used as a |method|: > - GetFormat()->strftime() - -strgetchar({str}, {index}) *strgetchar()* - Get character {index} from {str}. This uses a character - index, not a byte index. Composing characters are considered - separate characters here. - Also see |strcharpart()| and |strchars()|. - - Can also be used as a |method|: > - GetText()->strgetchar(5) - -stridx({haystack}, {needle} [, {start}]) *stridx()* - The result is a Number, which gives the byte index in - {haystack} of the first occurrence of the String {needle}. - If {start} is specified, the search starts at index {start}. - This can be used to find a second match: > - :let colon1 = stridx(line, ":") - :let colon2 = stridx(line, ":", colon1 + 1) -< The search is done case-sensitive. - For pattern searches use |match()|. - -1 is returned if the {needle} does not occur in {haystack}. - See also |strridx()|. - Examples: > - :echo stridx("An Example", "Example") 3 - :echo stridx("Starting point", "Start") 0 - :echo stridx("Starting point", "start") -1 -< *strstr()* *strchr()* - stridx() works similar to the C function strstr(). When used - with a single character it works similar to strchr(). - - Can also be used as a |method|: > - GetHaystack()->stridx(needle) - - *string()* -string({expr}) Return {expr} converted to a String. If {expr} is a Number, - Float, String, Blob or a composition of them, then the result - can be parsed back with |eval()|. - {expr} type result ~ - String 'string' - Number 123 - Float 123.123456 or 1.123456e8 or - `str2float('inf')` - Funcref `function('name')` - Blob 0z00112233.44556677.8899 - List [item, item] - Dictionary {key: value, key: value} - Note that in String values the ' character is doubled. - Also see |strtrans()|. - Note 2: Output format is mostly compatible with YAML, except - for infinite and NaN floating-point values representations - which use |str2float()|. Strings are also dumped literally, - only single quote is escaped, which does not allow using YAML - for parsing back binary strings. |eval()| should always work for - strings and floats though and this is the only official - method, use |msgpackdump()| or |json_encode()| if you need to - share data with other application. - - Can also be used as a |method|: > - mylist->string() - -strlen({string}) *strlen()* - The result is a Number, which is the length of the String - {string} in bytes. - If the argument is a Number it is first converted to a String. - For other types an error is given. - If you want to count the number of multibyte characters use - |strchars()|. - Also see |len()|, |strdisplaywidth()| and |strwidth()|. - - Can also be used as a |method|: > - GetString()->strlen() - -strpart({src}, {start} [, {len} [, {chars}]]) *strpart()* - The result is a String, which is part of {src}, starting from - byte {start}, with the byte length {len}. - When {chars} is present and TRUE then {len} is the number of - characters positions (composing characters are not counted - separately, thus "1" means one base character and any - following composing characters). - To count {start} as characters instead of bytes use - |strcharpart()|. - - When bytes are selected which do not exist, this doesn't - result in an error, the bytes are simply omitted. - If {len} is missing, the copy continues from {start} till the - end of the {src}. > - strpart("abcdefg", 3, 2) == "de" - strpart("abcdefg", -2, 4) == "ab" - strpart("abcdefg", 5, 4) == "fg" - strpart("abcdefg", 3) == "defg" - -< Note: To get the first character, {start} must be 0. For - example, to get the character under the cursor: > - strpart(getline("."), col(".") - 1, 1, v:true) -< - Can also be used as a |method|: > - GetText()->strpart(5) - -strptime({format}, {timestring}) *strptime()* - The result is a Number, which is a unix timestamp representing - the date and time in {timestring}, which is expected to match - the format specified in {format}. - - The accepted {format} depends on your system, thus this is not - portable! See the manual page of the C function strptime() - for the format. Especially avoid "%c". The value of $TZ also - matters. - - If the {timestring} cannot be parsed with {format} zero is - returned. If you do not know the format of {timestring} you - can try different {format} values until you get a non-zero - result. - - See also |strftime()|. - Examples: > - :echo strptime("%Y %b %d %X", "1997 Apr 27 11:49:23") -< 862156163 > - :echo strftime("%c", strptime("%y%m%d %T", "970427 11:53:55")) -< Sun Apr 27 11:53:55 1997 > - :echo strftime("%c", strptime("%Y%m%d%H%M%S", "19970427115355") + 3600) -< Sun Apr 27 12:53:55 1997 - - Can also be used as a |method|: > - GetFormat()->strptime(timestring) -< -strridx({haystack}, {needle} [, {start}]) *strridx()* - The result is a Number, which gives the byte index in - {haystack} of the last occurrence of the String {needle}. - When {start} is specified, matches beyond this index are - ignored. This can be used to find a match before a previous - match: > - :let lastcomma = strridx(line, ",") - :let comma2 = strridx(line, ",", lastcomma - 1) -< The search is done case-sensitive. - For pattern searches use |match()|. - -1 is returned if the {needle} does not occur in {haystack}. - If the {needle} is empty the length of {haystack} is returned. - See also |stridx()|. Examples: > - :echo strridx("an angry armadillo", "an") 3 -< *strrchr()* - When used with a single character it works similar to the C - function strrchr(). - - Can also be used as a |method|: > - GetHaystack()->strridx(needle) - -strtrans({string}) *strtrans()* - The result is a String, which is {string} with all unprintable - characters translated into printable characters |'isprint'|. - Like they are shown in a window. Example: > - echo strtrans(@a) -< This displays a newline in register a as "^@" instead of - starting a new line. - - Can also be used as a |method|: > - GetString()->strtrans() - -strwidth({string}) *strwidth()* - The result is a Number, which is the number of display cells - String {string} occupies. A Tab character is counted as one - cell, alternatively use |strdisplaywidth()|. - When {string} contains characters with East Asian Width Class - Ambiguous, this function's return value depends on 'ambiwidth'. - Also see |strlen()|, |strdisplaywidth()| and |strchars()|. - - Can also be used as a |method|: > - GetString()->strwidth() - -submatch({nr} [, {list}]) *submatch()* *E935* - Only for an expression in a |:substitute| command or - substitute() function. - Returns the {nr}'th submatch of the matched text. When {nr} - is 0 the whole matched text is returned. - Note that a NL in the string can stand for a line break of a - multi-line match or a NUL character in the text. - Also see |sub-replace-expression|. - - If {list} is present and non-zero then submatch() returns - a list of strings, similar to |getline()| with two arguments. - NL characters in the text represent NUL characters in the - text. - Only returns more than one item for |:substitute|, inside - |substitute()| this list will always contain one or zero - items, since there are no real line breaks. - - When substitute() is used recursively only the submatches in - the current (deepest) call can be obtained. - - Examples: > - :s/\d\+/\=submatch(0) + 1/ - :echo substitute(text, '\d\+', '\=submatch(0) + 1', '') -< This finds the first number in the line and adds one to it. - A line break is included as a newline character. - - Can also be used as a |method|: > - GetNr()->submatch() - -substitute({string}, {pat}, {sub}, {flags}) *substitute()* - The result is a String, which is a copy of {string}, in which - the first match of {pat} is replaced with {sub}. - When {flags} is "g", all matches of {pat} in {string} are - replaced. Otherwise {flags} should be "". - - This works like the ":substitute" command (without any flags). - But the matching with {pat} is always done like the 'magic' - option is set and 'cpoptions' is empty (to make scripts - portable). 'ignorecase' is still relevant, use |/\c| or |/\C| - if you want to ignore or match case and ignore 'ignorecase'. - 'smartcase' is not used. See |string-match| for how {pat} is - used. - - A "~" in {sub} is not replaced with the previous {sub}. - Note that some codes in {sub} have a special meaning - |sub-replace-special|. For example, to replace something with - "\n" (two characters), use "\\\\n" or '\\n'. - - When {pat} does not match in {string}, {string} is returned - unmodified. - - Example: > - :let &path = substitute(&path, ",\\=[^,]*$", "", "") -< This removes the last component of the 'path' option. > - :echo substitute("testing", ".*", "\\U\\0", "") -< results in "TESTING". - - When {sub} starts with "\=", the remainder is interpreted as - an expression. See |sub-replace-expression|. Example: > - :echo substitute(s, '%\(\x\x\)', - \ '\=nr2char("0x" . submatch(1))', 'g') - -< When {sub} is a Funcref that function is called, with one - optional argument. Example: > - :echo substitute(s, '%\(\x\x\)', SubNr, 'g') -< The optional argument is a list which contains the whole - matched string and up to nine submatches, like what - |submatch()| returns. Example: > - :echo substitute(s, '%\(\x\x\)', {m -> '0x' . m[1]}, 'g') - -< Can also be used as a |method|: > - GetString()->substitute(pat, sub, flags) - -swapinfo({fname}) *swapinfo()* - The result is a dictionary, which holds information about the - swapfile {fname}. The available fields are: - version VIM version - user user name - host host name - fname original file name - pid PID of the VIM process that created the swap - file - mtime last modification time in seconds - inode Optional: INODE number of the file - dirty 1 if file was modified, 0 if not - In case of failure an "error" item is added with the reason: - Cannot open file: file not found or in accessible - Cannot read file: cannot read first block - Not a swap file: does not contain correct block ID - Magic number mismatch: Info in first block is invalid - - Can also be used as a |method|: > - GetFilename()->swapinfo() - -swapname({buf}) *swapname()* - The result is the swap file path of the buffer {buf}. - For the use of {buf}, see |bufname()| above. - If buffer {buf} is the current buffer, the result is equal to - |:swapname| (unless there is no swap file). - If buffer {buf} has no swap file, returns an empty string. - - Can also be used as a |method|: > - GetBufname()->swapname() - -synID({lnum}, {col}, {trans}) *synID()* - The result is a Number, which is the syntax ID at the position - {lnum} and {col} in the current window. - The syntax ID can be used with |synIDattr()| and - |synIDtrans()| to obtain syntax information about text. - - {col} is 1 for the leftmost column, {lnum} is 1 for the first - line. 'synmaxcol' applies, in a longer line zero is returned. - Note that when the position is after the last character, - that's where the cursor can be in Insert mode, synID() returns - zero. {lnum} is used like with |getline()|. - - When {trans} is |TRUE|, transparent items are reduced to the - item that they reveal. This is useful when wanting to know - the effective color. When {trans} is |FALSE|, the transparent - item is returned. This is useful when wanting to know which - syntax item is effective (e.g. inside parens). - Warning: This function can be very slow. Best speed is - obtained by going through the file in forward direction. - - Example (echoes the name of the syntax item under the cursor): > - :echo synIDattr(synID(line("."), col("."), 1), "name") -< - -synIDattr({synID}, {what} [, {mode}]) *synIDattr()* - The result is a String, which is the {what} attribute of - syntax ID {synID}. This can be used to obtain information - about a syntax item. - {mode} can be "gui", "cterm" or "term", to get the attributes - for that mode. When {mode} is omitted, or an invalid value is - used, the attributes for the currently active highlighting are - used (GUI, cterm or term). - Use synIDtrans() to follow linked highlight groups. - {what} result - "name" the name of the syntax item - "fg" foreground color (GUI: color name used to set - the color, cterm: color number as a string, - term: empty string) - "bg" background color (as with "fg") - "font" font name (only available in the GUI) - |highlight-font| - "sp" special color (as with "fg") |highlight-guisp| - "fg#" like "fg", but for the GUI and the GUI is - running the name in "#RRGGBB" form - "bg#" like "fg#" for "bg" - "sp#" like "fg#" for "sp" - "bold" "1" if bold - "italic" "1" if italic - "reverse" "1" if reverse - "inverse" "1" if inverse (= reverse) - "standout" "1" if standout - "underline" "1" if underlined - "undercurl" "1" if undercurled - "strikethrough" "1" if struckthrough - - Example (echoes the color of the syntax item under the - cursor): > - :echo synIDattr(synIDtrans(synID(line("."), col("."), 1)), "fg") -< - Can also be used as a |method|: > - :echo synID(line("."), col("."), 1)->synIDtrans()->synIDattr("fg") - -synIDtrans({synID}) *synIDtrans()* - The result is a Number, which is the translated syntax ID of - {synID}. This is the syntax group ID of what is being used to - highlight the character. Highlight links given with - ":highlight link" are followed. - - Can also be used as a |method|: > - :echo synID(line("."), col("."), 1)->synIDtrans()->synIDattr("fg") - -synconcealed({lnum}, {col}) *synconcealed()* - The result is a |List| with currently three items: - 1. The first item in the list is 0 if the character at the - position {lnum} and {col} is not part of a concealable - region, 1 if it is. {lnum} is used like with |getline()|. - 2. The second item in the list is a string. If the first item - is 1, the second item contains the text which will be - displayed in place of the concealed text, depending on the - current setting of 'conceallevel' and 'listchars'. - 3. The third and final item in the list is a number - representing the specific syntax region matched in the - line. When the character is not concealed the value is - zero. This allows detection of the beginning of a new - concealable region if there are two consecutive regions - with the same replacement character. For an example, if - the text is "123456" and both "23" and "45" are concealed - and replaced by the character "X", then: - call returns ~ - synconcealed(lnum, 1) [0, '', 0] - synconcealed(lnum, 2) [1, 'X', 1] - synconcealed(lnum, 3) [1, 'X', 1] - synconcealed(lnum, 4) [1, 'X', 2] - synconcealed(lnum, 5) [1, 'X', 2] - synconcealed(lnum, 6) [0, '', 0] - - -synstack({lnum}, {col}) *synstack()* - Return a |List|, which is the stack of syntax items at the - position {lnum} and {col} in the current window. {lnum} is - used like with |getline()|. Each item in the List is an ID - like what |synID()| returns. - The first item in the List is the outer region, following are - items contained in that one. The last one is what |synID()| - returns, unless not the whole item is highlighted or it is a - transparent item. - This function is useful for debugging a syntax file. - Example that shows the syntax stack under the cursor: > - for id in synstack(line("."), col(".")) - echo synIDattr(id, "name") - endfor -< When the position specified with {lnum} and {col} is invalid - nothing is returned. The position just after the last - character in a line and the first column in an empty line are - valid positions. - -system({cmd} [, {input}]) *system()* *E677* - Gets the output of {cmd} as a |string| (|systemlist()| returns - a |List|) and sets |v:shell_error| to the error code. - {cmd} is treated as in |jobstart()|: - If {cmd} is a List it runs directly (no 'shell'). - If {cmd} is a String it runs in the 'shell', like this: > - :call jobstart(split(&shell) + split(&shellcmdflag) + ['{cmd}']) - -< Not to be used for interactive commands. - - Result is a String, filtered to avoid platform-specific quirks: - - <CR><NL> is replaced with <NL> - - NUL characters are replaced with SOH (0x01) - - Example: > - :echo system(['ls', expand('%:h')]) - -< If {input} is a string it is written to a pipe and passed as - stdin to the command. The string is written as-is, line - separators are not changed. - If {input} is a |List| it is written to the pipe as - |writefile()| does with {binary} set to "b" (i.e. with - a newline between each list item, and newlines inside list - items converted to NULs). - When {input} is given and is a valid buffer id, the content of - the buffer is written to the file line by line, each line - terminated by NL (and NUL where the text has NL). - *E5677* - Note: system() cannot write to or read from backgrounded ("&") - shell commands, e.g.: > - :echo system("cat - &", "foo") -< which is equivalent to: > - $ echo foo | bash -c 'cat - &' -< The pipes are disconnected (unless overridden by shell - redirection syntax) before input can reach it. Use - |jobstart()| instead. - - Note: Use |shellescape()| or |::S| with |expand()| or - |fnamemodify()| to escape special characters in a command - argument. 'shellquote' and 'shellxquote' must be properly - configured. Example: > - :echo system('ls '..shellescape(expand('%:h'))) - :echo system('ls '..expand('%:h:S')) - -< Unlike ":!cmd" there is no automatic check for changed files. - Use |:checktime| to force a check. - - Can also be used as a |method|: > - :echo GetCmd()->system() - -systemlist({cmd} [, {input} [, {keepempty}]]) *systemlist()* - Same as |system()|, but returns a |List| with lines (parts of - output separated by NL) with NULs transformed into NLs. Output - is the same as |readfile()| will output with {binary} argument - set to "b", except that a final newline is not preserved, - unless {keepempty} is non-zero. - Note that on MS-Windows you may get trailing CR characters. - - To see the difference between "echo hello" and "echo -n hello" - use |system()| and |split()|: > - echo split(system('echo hello'), '\n', 1) -< - Returns an empty string on error. - - Can also be used as a |method|: > - :echo GetCmd()->systemlist() - -tabpagebuflist([{arg}]) *tabpagebuflist()* - The result is a |List|, where each item is the number of the - buffer associated with each window in the current tab page. - {arg} specifies the number of the tab page to be used. When - omitted the current tab page is used. - When {arg} is invalid the number zero is returned. - To get a list of all buffers in all tabs use this: > - let buflist = [] - for i in range(tabpagenr('$')) - call extend(buflist, tabpagebuflist(i + 1)) - endfor -< Note that a buffer may appear in more than one window. - - Can also be used as a |method|: > - GetTabpage()->tabpagebuflist() - -tabpagenr([{arg}]) *tabpagenr()* - The result is a Number, which is the number of the current - tab page. The first tab page has number 1. - The optional argument {arg} supports the following values: - $ the number of the last tab page (the tab page - count). - # the number of the last accessed tab page (where - |g<Tab>| goes to). If there is no previous - tab page, 0 is returned. - The number can be used with the |:tab| command. - - -tabpagewinnr({tabarg} [, {arg}]) *tabpagewinnr()* - Like |winnr()| but for tab page {tabarg}. - {tabarg} specifies the number of tab page to be used. - {arg} is used like with |winnr()|: - - When omitted the current window number is returned. This is - the window which will be used when going to this tab page. - - When "$" the number of windows is returned. - - When "#" the previous window nr is returned. - Useful examples: > - tabpagewinnr(1) " current window of tab page 1 - tabpagewinnr(4, '$') " number of windows in tab page 4 -< When {tabarg} is invalid zero is returned. - - Can also be used as a |method|: > - GetTabpage()->tabpagewinnr() -< - *tagfiles()* -tagfiles() Returns a |List| with the file names used to search for tags - for the current buffer. This is the 'tags' option expanded. - - -taglist({expr} [, {filename}]) *taglist()* - Returns a |List| of tags matching the regular expression {expr}. - - If {filename} is passed it is used to prioritize the results - in the same way that |:tselect| does. See |tag-priority|. - {filename} should be the full path of the file. - - Each list item is a dictionary with at least the following - entries: - name Name of the tag. - filename Name of the file where the tag is - defined. It is either relative to the - current directory or a full path. - cmd Ex command used to locate the tag in - the file. - kind Type of the tag. The value for this - entry depends on the language specific - kind values. Only available when - using a tags file generated by - Exuberant ctags or hdrtag. - static A file specific tag. Refer to - |static-tag| for more information. - More entries may be present, depending on the content of the - tags file: access, implementation, inherits and signature. - Refer to the ctags documentation for information about these - fields. For C code the fields "struct", "class" and "enum" - may appear, they give the name of the entity the tag is - contained in. - - The ex-command "cmd" can be either an ex search pattern, a - line number or a line number followed by a byte number. - - If there are no matching tags, then an empty list is returned. - - To get an exact tag match, the anchors '^' and '$' should be - used in {expr}. This also make the function work faster. - Refer to |tag-regexp| for more information about the tag - search regular expression pattern. - - Refer to |'tags'| for information about how the tags file is - located by Vim. Refer to |tags-file-format| for the format of - the tags file generated by the different ctags tools. - - Can also be used as a |method|: > - GetTagpattern()->taglist() - -tempname() *tempname()* *temp-file-name* - The result is a String, which is the name of a file that - doesn't exist. It can be used for a temporary file. Example: > - :let tmpfile = tempname() - :exe "redir > " . tmpfile -< For Unix, the file will be in a private directory |tempfile|. - For MS-Windows forward slashes are used when the 'shellslash' - option is set or when 'shellcmdflag' starts with '-'. - -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 - modified (all buffer contents are destroyed). - - The {opts} dict is similar to the one passed to |jobstart()|, - but the `pty`, `width`, `height`, and `TERM` fields are - ignored: `height`/`width` are taken from the current window - and `$TERM` is set to "xterm-256color". - Returns the same values as |jobstart()|. - - See |terminal| for more information. - -test_ functions are documented here: |test-functions-details| - -tan({expr}) *tan()* - Return the tangent of {expr}, measured in radians, as a |Float| - in the range [-inf, inf]. - {expr} must evaluate to a |Float| or a |Number|. - Examples: > - :echo tan(10) -< 0.648361 > - :echo tan(-4.01) -< -1.181502 - - Can also be used as a |method|: > - Compute()->tan() - -tanh({expr}) *tanh()* - Return the hyperbolic tangent of {expr} as a |Float| in the - range [-1, 1]. - {expr} must evaluate to a |Float| or a |Number|. - Examples: > - :echo tanh(0.5) -< 0.462117 > - :echo tanh(-1) -< -0.761594 - - Can also be used as a |method|: > - Compute()->tanh() -< - *timer_info()* -timer_info([{id}]) - Return a list with information about timers. - When {id} is given only information about this timer is - returned. When timer {id} does not exist an empty list is - returned. - When {id} is omitted information about all timers is returned. - - For each timer the information is stored in a |Dictionary| with - these items: - "id" the timer ID - "time" time the timer was started with - "repeat" number of times the timer will still fire; - -1 means forever - "callback" the callback - - Can also be used as a |method|: > - GetTimer()->timer_info() -< -timer_pause({timer}, {paused}) *timer_pause()* - Pause or unpause a timer. A paused timer does not invoke its - callback when its time expires. Unpausing a timer may cause - the callback to be invoked almost immediately if enough time - has passed. - - Pausing a timer is useful to avoid the callback to be called - for a short time. - - If {paused} evaluates to a non-zero Number or a non-empty - String, then the timer is paused, otherwise it is unpaused. - See |non-zero-arg|. - - Can also be used as a |method|: > - GetTimer()->timer_pause(1) -< - *timer_start()* *timer* *timers* -timer_start({time}, {callback} [, {options}]) - Create a timer and return the timer ID. - - {time} is the waiting time in milliseconds. This is the - minimum time before invoking the callback. When the system is - busy or Vim is not waiting for input the time will be longer. - - {callback} is the function to call. It can be the name of a - function or a |Funcref|. It is called with one argument, which - is the timer ID. The callback is only invoked when Vim is - waiting for input. - - {options} is a dictionary. Supported entries: - "repeat" Number of times to repeat the callback. - -1 means forever. Default is 1. - If the timer causes an error three times in a - row the repeat is cancelled. - - Example: > - func MyHandler(timer) - echo 'Handler called' - endfunc - let timer = timer_start(500, 'MyHandler', - \ {'repeat': 3}) -< This invokes MyHandler() three times at 500 msec intervals. - - Can also be used as a |method|: > - GetMsec()->timer_start(callback) - -< Not available in the |sandbox|. - -timer_stop({timer}) *timer_stop()* - Stop a timer. The timer callback will no longer be invoked. - {timer} is an ID returned by timer_start(), thus it must be a - Number. If {timer} does not exist there is no error. - - Can also be used as a |method|: > - GetTimer()->timer_stop() -< -timer_stopall() *timer_stopall()* - Stop all timers. The timer callbacks will no longer be - invoked. Useful if some timers is misbehaving. If there are - no timers there is no error. - -tolower({expr}) *tolower()* - The result is a copy of the String given, with all uppercase - characters turned into lowercase (just like applying |gu| to - the string). - - Can also be used as a |method|: > - GetText()->tolower() - -toupper({expr}) *toupper()* - The result is a copy of the String given, with all lowercase - characters turned into uppercase (just like applying |gU| to - the string). - - Can also be used as a |method|: > - GetText()->toupper() - -tr({src}, {fromstr}, {tostr}) *tr()* - The result is a copy of the {src} string with all characters - which appear in {fromstr} replaced by the character in that - position in the {tostr} string. Thus the first character in - {fromstr} is translated into the first character in {tostr} - and so on. Exactly like the unix "tr" command. - This code also deals with multibyte characters properly. - - Examples: > - echo tr("hello there", "ht", "HT") -< returns "Hello THere" > - echo tr("<blob>", "<>", "{}") -< returns "{blob}" - - Can also be used as a |method|: > - GetText()->tr(from, to) - -trim({text} [, {mask} [, {dir}]]) *trim()* - Return {text} as a String where any character in {mask} is - removed from the beginning and/or end of {text}. - If {mask} is not given, {mask} is all characters up to 0x20, - which includes Tab, space, NL and CR, plus the non-breaking - space character 0xa0. - The optional {dir} argument specifies where to remove the - characters: - 0 remove from the beginning and end of {text} - 1 remove only at the beginning of {text} - 2 remove only at the end of {text} - When omitted both ends are trimmed. - This function deals with multibyte characters properly. - Examples: > - echo trim(" some text ") -< returns "some text" > - echo trim(" \r\t\t\r RESERVE \t\n\x0B\xA0") . "_TAIL" -< returns "RESERVE_TAIL" > - echo trim("rm<Xrm<>X>rrm", "rm<>") -< returns "Xrm<>X" (characters in the middle are not removed) > - echo trim(" vim ", " ", 2) -< returns " vim" - - Can also be used as a |method|: > - GetText()->trim() - -trunc({expr}) *trunc()* - Return the largest integral value with magnitude less than or - equal to {expr} as a |Float| (truncate towards zero). - {expr} must evaluate to a |Float| or a |Number|. - Examples: > - echo trunc(1.456) -< 1.0 > - echo trunc(-5.456) -< -5.0 > - echo trunc(4.0) -< 4.0 - - Can also be used as a |method|: > - Compute()->trunc() - -type({expr}) *type()* - The result is a Number representing the type of {expr}. - Instead of using the number directly, it is better to use the - v:t_ variable that has the value: - Number: 0 (|v:t_number|) - String: 1 (|v:t_string|) - Funcref: 2 (|v:t_func|) - List: 3 (|v:t_list|) - Dictionary: 4 (|v:t_dict|) - Float: 5 (|v:t_float|) - Boolean: 6 (|v:true| and |v:false|) - Null: 7 (|v:null|) - Blob: 10 (|v:t_blob|) - For backward compatibility, this method can be used: > - :if type(myvar) == type(0) - :if type(myvar) == type("") - :if type(myvar) == type(function("tr")) - :if type(myvar) == type([]) - :if type(myvar) == type({}) - :if type(myvar) == type(0.0) - :if type(myvar) == type(v:true) -< In place of checking for |v:null| type it is better to check - for |v:null| directly as it is the only value of this type: > - :if myvar is v:null -< To check if the v:t_ variables exist use this: > - :if exists('v:t_number') - -< Can also be used as a |method|: > - mylist->type() - -undofile({name}) *undofile()* - Return the name of the undo file that would be used for a file - with name {name} when writing. This uses the 'undodir' - option, finding directories that exist. It does not check if - the undo file exists. - {name} is always expanded to the full path, since that is what - is used internally. - If {name} is empty undofile() returns an empty string, since a - buffer without a file name will not write an undo file. - Useful in combination with |:wundo| and |:rundo|. - - Can also be used as a |method|: > - GetFilename()->undofile() - -undotree() *undotree()* - Return the current state of the undo tree in a dictionary with - the following items: - "seq_last" The highest undo sequence number used. - "seq_cur" The sequence number of the current position in - the undo tree. This differs from "seq_last" - when some changes were undone. - "time_cur" Time last used for |:earlier| and related - commands. Use |strftime()| to convert to - something readable. - "save_last" Number of the last file write. Zero when no - write yet. - "save_cur" Number of the current position in the undo - tree. - "synced" Non-zero when the last undo block was synced. - This happens when waiting from input from the - user. See |undo-blocks|. - "entries" A list of dictionaries with information about - undo blocks. - - The first item in the "entries" list is the oldest undo item. - Each List item is a |Dictionary| with these items: - "seq" Undo sequence number. Same as what appears in - |:undolist|. - "time" Timestamp when the change happened. Use - |strftime()| to convert to something readable. - "newhead" Only appears in the item that is the last one - that was added. This marks the last change - and where further changes will be added. - "curhead" Only appears in the item that is the last one - that was undone. This marks the current - position in the undo tree, the block that will - be used by a redo command. When nothing was - undone after the last change this item will - not appear anywhere. - "save" Only appears on the last block before a file - write. The number is the write count. The - first write has number 1, the last one the - "save_last" mentioned above. - "alt" Alternate entry. This is again a List of undo - blocks. Each item may again have an "alt" - item. - -uniq({list} [, {func} [, {dict}]]) *uniq()* *E882* - Remove second and succeeding copies of repeated adjacent - {list} items in-place. Returns {list}. If you want a list - to remain unmodified make a copy first: > - :let newlist = uniq(copy(mylist)) -< The default compare function uses the string representation of - each item. For the use of {func} and {dict} see |sort()|. - - Can also be used as a |method|: > - mylist->uniq() - -values({dict}) *values()* - Return a |List| with all the values of {dict}. The |List| is - in arbitrary order. Also see |items()| and |keys()|. - - Can also be used as a |method|: > - mydict->values() - -virtcol({expr}) *virtcol()* - The result is a Number, which is the screen column of the file - position given with {expr}. That is, the last screen position - occupied by the character at that position, when the screen - would be of unlimited width. When there is a <Tab> at the - position, the returned Number will be the column at the end of - the <Tab>. For example, for a <Tab> in column 1, with 'ts' - set to 8, it returns 8. |conceal| is ignored. - For the byte position use |col()|. - For the use of {expr} see |col()|. - When 'virtualedit' is used {expr} can be [lnum, col, off], where - "off" is the offset in screen columns from the start of the - character. E.g., a position within a <Tab> or after the last - character. When "off" is omitted zero is used. - When Virtual editing is active in the current mode, a position - beyond the end of the line can be returned. |'virtualedit'| - The accepted positions are: - . the cursor position - $ the end of the cursor line (the result is the - number of displayed characters in the cursor line - plus one) - 'x position of mark x (if the mark is not set, 0 is - returned) - v In Visual mode: the start of the Visual area (the - cursor is the end). When not in Visual mode - returns the cursor position. Differs from |'<| in - that it's updated right away. - Note that only marks in the current file can be used. - Examples: > - virtcol(".") with text "foo^Lbar", with cursor on the "^L", returns 5 - virtcol("$") with text "foo^Lbar", returns 9 - virtcol("'t") with text " there", with 't at 'h', returns 6 -< The first column is 1. 0 is returned for an error. - A more advanced example that echoes the maximum length of - all lines: > - echo max(map(range(1, line('$')), "virtcol([v:val, '$'])")) - -< Can also be used as a |method|: > - GetPos()->virtcol() - -visualmode([{expr}]) *visualmode()* - The result is a String, which describes the last Visual mode - used in the current buffer. Initially it returns an empty - string, but once Visual mode has been used, it returns "v", - "V", or "<CTRL-V>" (a single CTRL-V character) for - character-wise, line-wise, or block-wise Visual mode - respectively. - Example: > - :exe "normal " . visualmode() -< This enters the same Visual mode as before. It is also useful - in scripts if you wish to act differently depending on the - Visual mode that was used. - If Visual mode is active, use |mode()| to get the Visual mode - (e.g., in a |:vmap|). - If {expr} is supplied and it evaluates to a non-zero Number or - 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()* - Waits until {condition} evaluates to |TRUE|, where {condition} - is a |Funcref| or |string| containing an expression. - - {timeout} is the maximum waiting time in milliseconds, -1 - means forever. - - Condition is evaluated on user events, internal events, and - every {interval} milliseconds (default: 200). - - Returns a status integer: - 0 if the condition was satisfied before timeout - -1 if the timeout was exceeded - -2 if the function was interrupted (by |CTRL-C|) - -3 if an error occurred - -wildmenumode() *wildmenumode()* - Returns |TRUE| when the wildmenu is active and |FALSE| - otherwise. See 'wildmenu' and 'wildmode'. - This can be used in mappings to handle the 'wildcharm' option - gracefully. (Makes only sense with |mapmode-c| mappings). - - For example to make <c-j> work like <down> in wildmode, use: > - :cnoremap <expr> <C-j> wildmenumode() ? "\<Down>\<Tab>" : "\<c-j>" -< - (Note, this needs the 'wildcharm' option set appropriately). - -win_execute({id}, {command} [, {silent}]) *win_execute()* - Like `execute()` but in the context of window {id}. - The window will temporarily be made the current window, - without triggering autocommands or changing directory. When - executing {command} autocommands will be triggered, this may - have unexpected side effects. Use |:noautocmd| if needed. - Example: > - call win_execute(winid, 'syntax enable') -< - Can also be used as a |method|, the base is passed as the - second argument: > - GetCommand()->win_execute(winid) - -win_findbuf({bufnr}) *win_findbuf()* - Returns a |List| with |window-ID|s for windows that contain - buffer {bufnr}. When there is none the list is empty. - - Can also be used as a |method|: > - GetBufnr()->win_findbuf() - -win_getid([{win} [, {tab}]]) *win_getid()* - Get the |window-ID| for the specified window. - When {win} is missing use the current window. - With {win} this is the window number. The top window has - number 1. - Without {tab} use the current tab, otherwise the tab with - number {tab}. The first tab has number one. - Return zero if the window cannot be found. - - Can also be used as a |method|: > - GetWinnr()->win_getid() - -win_gettype([{nr}]) *win_gettype()* - Return the type of the window: - "autocmd" autocommand window. Temporary window - used to execute autocommands. - "command" command-line window |cmdwin| - (empty) normal window - "loclist" |location-list-window| - "popup" popup window |popup| - "preview" preview window |preview-window| - "quickfix" |quickfix-window| - "unknown" window {nr} not found - - When {nr} is omitted return the type of the current window. - When {nr} is given return the type of this window by number or - |window-ID|. - - Also see the 'buftype' option. When running a terminal in a - popup window then 'buftype' is "terminal" and win_gettype() - returns "popup". - - Can also be used as a |method|: > - GetWinid()->win_gettype() -< -win_gotoid({expr}) *win_gotoid()* - Go to window with ID {expr}. This may also change the current - tabpage. - Return TRUE if successful, FALSE if the window cannot be found. - - Can also be used as a |method|: > - GetWinid()->win_gotoid() - -win_id2tabwin({expr}) *win_id2tabwin()* - Return a list with the tab number and window number of window - with ID {expr}: [tabnr, winnr]. - Return [0, 0] if the window cannot be found. - - Can also be used as a |method|: > - GetWinid()->win_id2tabwin() - -win_id2win({expr}) *win_id2win()* - Return the window number of window with ID {expr}. - Return 0 if the window cannot be found in the current tabpage. - - Can also be used as a |method|: > - GetWinid()->win_id2win() - -win_screenpos({nr}) *win_screenpos()* - Return the screen position of window {nr} as a list with two - numbers: [row, col]. The first window always has position - [1, 1], unless there is a tabline, then it is [2, 1]. - {nr} can be the window number or the |window-ID|. Use zero - for the current window. - Returns [0, 0] if the window cannot be found in the current - tabpage. - - Can also be used as a |method|: > - GetWinid()->win_screenpos() -< -win_splitmove({nr}, {target} [, {options}]) *win_splitmove()* - Move the window {nr} to a new split of the window {target}. - This is similar to moving to {target}, creating a new window - using |:split| but having the same contents as window {nr}, and - then closing {nr}. - - Both {nr} and {target} can be window numbers or |window-ID|s. - Both must be in the current tab page. - - Returns zero for success, non-zero for failure. - - {options} is a |Dictionary| with the following optional entries: - "vertical" When TRUE, the split is created vertically, - like with |:vsplit|. - "rightbelow" When TRUE, the split is made below or to the - right (if vertical). When FALSE, it is done - above or to the left (if vertical). When not - present, the values of 'splitbelow' and - 'splitright' are used. - - Can also be used as a |method|: > - GetWinid()->win_splitmove(target) -< - *winbufnr()* -winbufnr({nr}) The result is a Number, which is the number of the buffer - associated with window {nr}. {nr} can be the window number or - the |window-ID|. - When {nr} is zero, the number of the buffer in the current - window is returned. - When window {nr} doesn't exist, -1 is returned. - Example: > - :echo "The file in the current window is " . bufname(winbufnr(0)) -< - Can also be used as a |method|: > - FindWindow()->winbufnr()->bufname() -< - *wincol()* -wincol() The result is a Number, which is the virtual column of the - cursor in the window. This is counting screen cells from the - left side of the window. The leftmost column is one. - - *windowsversion()* -windowsversion() - The result is a String. For MS-Windows it indicates the OS - version. E.g, Windows 10 is "10.0", Windows 8 is "6.2", - Windows XP is "5.1". For non-MS-Windows systems the result is - an empty string. - -winheight({nr}) *winheight()* - The result is a Number, which is the height of window {nr}. - {nr} can be the window number or the |window-ID|. - When {nr} is zero, the height of the current window is - returned. When window {nr} doesn't exist, -1 is returned. - An existing window always has a height of zero or more. - This excludes any window toolbar line. - Examples: > - :echo "The current window has " . winheight(0) . " lines." - -< Can also be used as a |method|: > - GetWinid()->winheight() -< -winlayout([{tabnr}]) *winlayout()* - The result is a nested List containing the layout of windows - in a tabpage. - - Without {tabnr} use the current tabpage, otherwise the tabpage - with number {tabnr}. If the tabpage {tabnr} is not found, - returns an empty list. - - For a leaf window, it returns: - ['leaf', {winid}] - For horizontally split windows, which form a column, it - returns: - ['col', [{nested list of windows}]] - For vertically split windows, which form a row, it returns: - ['row', [{nested list of windows}]] - - Example: > - " Only one window in the tab page - :echo winlayout() - ['leaf', 1000] - " Two horizontally split windows - :echo winlayout() - ['col', [['leaf', 1000], ['leaf', 1001]]] - " The second tab page, with three horizontally split - " windows, with two vertically split windows in the - " middle window - :echo winlayout(2) - ['col', [['leaf', 1002], ['row', [['leaf', 1003], - ['leaf', 1001]]], ['leaf', 1000]]] -< - Can also be used as a |method|: > - GetTabnr()->winlayout() -< - *winline()* -winline() The result is a Number, which is the screen line of the cursor - in the window. This is counting screen lines from the top of - the window. The first line is one. - If the cursor was moved the view on the file will be updated - first, this may cause a scroll. - - *winnr()* -winnr([{arg}]) The result is a Number, which is the number of the current - window. The top window has number 1. - Returns zero for a popup window. - - The optional argument {arg} supports the following values: - $ the number of the last window (the window - count). - # the number of the last accessed window (where - |CTRL-W_p| goes to). If there is no previous - window or it is in another tab page 0 is - returned. - {N}j the number of the Nth window below the - current window (where |CTRL-W_j| goes to). - {N}k the number of the Nth window above the current - window (where |CTRL-W_k| goes to). - {N}h the number of the Nth window left of the - current window (where |CTRL-W_h| goes to). - {N}l the number of the Nth window right of the - current window (where |CTRL-W_l| goes to). - The number can be used with |CTRL-W_w| and ":wincmd w" - |:wincmd|. - Also see |tabpagewinnr()| and |win_getid()|. - Examples: > - let window_count = winnr('$') - let prev_window = winnr('#') - let wnum = winnr('3k') - -< Can also be used as a |method|: > - GetWinval()->winnr() -< - *winrestcmd()* -winrestcmd() Returns a sequence of |:resize| commands that should restore - the current window sizes. Only works properly when no windows - are opened or closed and the current window and tab page is - unchanged. - Example: > - :let cmd = winrestcmd() - :call MessWithWindowSizes() - :exe cmd -< - *winrestview()* -winrestview({dict}) - Uses the |Dictionary| returned by |winsaveview()| to restore - the view of the current window. - Note: The {dict} does not have to contain all values, that are - returned by |winsaveview()|. If values are missing, those - settings won't be restored. So you can use: > - :call winrestview({'curswant': 4}) -< - This will only set the curswant value (the column the cursor - wants to move on vertical movements) of the cursor to column 5 - (yes, that is 5), while all other settings will remain the - same. This is useful, if you set the cursor position manually. - - If you have changed the values the result is unpredictable. - If the window size changed the result won't be the same. - - Can also be used as a |method|: > - GetView()->winrestview() -< - *winsaveview()* -winsaveview() Returns a |Dictionary| that contains information to restore - the view of the current window. Use |winrestview()| to - restore the view. - This is useful if you have a mapping that jumps around in the - buffer and you want to go back to the original view. - This does not save fold information. Use the 'foldenable' - option to temporarily switch off folding, so that folds are - not opened when moving around. This may have side effects. - The return value includes: - lnum cursor line number - col cursor column (Note: the first column - zero, as opposed to what getpos() - returns) - coladd cursor column offset for 'virtualedit' - curswant column for vertical movement - topline first line in the window - topfill filler lines, only in diff mode - leftcol first column displayed; only used when - 'wrap' is off - skipcol columns skipped - Note that no option values are saved. - - -winwidth({nr}) *winwidth()* - The result is a Number, which is the width of window {nr}. - {nr} can be the window number or the |window-ID|. - When {nr} is zero, the width of the current window is - returned. When window {nr} doesn't exist, -1 is returned. - An existing window always has a width of zero or more. - Examples: > - :echo "The current window has " . winwidth(0) . " columns." - :if winwidth(0) <= 50 - : 50 wincmd | - :endif -< For getting the terminal or screen size, see the 'columns' - option. - - Can also be used as a |method|: > - GetWinid()->winwidth() - -wordcount() *wordcount()* - The result is a dictionary of byte/chars/word statistics for - the current buffer. This is the same info as provided by - |g_CTRL-G| - The return value includes: - bytes Number of bytes in the buffer - chars Number of chars in the buffer - words Number of words in the buffer - cursor_bytes Number of bytes before cursor position - (not in Visual mode) - cursor_chars Number of chars before cursor position - (not in Visual mode) - cursor_words Number of words before cursor position - (not in Visual mode) - visual_bytes Number of bytes visually selected - (only in Visual mode) - visual_chars Number of chars visually selected - (only in Visual mode) - visual_words Number of words visually selected - (only in Visual mode) - - - *writefile()* -writefile({object}, {fname} [, {flags}]) - When {object} is a |List| write it to file {fname}. Each list - item is separated with a NL. Each list item must be a String - or Number. - When {flags} contains "b" then binary mode is used: There will - not be a NL after the last list item. An empty item at the - end does cause the last line in the file to end in a NL. - - When {object} is a |Blob| write the bytes to file {fname} - unmodified. - - When {flags} contains "a" then append mode is used, lines are - appended to the file: > - :call writefile(["foo"], "event.log", "a") - :call writefile(["bar"], "event.log", "a") -< - When {flags} contains "S" fsync() call is not used, with "s" - it is used, 'fsync' option applies by default. No fsync() - means that writefile() will finish faster, but writes may be - left in OS buffers and not yet written to disk. Such changes - will disappear if system crashes before OS does writing. - - All NL characters are replaced with a NUL character. - Inserting CR characters needs to be done before passing {list} - to writefile(). - An existing file is overwritten, if possible. - When the write fails -1 is returned, otherwise 0. There is an - error message if the file can't be created or when writing - fails. - Also see |readfile()|. - To copy a file byte for byte: > - :let fl = readfile("foo", "b") - :call writefile(fl, "foocopy", "b") - -< Can also be used as a |method|: > - GetText()->writefile("thefile") - -xor({expr}, {expr}) *xor()* - Bitwise XOR on the two arguments. The arguments are converted - to a number. A List, Dict or Float argument causes an error. - Example: > - :let bits = xor(bits, 0x80) -< - Can also be used as a |method|: > - :let bits = bits->xor(0x80) -< - - *string-match* -Matching a pattern in a String - -A regexp pattern as explained at |pattern| is normally used to find a match in -the buffer lines. When a pattern is used to find a match in a String, almost -everything works in the same way. The difference is that a String is handled -like it is one line. When it contains a "\n" character, this is not seen as a -line break for the pattern. It can be matched with a "\n" in the pattern, or -with ".". Example: > - :let a = "aaaa\nxxxx" - :echo matchstr(a, "..\n..") - aa - xx - :echo matchstr(a, "a.x") - a - x - -Don't forget that "^" will only match at the first character of the String and -"$" at the last character of the string. They don't match after or before a -"\n". +The alphabetic list of all builtin functions and details are in a separate +help file: |builtin-functions|. ============================================================================== 5. Defining functions *user-function* @@ -11063,7 +2434,7 @@ See |:verbose-cmd| for more information. command, use line breaks instead of |:bar|: > :exe "func Foo()\necho 'foo'\nendfunc" < - *:delf* *:delfunction* *E130* *E131* *E933* + *:delf* *:delfunction* *E131* *E933* :delf[unction][!] {name} Delete function {name}. {name} can also be a |Dictionary| entry that is a @@ -11160,9 +2531,9 @@ Example: > : echohl Title : echo a:title : echohl None - : echo a:0 . " items:" + : echo a:0 .. " items:" : for s in a:000 - : echon ' ' . s + : echon ' ' .. s : endfor :endfunction @@ -11201,7 +2572,7 @@ This function can then be called with: > this works: *function-range-example* > :function Mynumber(arg) - : echo line(".") . " " . a:arg + : echo line(".") .. " " .. a:arg :endfunction :1,5call Mynumber(getline(".")) < @@ -11212,7 +2583,7 @@ This function can then be called with: > Example of a function that handles the range itself: > :function Cont() range - : execute (a:firstline + 1) . "," . a:lastline . 's/^/\t\\ ' + : execute (a:firstline + 1) .. "," .. a:lastline .. 's/^/\t\\ ' :endfunction :4,8call Cont() < @@ -11374,7 +2745,7 @@ This does NOT work: > This cannot be used to add an item to a |List|. This cannot be used to set a byte in a String. You can do that like this: > - :let var = var[0:2] . 'X' . var[4:] + :let var = var[0:2] .. 'X' .. var[4:] < When {var-name} is a |Blob| then {idx} can be the length of the blob, in which case one byte is appended. @@ -11436,7 +2807,7 @@ This does NOT work: > is just like using the |:set| command: both the local value and the global value are changed. Example: > - :let &path = &path . ',/usr/local/include' + :let &path = &path .. ',/usr/local/include' :let &{option-name} .= {expr1} For a string option: Append {expr1} to the value. @@ -11693,6 +3064,8 @@ text... :if {expr1} *:if* *:end* *:endif* *:en* *E171* *E579* *E580* :en[dif] Execute the commands until the next matching ":else" or ":endif" if {expr1} evaluates to non-zero. + Although the short forms work, it is recommended to + always use `:endif` to avoid confusion. From Vim version 4.5 until 5.0, every Ex command in between the ":if" and ":endif" is ignored. These two @@ -12290,7 +3663,7 @@ exception most recently caught as long it is not finished. :function! Caught() : if v:exception != "" - : echo 'Caught "' . v:exception . '" in ' . v:throwpoint + : echo 'Caught "' .. v:exception .. '" in ' .. v:throwpoint : else : echo 'Nothing caught' : endif @@ -12693,8 +4066,8 @@ a script in order to catch unexpected things. :catch /^Vim:Interrupt$/ : echo "Script interrupted" :catch /.*/ - : echo "Internal error (" . v:exception . ")" - : echo " - occurred at " . v:throwpoint + : echo "Internal error (" .. v:exception .. ")" + : echo " - occurred at " .. v:throwpoint :endtry :" end of script @@ -12890,7 +4263,7 @@ parentheses can be cut out from |v:exception| with the ":substitute" command. :function! CheckRange(a, func) : if a:a < 0 - : throw "EXCEPT:MATHERR:RANGE(" . a:func . ")" + : throw "EXCEPT:MATHERR:RANGE(" .. a:func .. ")" : endif :endfunction : @@ -12917,7 +4290,7 @@ parentheses can be cut out from |v:exception| with the ":substitute" command. : try : execute "write" fnameescape(a:file) : catch /^Vim(write):/ - : throw "EXCEPT:IO(" . getcwd() . ", " . a:file . "):WRITEERR" + : throw "EXCEPT:IO(" .. getcwd() .. ", " .. a:file .. "):WRITEERR" : endtry :endfunction : @@ -12936,9 +4309,9 @@ parentheses can be cut out from |v:exception| with the ":substitute" command. : let dir = substitute(v:exception, '.*(\(.\+\),\s*.\+).*', '\1', "") : let file = substitute(v:exception, '.*(.\+,\s*\(.\+\)).*', '\1', "") : if file !~ '^/' - : let file = dir . "/" . file + : let file = dir .. "/" .. file : endif - : echo 'I/O error for "' . file . '"' + : echo 'I/O error for "' .. file .. '"' : :catch /^EXCEPT/ : echo "Unspecified error" @@ -13006,7 +4379,7 @@ clauses, however, is executed. : echo "inner finally" : endtry :catch - : echo 'outer catch-all caught "' . v:exception . '"' + : echo 'outer catch-all caught "' .. v:exception .. '"' : finally : echo "outer finally" :endtry @@ -13068,7 +4441,7 @@ Printing in Binary ~ : let n = a:nr : let r = "" : while n - : let r = '01'[n % 2] . r + : let r = '01'[n % 2] .. r : let n = n / 2 : endwhile : return r @@ -13079,7 +4452,7 @@ Printing in Binary ~ :func String2Bin(str) : let out = '' : for ix in range(strlen(a:str)) - : let out = out . '-' . Nr2Bin(char2nr(a:str[ix])) + : let out = out .. '-' .. Nr2Bin(char2nr(a:str[ix])) : endfor : return out[1:] :endfunc @@ -13153,7 +4526,7 @@ code can be used: > unlet scriptnames_output ============================================================================== -The sandbox *eval-sandbox* *sandbox* *E48* +The sandbox *eval-sandbox* *sandbox* The 'foldexpr', 'formatexpr', 'includeexpr', 'indentexpr', 'statusline' and 'foldtext' options may be evaluated in a sandbox. This means that you are @@ -13162,6 +4535,7 @@ safety for when these options are set from a modeline. It is also used when the command from a tags file is executed and for CTRL-R = in the command line. The sandbox is also used for the |:sandbox| command. + *E48* These items are not allowed in the sandbox: - changing the buffer text - defining or changing mapping, autocommands, user commands diff --git a/runtime/doc/filetype.txt b/runtime/doc/filetype.txt index bbbe71ec3a..5486c87af9 100644 --- a/runtime/doc/filetype.txt +++ b/runtime/doc/filetype.txt @@ -24,12 +24,21 @@ Each time a new or existing file is edited, Vim will try to recognize the type of the file and set the 'filetype' option. This will trigger the FileType event, which can be used to set the syntax highlighting, set options, etc. -Detail: The ":filetype on" command will load this file: +Detail: The ":filetype on" command will load these files: + $VIMRUNTIME/filetype.lua $VIMRUNTIME/filetype.vim - This file is a Vim script that defines autocommands for the - BufNewFile and BufRead events. If the file type is not found by the - name, the file $VIMRUNTIME/scripts.vim is used to detect it from the - contents of the file. + filetype.lua creates an autocommand that fires for all BufNewFile and + BufRead events. It tries to detect the filetype based off of the + file's extension or name. + + filetype.vim is a Vim script that defines autocommands for the + BufNewFile and BufRead events. In contrast to filetype.lua, this + file creates separate BufNewFile and BufRead events for each filetype + pattern. + + If the file type is not found by the name, the file + $VIMRUNTIME/scripts.vim is used to detect it from the contents of the + file. When the GUI is running or will start soon, the |menu.vim| script is also sourced. See |'go-M'| about avoiding that. @@ -122,14 +131,15 @@ shell script: "#!/bin/csh". argument was used. *filetype-overrule* -When the same extension is used for two filetypes, Vim tries to guess what -kind of file it is. This doesn't always work. A number of global variables -can be used to overrule the filetype used for certain extensions: +When the same extension is used for multiple filetypes, Vim tries to guess +what kind of file it is. This doesn't always work. A number of global +variables can be used to overrule the filetype used for certain extensions: file name variable ~ *.asa g:filetype_asa |ft-aspvbs-syntax| |ft-aspperl-syntax| *.asm g:asmsyntax |ft-asm-syntax| *.asp g:filetype_asp |ft-aspvbs-syntax| |ft-aspperl-syntax| + *.bas g:filetype_bas |ft-basic-syntax| *.fs g:filetype_fs |ft-forth-syntax| *.i g:filetype_i |ft-progress-syntax| *.inc g:filetype_inc @@ -149,9 +159,10 @@ is used. The default value is set like this: > This means that the contents of compressed files are not inspected. *new-filetype* -If a file type that you want to use is not detected yet, there are four ways -to add it. In any way, it's better not to modify the $VIMRUNTIME/filetype.vim -file. It will be overwritten when installing a new version of Vim. +If a file type that you want to use is not detected yet, there are a few ways +to add it. In any way, it's better not to modify the $VIMRUNTIME/filetype.lua +or $VIMRUNTIME/filetype.vim files. They will be overwritten when installing a +new version of Nvim. A. If you want to overrule all default file type checks. This works by writing one file for each filetype. The disadvantage is that @@ -191,7 +202,7 @@ B. If you want to detect your file after the default file type checks. au BufRead,BufNewFile * if &ft == 'pascal' | set ft=mypascal | endif -C. If your file type can be detected by the file name. +C. If your file type can be detected by the file name or extension. 1. Create your user runtime directory. You would normally use the first item of the 'runtimepath' option. Example for Unix: > :!mkdir -p ~/.config/nvim @@ -206,9 +217,38 @@ C. If your file type can be detected by the file name. au! BufRead,BufNewFile *.mine setfiletype mine au! BufRead,BufNewFile *.xyz setfiletype drawing augroup END -< Write this file as "filetype.vim" in your user runtime directory. For +< + Write this file as "filetype.vim" in your user runtime directory. For example, for Unix: > :w ~/.config/nvim/filetype.vim +< + Alternatively, create a file called "filetype.lua" that adds new + filetypes. + Example: > + vim.filetype.add({ + extension = { + foo = "fooscript", + }, + filename = { + [".foorc"] = "foorc", + }, + pattern = { + [".*/etc/foo/.*%.conf"] = "foorc", + }, + }) +< + See |vim.filetype.add()|. + *g:do_filetype_lua* + For now, Lua filetype detection is opt-in. You can enable it by adding + the following to your |init.vim|: > + let g:do_filetype_lua = 1 +< *g:did_load_filetypes* + In either case, the builtin filetype detection provided by Nvim can be + disabled by setting the did_load_filetypes global variable. If this + variable exists, $VIMRUNTIME/filetype.vim will not run. + Example: > + " Disable filetype.vim + let g:did_load_filetypes = 1 < 3. To use the new filetype detection you must restart Vim. @@ -245,9 +285,9 @@ D. If your filetype can only be detected by inspecting the contents of the $VIMRUNTIME/scripts.vim. *remove-filetype* -If a file type is detected that is wrong for you, install a filetype.vim or -scripts.vim to catch it (see above). You can set 'filetype' to a non-existing -name to avoid that it will be set later anyway: > +If a file type is detected that is wrong for you, install a filetype.lua, +filetype.vim or scripts.vim to catch it (see above). You can set 'filetype' to +a non-existing name to avoid that it will be set later anyway: > :set filetype=ignored If you are setting up a system with many users, and you don't want each user @@ -314,12 +354,12 @@ define yourself. There are a few ways to avoid this: You need to define your own mapping before the plugin is loaded (before editing a file of that type). The plugin will then skip installing the default mapping. - *no_mail_maps* + *no_mail_maps* *g:no_mail_maps* 3. Disable defining mappings for a specific filetype by setting a variable, which contains the name of the filetype. For the "mail" filetype this would be: > :let no_mail_maps = 1 -< *no_plugin_maps* +< *no_plugin_maps* *g:no_plugin_maps* 4. Disable defining mappings for all filetypes by setting a variable: > :let no_plugin_maps = 1 < diff --git a/runtime/doc/fold.txt b/runtime/doc/fold.txt index 80c934d13b..9e3d78faff 100644 --- a/runtime/doc/fold.txt +++ b/runtime/doc/fold.txt @@ -497,11 +497,13 @@ Note the use of backslashes to avoid some characters to be interpreted by the :function MyFoldText() : let line = getline(v:foldstart) : let sub = substitute(line, '/\*\|\*/\|{{{\d\=', '', 'g') - : return v:folddashes . sub + : return v:folddashes .. sub :endfunction Evaluating 'foldtext' is done in the |sandbox|. The current window is set to -the window that displays the line. Errors are ignored. +the window that displays the line. + +Errors are ignored. For debugging set the 'debug' option to "throw". The default value is |foldtext()|. This returns a reasonable text for most types of folding. If you don't like it, you can specify your own 'foldtext' diff --git a/runtime/doc/ft_raku.txt b/runtime/doc/ft_raku.txt index 00b140ee9c..3d1179ed4e 100644 --- a/runtime/doc/ft_raku.txt +++ b/runtime/doc/ft_raku.txt @@ -47,20 +47,20 @@ Numbers, subscripts and superscripts are available with 's' and 'S': But some don't come defined by default. Those are digraph definitions you can add in your ~/.vimrc file. > - exec 'digraph \\ '.char2nr('∖') - exec 'digraph \< '.char2nr('≼') - exec 'digraph \> '.char2nr('≽') - exec 'digraph (L '.char2nr('⊈') - exec 'digraph )L '.char2nr('⊉') - exec 'digraph (/ '.char2nr('⊄') - exec 'digraph )/ '.char2nr('⊅') - exec 'digraph )/ '.char2nr('⊅') - exec 'digraph U+ '.char2nr('⊎') - exec 'digraph 0- '.char2nr('⊖') + exec 'digraph \\ ' .. char2nr('∖') + exec 'digraph \< ' .. char2nr('≼') + exec 'digraph \> ' .. char2nr('≽') + exec 'digraph (L ' .. char2nr('⊈') + exec 'digraph )L ' .. char2nr('⊉') + exec 'digraph (/ ' .. char2nr('⊄') + exec 'digraph )/ ' .. char2nr('⊅') + exec 'digraph )/ ' .. char2nr('⊅') + exec 'digraph U+ ' .. char2nr('⊎') + exec 'digraph 0- ' .. char2nr('⊖') " Euler's constant - exec 'digraph ne '.char2nr('𝑒') + exec 'digraph ne ' .. char2nr('𝑒') " Raku's atomic operations marker - exec 'digraph @@ '.char2nr('⚛') + exec 'digraph @@ ' .. char2nr('⚛') Alternatively, you can write Insert mode abbreviations that convert ASCII- based operators into their single-character Unicode equivalent. > diff --git a/runtime/doc/ft_rust.txt b/runtime/doc/ft_rust.txt index ff2e0ca56f..5c8782ec7a 100644 --- a/runtime/doc/ft_rust.txt +++ b/runtime/doc/ft_rust.txt @@ -26,7 +26,7 @@ behavior of the plugin. g:rustc_path~ Set this option to the path to rustc for use in the |:RustRun| and |:RustExpand| commands. If unset, "rustc" will be located in $PATH: > - let g:rustc_path = $HOME."/bin/rustc" + let g:rustc_path = $HOME .. "/bin/rustc" < *g:rustc_makeprg_no_percent* @@ -87,7 +87,7 @@ g:rust_bang_comment_leader~ g:ftplugin_rust_source_path~ Set this option to a path that should be prepended to 'path' for Rust source files: > - let g:ftplugin_rust_source_path = $HOME.'/dev/rust' + let g:ftplugin_rust_source_path = $HOME .. '/dev/rust' < *g:rustfmt_command* diff --git a/runtime/doc/ft_sql.txt b/runtime/doc/ft_sql.txt index 53a99a9e1d..fccbbce17f 100644 --- a/runtime/doc/ft_sql.txt +++ b/runtime/doc/ft_sql.txt @@ -109,8 +109,8 @@ must be configurable. The filetype plugin attempts to define many of the standard objects, plus many additional ones. In order to make this as flexible as possible, you can override the list of objects from within your |vimrc| with the following: > - let g:ftplugin_sql_objects = 'function,procedure,event,table,trigger' . - \ ',schema,service,publication,database,datatype,domain' . + let g:ftplugin_sql_objects = 'function,procedure,event,table,trigger' .. + \ ',schema,service,publication,database,datatype,domain' .. \ ',index,subscription,synchronization,view,variable' The following |Normal| mode and |Visual| mode maps have been created which use @@ -131,10 +131,10 @@ Repeatedly pressing ]} will cycle through each of these create statements: > create index i1 on t1 (c1); The default setting for g:ftplugin_sql_objects is: > - let g:ftplugin_sql_objects = 'function,procedure,event,' . - \ '\\(existing\\\\|global\\s\\+temporary\\s\\+\\)\\\{,1}' . - \ 'table,trigger' . - \ ',schema,service,publication,database,datatype,domain' . + let g:ftplugin_sql_objects = 'function,procedure,event,' .. + \ '\\(existing\\\\|global\\s\\+temporary\\s\\+\\)\\\{,1}' .. + \ 'table,trigger' .. + \ ',schema,service,publication,database,datatype,domain' .. \ ',index,subscription,synchronization,view,variable' The above will also handle these cases: > diff --git a/runtime/doc/help.txt b/runtime/doc/help.txt index 173d3c0cdf..b97c9a2e3f 100644 --- a/runtime/doc/help.txt +++ b/runtime/doc/help.txt @@ -129,6 +129,7 @@ Advanced editing ~ |diff.txt| working with two to eight versions of the same file |autocmd.txt| automatically executing commands on an event |eval.txt| expression evaluation, conditional commands +|builtin.txt| builtin functions |fold.txt| hide (fold) ranges of lines |lua.txt| Lua API |api.txt| Nvim API via RPC, Lua and VimL diff --git a/runtime/doc/helphelp.txt b/runtime/doc/helphelp.txt index 9cc7d063a8..569995d319 100644 --- a/runtime/doc/helphelp.txt +++ b/runtime/doc/helphelp.txt @@ -219,7 +219,7 @@ command: > < *:helpt* *:helptags* - *E154* *E150* *E151* *E152* *E153* *E670* *E856* + *E150* *E151* *E152* *E153* *E154* *E670* *E856* :helpt[ags] [++t] {dir} Generate the help tags file(s) for directory {dir}. When {dir} is ALL then all "doc" directories in @@ -358,7 +358,7 @@ When referring to a Vim option in the help file, place the option name between two single quotes, eg. 'statusline' When referring to any other technical term, such as a filename or function -parameter, surround it in backticks (`), eg. `~/.path/to/init.vim`. +parameter, surround it in backticks, eg. `~/.path/to/init.vim`. HIGHLIGHTING diff --git a/runtime/doc/if_cscop.txt b/runtime/doc/if_cscop.txt index f05b3bb8ed..8947aefc1b 100644 --- a/runtime/doc/if_cscop.txt +++ b/runtime/doc/if_cscop.txt @@ -40,7 +40,7 @@ See |cscope-usage| to get started. ============================================================================== Cscope commands *cscope-commands* - *:cscope* *:cs* *:scs* *:scscope* *E259* *E262* *E561* *E560* + *:cscope* *:cs* *:scs* *:scscope* *E259* *E262* *E560* *E561* All cscope commands are accessed through suboptions to the cscope commands. `:cscope` or `:cs` is the main command `:scscope` or `:scs` does the same and splits the window diff --git a/runtime/doc/if_pyth.txt b/runtime/doc/if_pyth.txt index fea47de220..9b434e61d7 100644 --- a/runtime/doc/if_pyth.txt +++ b/runtime/doc/if_pyth.txt @@ -1,10 +1,10 @@ *if_pyth.txt* Nvim - VIM REFERENCE MANUAL by Paul Moore + NVIM REFERENCE MANUAL -The Python Interface to Vim *if_pyth* *python* *Python* +The Python Interface to NVim *if_pyth* *python* *Python* See |provider-python| for more information. @@ -134,7 +134,7 @@ Instead, put the Python command in a function and call that function: Note that "EOF" must be at the start of the line. ============================================================================== -The vim module *python-vim* *python2* +The vim module *python-vim* Python code gets all of its access to vim (with one exception - see |python-output| below) via the "vim" module. The vim module implements two @@ -322,14 +322,13 @@ Output from Python *python-output* supported, and may cause the program to crash. This should probably be fixed. - *python2-directory* *python3-directory* *pythonx-directory* + *python3-directory* *pythonx-directory* Python 'runtimepath' handling *python-special-path* In python vim.VIM_SPECIAL_PATH special directory is used as a replacement for the list of paths found in 'runtimepath': with this directory in sys.path and vim.path_hooks in sys.path_hooks python will try to load module from -{rtp}/python2 (or python3) and {rtp}/pythonx (for both python versions) for -each {rtp} found in 'runtimepath'. +{rtp}/python3 and {rtp}/pythonx for each {rtp} found in 'runtimepath'. Implementation is similar to the following, but written in C: > @@ -401,8 +400,8 @@ vim._get_paths *python-_get_paths* hook. You should not rely on this method being present in future versions, but can use it for debugging. - It returns a list of {rtp}/python2 (or {rtp}/python3) and - {rtp}/pythonx directories for each {rtp} in 'runtimepath'. + It returns a list of {rtp}/python3 and {rtp}/pythonx + directories for each {rtp} in 'runtimepath'. ============================================================================== Buffer objects *python-buffer* @@ -590,6 +589,11 @@ functions to evaluate Python expressions and pass their values to Vim script. ============================================================================== Python 3 *python3* +As Python 3 is the only supported version in Nvim, "python" is synonymous +with "python3" in the current version. However, code that aims to support older +versions of Neovim, as well as Vim, should prefer to use "python3" +variants explicitly if Python 3 is required. + *:py3* *:python3* :[range]py3 {stmt} :[range]py3 << [endmarker] @@ -619,31 +623,26 @@ Raising SystemExit exception in python isn't endorsed way to quit vim, use: > :py vim.command("qall!") < *has-python* -You can test what Python version is available with: > - if has('python') - echo 'there is Python 2.x' +You can test if Python is available with: > + if has('pythonx') + echo 'there is Python' endif if has('python3') echo 'there is Python 3.x' endif +Python 2 is no longer supported. Thus `has('python')` always returns +zero for backwards compatibility reasons. + ============================================================================== Python X *python_x* *pythonx* -Because most python code can be written so that it works with Python 2.6+ and -Python 3, the pyx* functions and commands have been written. They work the -same as the Python 2 and 3 variants, but select the Python version using the -'pyxversion' setting. - -Set 'pyxversion' in your |vimrc| to prefer Python 2 or Python 3 for Python -commands. Changing this setting at runtime risks losing the state of plugins -(such as initialization). - -If you want to use a module, you can put it in the {rtp}/pythonx directory. -See |pythonx-directory|. +The "pythonx" and "pyx" prefixes were introduced for python code which +works with Python 2.6+ and Python 3. As Nvim only supports Python 3, +all these commands are now synonymous to their "python3" equivalents. *:pyx* *:pythonx* -`:pyx` and `:pythonx` work similar to `:python`. To check if `:pyx` works: > +`:pyx` and `:pythonx` work the same as `:python3`. To check if `:pyx` works: > :pyx print("Hello") To see what version of Python is being used: > @@ -651,33 +650,15 @@ To see what version of Python is being used: > :pyx print(sys.version) < *:pyxfile* *python_x-special-comments* -`:pyxfile` works similar to `:pyfile`. But you can add a "shebang" comment to -force Vim to use `:pyfile` or `:py3file`: > - #!/any string/python2 " Shebang. Must be the first line of the file. - #!/any string/python3 " Shebang. Must be the first line of the file. - # requires python 2.x " Maximum lines depend on 'modelines'. - # requires python 3.x " Maximum lines depend on 'modelines'. -Unlike normal modelines, the bottom of the file is not checked. -If none of them are found, the 'pyxversion' option is used. - *W20* *W21* -If Vim does not support the selected Python version a silent message will be -printed. Use `:messages` to read them. +`:pyxfile` works the same as `:py3file`. *:pyxdo* -`:pyxdo` works similar to `:pydo`. +`:pyxdo` works the same as `:py3do`. *has-pythonx* -To check if pyx* functions and commands are available: > +To check if `pyx*` functions and commands are available: > if has('pythonx') - echo 'pyx* commands are available. (Python ' . &pyx . ')' - endif - -If you prefer Python 2 and want to fallback to Python 3, set 'pyxversion' -explicitly in your |.vimrc|. Example: > - if has('python') - set pyx=2 - elseif has('python3') - set pyx=3 + echo 'pyx* commands are available. (Python ' .. &pyx .. ')' endif ============================================================================== diff --git a/runtime/doc/indent.txt b/runtime/doc/indent.txt index 1b42092616..d0d4ddad32 100644 --- a/runtime/doc/indent.txt +++ b/runtime/doc/indent.txt @@ -771,6 +771,15 @@ You can set the indent for the first line after <script> and <style> "auto" auto indent (same indent as the blocktag) "inc" auto indent + one indent step +You can set the indent for attributes after an open <tag line: > + + :let g:html_indent_attribute = 1 +< + VALUE MEANING ~ + 1 auto indent, one indent step more than <tag + 2 auto indent, two indent steps (default) + > 2 auto indent, more indent steps + Many tags increase the indent for what follows per default (see "Add Indent Tags" in the script). You can add further tags with: > @@ -872,7 +881,7 @@ For example, with N = 1, this will give: *PHP_outdentphpescape* To indent PHP escape tags as the surrounding non-PHP code (only affects the PHP escape tags): > -:let g:PHP_outdentphpescape = 0 + :let g:PHP_outdentphpescape = 0 ------------- *PHP_removeCRwhenUnix* @@ -1199,7 +1208,7 @@ comments will be indented according to the correctly indented code. VIM *ft-vim-indent* - + *g:vim_indent_cont* For indenting Vim scripts there is one variable that specifies the amount of indent for a continuation line, a line that starts with a backslash: > diff --git a/runtime/doc/index.txt b/runtime/doc/index.txt index d8689e2c65..d02ab1b759 100644 --- a/runtime/doc/index.txt +++ b/runtime/doc/index.txt @@ -353,6 +353,7 @@ tag char note action in Normal mode ~ register x] |Y| ["x]Y yank N lines [into register x]; synonym for "yy" + Note: Mapped to "y$" by default. |default-mappings| |ZZ| ZZ write if buffer changed and close window |ZQ| ZQ close window without writing |[| [{char} square bracket command (see |[| below) @@ -923,7 +924,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 @@ -986,6 +989,8 @@ tag command note action in Visual mode ~ |v_i{| i{ same as iB |v_i}| i} same as iB |v_o| o move cursor to other corner of area +|v_p| p replace highlighted area with register + contents; deleted text in unnamed register |v_r| r 2 replace highlighted area with a character |v_s| s 2 delete highlighted area and start insert |v_u| u 2 make highlighted area lowercase diff --git a/runtime/doc/insert.txt b/runtime/doc/insert.txt index fd1d0f8ea6..39682a2ab2 100644 --- a/runtime/doc/insert.txt +++ b/runtime/doc/insert.txt @@ -76,6 +76,8 @@ CTRL-U Delete all entered characters before the cursor in the current line. If there are no newly entered characters and 'backspace' is not empty, delete all characters before the cursor in the current line. + If C-indenting is enabled the indent will be adjusted if the + line becomes blank. See |i_backspacing| about joining lines. *i_CTRL-U-default* By default, sets a new undo point before deleting. @@ -828,7 +830,7 @@ space is preferred). Maximum line length is 510 bytes. For an example, imagine the 'thesaurus' file has a line like this: > angry furious mad enraged -<Placing the cursor after the letters "ang" and typing CTRL-X CTRL-T would +Placing the cursor after the letters "ang" and typing CTRL-X CTRL-T would complete the word "angry"; subsequent presses would change the word to "furious", "mad" etc. @@ -840,7 +842,7 @@ https://github.com/vim/vim/issues/629#issuecomment-443293282 Unpack thesaurus_pkg.zip, put the thesaurus.txt file somewhere, e.g. ~/.vim/thesaurus/english.txt, and the 'thesaurus' option to this file name. - + Completing keywords with 'thesaurusfunc' *compl-thesaurusfunc* If the 'thesaurusfunc' option is set, then the user specified function is @@ -862,7 +864,7 @@ Groß): > else let res = [] let h = '' - for l in split(system('aiksaurus '.shellescape(a:base)), '\n') + for l in split(system('aiksaurus ' .. shellescape(a:base)), '\n') if l[:3] == '=== ' let h = substitute(l[4:], ' =*$', '', '') elseif l[0] =~ '\a' @@ -1197,7 +1199,7 @@ An example that completes the names of the months: > " find months matching with "a:base" let res = [] for m in split("Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec") - if m =~ '^' . a:base + if m =~ '^' .. a:base call add(res, m) endif endfor @@ -1219,7 +1221,7 @@ The same, but now pretending searching for matches is slow: > else " find months matching with "a:base" for m in split("Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec") - if m =~ '^' . a:base + if m =~ '^' .. a:base call complete_add(m) endif sleep 300m " simulate searching for next match @@ -1878,6 +1880,9 @@ When 'autoindent' is on, the indent for a new line is obtained from the previous line. When 'smartindent' or 'cindent' is on, the indent for a line is automatically adjusted for C programs. +'formatoptions' can be set to copy the comment leader when opening a new +line. + 'textwidth' can be set to the maximum width for a line. When a line becomes too long when appending characters a line break is automatically inserted. diff --git a/runtime/doc/intro.txt b/runtime/doc/intro.txt index 0e0156ac6b..54999fa163 100644 --- a/runtime/doc/intro.txt +++ b/runtime/doc/intro.txt @@ -322,7 +322,6 @@ notation meaning equivalent decimal value(s) ~ <Bar> vertical bar | 124 *<Bar>* <Del> delete 127 <CSI> command sequence intro ALT-Esc 155 *<CSI>* -<xCSI> CSI when typed in the GUI *<xCSI>* <EOL> end-of-line (can be <CR>, <NL> or <CR><NL>, depends on system and 'fileformat') *<EOL>* diff --git a/runtime/doc/lsp-extension.txt b/runtime/doc/lsp-extension.txt index d13303ada6..6e9ad940c7 100644 --- a/runtime/doc/lsp-extension.txt +++ b/runtime/doc/lsp-extension.txt @@ -60,7 +60,7 @@ The example will: return nil end local dir = bufname - -- Just in case our algo is buggy, don't infinite loop. + -- Just in case our algorithm is buggy, don't infinite loop. for _ = 1, 100 do local did_change dir, did_change = dirname(dir) diff --git a/runtime/doc/lsp.txt b/runtime/doc/lsp.txt index a3929aeab9..d717759444 100644 --- a/runtime/doc/lsp.txt +++ b/runtime/doc/lsp.txt @@ -214,7 +214,7 @@ For |lsp-request|, each |lsp-handler| has this signature: > request, a table with information about the error is sent. Otherwise, it is `nil`. See |lsp-response|. {result} (Result | Params | nil) - When the language server is able to succesfully + When the language server is able to successfully complete a request, this contains the `result` key of the response. See |lsp-response|. {ctx} (table) @@ -236,7 +236,7 @@ For |lsp-request|, each |lsp-handler| has this signature: > {config} (table) Configuration for the handler. - Each handler can define it's own configuration + Each handler can define its own configuration table that allows users to customize the behavior of a particular handler. @@ -274,7 +274,7 @@ For |lsp-notification|, each |lsp-handler| has this signature: > {config} (table) Configuration for the handler. - Each handler can define it's own configuration + Each handler can define its own configuration table that allows users to customize the behavior of a particular handler. @@ -369,7 +369,7 @@ Handlers can be set by: For example: > vim.lsp.start_client { - ..., -- Other configuration ommitted. + ..., -- Other configuration omitted. handlers = { ["textDocument/definition"] = my_custom_server_definition }, @@ -394,6 +394,9 @@ in the following order: 2. Handler defined in |vim.lsp.start_client()|, if any. 3. Handler defined in |vim.lsp.handlers|, if any. + *vim.lsp.log_levels* +Log levels are defined in |vim.log.levels| + VIM.LSP.PROTOCOL *vim.lsp.protocol* @@ -444,7 +447,7 @@ LspCodeLens |nvim_buf_set_extmark()|. LspCodeLensSeparator *hl-LspCodeLensSeparator* - Used to color the seperator between two or more code lens. + Used to color the separator between two or more code lens. *lsp-highlight-signature* @@ -746,8 +749,8 @@ omnifunc({findstart}, {base}) *vim.lsp.omnifunc()* set_log_level({level}) *vim.lsp.set_log_level()* Sets the global log level for LSP logging. - Levels by name: "trace", "debug", "info", "warn", "error" - Level numbers begin with "trace" at 0 + Levels by name: "TRACE", "DEBUG", "INFO", "WARN", "ERROR" + Level numbers begin with "TRACE" at 0 Use `lsp.log_levels` for reverse lookup. @@ -835,10 +838,10 @@ start_client({config}) *vim.lsp.start_client()* throws an error. `code` is a number describing the error. Other arguments may be passed depending on the error - kind. See |vim.lsp.client_errors| for - possible errors. Use - `vim.lsp.client_errors[code]` to get - human-friendly name. + kind. See |vim.lsp.rpc.client_errors| + for possible errors. Use + `vim.lsp.rpc.client_errors[code]` to + get human-friendly name. {before_init} Callback with parameters (initialize_params, config) invoked before the LSP "initialize" phase, @@ -1005,7 +1008,7 @@ document_highlight() *vim.lsp.buf.document_highlight()* Send request to the server to resolve document highlights for the current text document position. This request can be triggered by a key mapping or by events such as `CursorHold` , - eg: + e.g.: > autocmd CursorHold <buffer> lua vim.lsp.buf.document_highlight() autocmd CursorHoldI <buffer> lua vim.lsp.buf.document_highlight() @@ -1021,11 +1024,12 @@ document_symbol() *vim.lsp.buf.document_symbol()* Lists all symbols in the current buffer in the quickfix window. -execute_command({command}) *vim.lsp.buf.execute_command()* +execute_command({command_params}) *vim.lsp.buf.execute_command()* Executes an LSP server command. Parameters: ~ - {command} A valid `ExecuteCommandParams` object + {command_params} table A valid `ExecuteCommandParams` + object See also: ~ https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_executeCommand @@ -1221,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, @@ -1301,7 +1305,7 @@ hover({_}, {result}, {ctx}, {config}) *vim.lsp.handlers.hover()* {config} table Configuration table. • border: (default=nil) • Add borders to the floating window - • See |vim.api.nvim_open_win()| + • See |nvim_open_win()| *vim.lsp.handlers.signature_help()* signature_help({_}, {result}, {ctx}, {config}) @@ -1328,7 +1332,7 @@ signature_help({_}, {result}, {ctx}, {config}) Lua module: vim.lsp.util *lsp-util* *vim.lsp.util.apply_text_document_edit()* -apply_text_document_edit({text_document_edit}, {index}) +apply_text_document_edit({text_document_edit}, {index}, {offset_encoding}) Applies a `TextDocumentEdit` , which is a list of changes to a single document. @@ -1348,18 +1352,19 @@ apply_text_edits({text_edits}, {bufnr}, {offset_encoding}) Parameters: ~ {text_edits} table list of `TextEdit` objects {bufnr} number Buffer id - {offset_encoding} string utf-8|utf-16|utf-32|nil defaults - to encoding of first client of `bufnr` + {offset_encoding} string utf-8|utf-16|utf-32 defaults to + encoding of first client of `bufnr` See also: ~ https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textEdit *vim.lsp.util.apply_workspace_edit()* -apply_workspace_edit({workspace_edit}) +apply_workspace_edit({workspace_edit}, {offset_encoding}) Applies a `WorkspaceEdit` . Parameters: ~ - {workspace_edit} (table) `WorkspaceEdit` + {workspace_edit} table `WorkspaceEdit` + {offset_encoding} string utf-8|utf-16|utf-32 (required) buf_clear_references({bufnr}) *vim.lsp.util.buf_clear_references()* Removes document highlights from a buffer. @@ -1376,9 +1381,7 @@ buf_highlight_references({bufnr}, {references}, {offset_encoding}) {references} table List of `DocumentHighlight` objects to highlight {offset_encoding} string One of "utf-8", "utf-16", - "utf-32", or nil. Defaults to - `offset_encoding` of first client of - `bufnr` + "utf-32". See also: ~ https://microsoft.github.io/language-server-protocol/specifications/specification-3-17/#documentHighlight @@ -1467,16 +1470,19 @@ get_effective_tabstop({bufnr}) *vim.lsp.util.get_effective_tabstop()* See also: ~ |softtabstop| -jump_to_location({location}) *vim.lsp.util.jump_to_location()* + *vim.lsp.util.jump_to_location()* +jump_to_location({location}, {offset_encoding}) Jumps to a location. Parameters: ~ - {location} ( `Location` | `LocationLink` ) + {location} table ( `Location` | `LocationLink` ) + {offset_encoding} string utf-8|utf-16|utf-32 (required) Return: ~ `true` if the jump succeeded -locations_to_items({locations}) *vim.lsp.util.locations_to_items()* + *vim.lsp.util.locations_to_items()* +locations_to_items({locations}, {offset_encoding}) Returns the items with the byte position calculated correctly and in sorted order, for display in quickfix and location lists. @@ -1485,8 +1491,10 @@ locations_to_items({locations}) *vim.lsp.util.locations_to_items()* |setqflist()| or |setloclist()|. Parameters: ~ - {locations} (table) list of `Location` s or - `LocationLink` s + {locations} table list of `Location` s or + `LocationLink` s + {offset_encoding} string offset_encoding for locations + utf-8|utf-16|utf-32 Return: ~ (table) list of items diff --git a/runtime/doc/lua.txt b/runtime/doc/lua.txt index 97062e5986..98af84e1cb 100644 --- a/runtime/doc/lua.txt +++ b/runtime/doc/lua.txt @@ -17,9 +17,9 @@ an idea of what lurks beneath: > :lua print(vim.inspect(package.loaded)) Nvim includes a "standard library" |lua-stdlib| for Lua. It complements the -"editor stdlib" (|functions| and Ex commands) and the |API|, all of which can -be used from Lua code. A good overview of using Lua in neovim is given by -https://github.com/nanotee/nvim-lua-guide. +"editor stdlib" (|builtin-functions| and Ex commands) and the |API|, all of +which can be used from Lua code. A good overview of using Lua in neovim is +given by https://github.com/nanotee/nvim-lua-guide. Module conflicts are resolved by "last wins". For example if both of these are on 'runtimepath': @@ -249,13 +249,15 @@ arguments separated by " " (space) instead of "\t" (tab). *:lua* :[range]lua {chunk} Executes Lua chunk {chunk}. - + if {chunk} starts with "=" the rest of the chunk is + evaluated as an expression and printed. `:lua =expr` + is equivalent to `:lua print(vim.inspect(expr))` Examples: > :lua vim.api.nvim_command('echo "Hello, Nvim!"') < To see the Lua version: > :lua print(_VERSION) < To see the LuaJIT version: > - :lua print(jit.version) + :lua =jit.version < *:lua-heredoc* :[range]lua << [endmarker] @@ -272,7 +274,7 @@ arguments separated by " " (space) instead of "\t" (tab). lua << EOF local linenr = vim.api.nvim_win_get_cursor(0)[1] local curline = vim.api.nvim_buf_get_lines( - 0, linenr, linenr + 1, false)[1] + 0, linenr - 1, linenr, false)[1] print(string.format("Current line [%d] has %d bytes", linenr, #curline)) EOF @@ -566,6 +568,26 @@ Example: TCP echo-server *tcp-server* end) print('TCP echo-server listening on port: '..server:getsockname().port) + +Multithreading *lua-loop-threading* + +Plugins can perform work in separate (os-level) threads using the threading +APIs in luv, for instance `vim.loop.new_thread`. Note that every thread +gets its own separate lua interpreter state, with no access to lua globals +in the main thread. Neither can the state of the editor (buffers, windows, +etc) be directly accessed from threads. + +A subset of the `vim.*` API is available in threads. This includes: + +- `vim.loop` with a separate event loop per thread. +- `vim.mpack` and `vim.json` (useful for serializing messages between threads) +- `require` in threads can use lua packages from the global |lua-package-path| +- `print()` and `vim.inspect` +- `vim.diff` +- most utility functions in `vim.*` for working with pure lua values + like `vim.split`, `vim.tbl_*`, `vim.list_*`, and so on. +- `vim.is_thread()` returns true from a non-main thread. + ------------------------------------------------------------------------------ VIM.HIGHLIGHT *lua-highlight* @@ -593,13 +615,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* @@ -791,9 +833,9 @@ vim.stricmp({a}, {b}) *vim.stricmp()* respectively. vim.str_utfindex({str}[, {index}]) *vim.str_utfindex()* - Convert byte index to UTF-32 and UTF-16 indicies. If {index} is not - supplied, the length of the string is used. All indicies are zero-based. - Returns two values: the UTF-32 and UTF-16 indicies respectively. + Convert byte index to UTF-32 and UTF-16 indices. If {index} is not + supplied, the length of the string is used. All indices are zero-based. + Returns two values: the UTF-32 and UTF-16 indices respectively. Embedded NUL bytes are treated as terminating the string. Invalid UTF-8 bytes, and embedded surrogates are counted as one code @@ -913,6 +955,15 @@ vim.types *vim.types* `vim.types.dictionary` will not change or that `vim.types` table will only contain values for these three types. + *log_levels* *vim.log.levels* +Log levels are one of the values defined in `vim.log.levels`: + + vim.log.levels.DEBUG + vim.log.levels.ERROR + vim.log.levels.INFO + vim.log.levels.TRACE + vim.log.levels.WARN + ------------------------------------------------------------------------------ LUA-VIMSCRIPT BRIDGE *lua-vimscript* @@ -1229,19 +1280,32 @@ inspect({object}, {options}) *vim.inspect()* https://github.com/kikito/inspect.lua https://github.com/mpeterv/vinspect -notify({msg}, {log_level}, {opts}) *vim.notify()* - Notification provider +notify({msg}, {level}, {opts}) *vim.notify()* + Display a notification to the user. - Without a runtime, writes to :Messages + This function can be overridden by plugins to display + notifications using a custom provider (such as the system + notification provider). By default, writes to |:messages|. Parameters: ~ - {msg} string Content of the notification to show to - the user - {log_level} number|nil enum from vim.log.levels - {opts} table|nil additional options (timeout, etc) + {msg} string Content of the notification to show to the + user. + {level} number|nil One of the values from + |vim.log.levels|. + {opts} table|nil Optional parameters. Unused by default. - See also: ~ - :help nvim_notify +notify_once({msg}, {level}, {opts}) *vim.notify_once()* + Display a notification only one time. + + Like |vim.notify()|, but subsequent calls with the same + message will not display a notification. + + Parameters: ~ + {msg} string Content of the notification to show to the + user. + {level} number|nil One of the values from + |vim.log.levels|. + {opts} table|nil Optional parameters. Unused by default. on_key({fn}, {ns_id}) *vim.on_key()* Adds Lua function {fn} with namespace id {ns_id} as a listener @@ -1305,6 +1369,18 @@ paste({lines}, {phase}) *vim.paste()* See also: ~ |paste| +pretty_print({...}) *vim.pretty_print()* + Prints given arguments in human-readable format. Example: > + -- Print highlight group Normal and store it's contents in a variable. + local hl_normal = vim.pretty_print(vim.api.nvim_get_hl_by_name("Normal", true)) +< + + Return: ~ + given arguments. + + See also: ~ + |vim.inspect()| + region({bufnr}, {pos1}, {pos2}, {regtype}, {inclusive}) *vim.region()* Get a table of lines with start, end columns for a region marked by two points @@ -1647,16 +1723,25 @@ validate({opt}) *vim.validate()* => error('arg1: expected even number, got 3') < + If multiple types are valid they can be given as a list. > + + vim.validate{arg1={{'foo'}, {'table', 'string'}}, arg2={'foo', {'table', 'string'}}} + => NOP (success) + + vim.validate{arg1={1, {'string', table'}}} + => error('arg1: expected string|table, got number') +< + Parameters: ~ - {opt} Map of parameter names to validations. Each key is - a parameter name; each value is a tuple in one of - these forms: + {opt} table of parameter names to validations. Each key + is a parameter name; each value is a tuple in one + of these forms: 1. (arg_value, type_name, optional) • arg_value: argument value - • type_name: string type name, one of: ("table", - "t", "string", "s", "number", "n", "boolean", - "b", "function", "f", "nil", "thread", - "userdata") + • type_name: string|table type name, one of: + ("table", "t", "string", "s", "number", "n", + "boolean", "b", "function", "f", "nil", + "thread", "userdata") or list of them. • optional: (optional) boolean, if true, `nil` is valid @@ -1718,6 +1803,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. @@ -1741,6 +1833,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 @@ -1760,4 +1868,155 @@ select({items}, {opts}, {on_choice}) *vim.ui.select()* 1-based index of `item` within `item` . `nil` if the user aborted the dialog. + +============================================================================== +Lua module: filetype *lua-filetype* + +add({filetypes}) *vim.filetype.add()* + Add new filetype mappings. + + Filetype mappings can be added either by extension or by + filename (either the "tail" or the full file path). The full + file path is checked first, followed by the file name. If a + match is not found using the filename, then the filename is + matched against the list of patterns (sorted by priority) + until a match is found. Lastly, if pattern matching does not + find a filetype, then the file extension is used. + + The filetype can be either a string (in which case it is used + as the filetype directly) or a function. If a function, it + takes the full path and buffer number of the file as arguments + (along with captures from the matched pattern, if any) and + should return a string that will be used as the buffer's + filetype. + + Filename patterns can specify an optional priority to resolve + cases when a file path matches multiple patterns. Higher + priorities are matched first. When omitted, the priority + defaults to 0. + + See $VIMRUNTIME/lua/vim/filetype.lua for more examples. + + Note that Lua filetype detection is only enabled when + |g:do_filetype_lua| is set to 1. + + Example: > + + vim.filetype.add({ + extension = { + foo = "fooscript", + bar = function(path, bufnr) + if some_condition() then + return "barscript" + end + return "bar" + end, + }, + filename = { + [".foorc"] = "toml", + ["/etc/foo/config"] = "toml", + }, + pattern = { + [".*‍/etc/foo/.*"] = "fooscript", + -- Using an optional priority + [".*‍/etc/foo/.*%.conf"] = { "dosini", { priority = 10 } }, + ["README.(%a+)$"] = function(path, bufnr, ext) + if ext == "md" then + return "markdown" + elseif ext == "rst" then + return "rst" + end + end, + }, + }) +< + + Parameters: ~ + {filetypes} table A table containing new filetype maps + (see example). + +match({name}, {bufnr}) *vim.filetype.match()* + Set the filetype for the given buffer from a file name. + + Parameters: ~ + {name} string File name (can be an absolute or relative + path) + {bufnr} number|nil The buffer to set the filetype for. + Defaults to the current buffer. + + +============================================================================== +Lua module: keymap *lua-keymap* + +del({modes}, {lhs}, {opts}) *vim.keymap.del()* + Remove an existing mapping. Examples: > + + vim.keymap.del('n', 'lhs') + + vim.keymap.del({'n', 'i', 'v'}, '<leader>w', { buffer = 5 }) +< + + Parameters: ~ + {opts} table A table of optional arguments: + • buffer: (number or boolean) Remove a mapping + from the given buffer. When "true" or 0, use the + current buffer. + + See also: ~ + |vim.keymap.set()| + +set({mode}, {lhs}, {rhs}, {opts}) *vim.keymap.set()* + Add a new |mapping|. Examples: > + + -- Can add mapping to Lua functions + vim.keymap.set('n', 'lhs', function() print("real lua function") end) + + -- Can use it to map multiple modes + vim.keymap.set({'n', 'v'}, '<leader>lr', vim.lsp.buf.references, { buffer=true }) + + -- Can add mapping for specific buffer + vim.keymap.set('n', '<leader>w', "<cmd>w<cr>", { silent = true, buffer = 5 }) + + -- Expr mappings + vim.keymap.set('i', '<Tab>', function() + return vim.fn.pumvisible() == 1 and "<C-n>" or "<Tab>" + end, { expr = true }) + -- <Plug> mappings + vim.keymap.set('n', '[%', '<Plug>(MatchitNormalMultiBackward)') +< + + Note that in a mapping like: > + + vim.keymap.set('n', 'asdf', require('jkl').my_fun) +< + + the require('jkl') gets evaluated during this call in order to + access the function. If you want to avoid this cost at startup + you can wrap it in a function, for example: > + + vim.keymap.set('n', 'asdf', function() return require('jkl').my_fun() end) +< + + Parameters: ~ + {mode} string|table Same mode short names as + |nvim_set_keymap()|. Can also be list of modes to + create mapping on multiple modes. + {lhs} string Left-hand side |{lhs}| of the mapping. + {rhs} string|function Right-hand side |{rhs}| of the + mapping. Can also be a Lua function. + {opts} table A table of |:map-arguments| such as + "silent". In addition to the options listed in + |nvim_set_keymap()|, this table also accepts the + following keys: + • replace_keycodes: (boolean, default true) When + both this and expr is "true", + |nvim_replace_termcodes()| is applied to the + result of Lua expr maps. + • remap: (boolean) Make the mapping recursive. + This is the inverse of the "noremap" option from + |nvim_set_keymap()|. Default `false` . + + See also: ~ + |nvim_set_keymap()| + vim:tw=78:ts=8:ft=help:norl: diff --git a/runtime/doc/map.txt b/runtime/doc/map.txt index 238ef39bd3..8715c3231c 100644 --- a/runtime/doc/map.txt +++ b/runtime/doc/map.txt @@ -65,6 +65,8 @@ modes. where the map command applies. Disallow mapping of {rhs}, to avoid nested and recursive mappings. Often used to redefine a command. + Note: A mapping whose {lhs} starts with <Plug> is + always applied even if mapping is disallowed. :unm[ap] {lhs} |mapmode-nvo| *:unm* *:unmap* @@ -82,8 +84,7 @@ modes. map command applies. The mapping may remain defined for other modes where it applies. It also works when {lhs} matches the {rhs} of a - mapping. This is for when when an abbreviation - applied. + mapping. This is for when an abbreviation applied. Note: Trailing spaces are included in the {lhs}. This unmap does NOT work: > :map @@ foo @@ -245,7 +246,7 @@ go through the main loop (e.g. to update the display), return "\<Ignore>". This is similar to "nothing" but makes Vim return from the loop that waits for input. -Also, keep in mind that the expression may be evaluated when looking for +Keep in mind that the expression may be evaluated when looking for typeahead, before the previous command has been executed. For example: > func StoreColumn() let g:column = col('.') @@ -286,7 +287,7 @@ Here is an example that inserts a list number that increases: > func ListItem() let g:counter += 1 - return g:counter . '. ' + return g:counter .. '. ' endfunc func ListReset() @@ -503,7 +504,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 @@ -1218,7 +1221,7 @@ scripts. *:command-verbose* When 'verbose' is non-zero, listing a command will also display where it was -last defined. Example: > +last defined and any completion argument. Example: > :verbose command TOhtml < Name Args Range Complete Definition ~ @@ -1242,13 +1245,17 @@ See |:verbose-cmd| for more information. :delc[ommand] {cmd} *:delc* *:delcommand* *E184* Delete the user-defined command {cmd}. +:delc[ommand] -buffer {cmd} *E1237* + Delete the user-defined command {cmd} that was defined + for the current buffer. + :comc[lear] *:comc* *:comclear* Delete all user-defined commands. Command attributes ~ - -User-defined commands are treated by Vim just like any other Ex commands. They + *command-attributes* +User-defined commands are treated by Nvim just like any other Ex commands. They can have arguments, or have a range specified. Arguments are subject to completion as filenames, buffers, etc. Exactly how this works depends upon the command's attributes, which are specified when the command is defined. @@ -1333,6 +1340,8 @@ completion can be enabled: -complete=custom,{func} custom completion, defined via {func} -complete=customlist,{func} custom completion, defined via {func} +If you specify completion while there is nothing to complete (-nargs=0, the +default) then you get error *E1208* . Note: That some completion methods might expand environment variables. @@ -1435,6 +1444,9 @@ There are some special cases as well: -register The first argument to the command can be an optional register name (like :del, :put, :yank). -buffer The command will only be available in the current buffer. + -keepscript Do not use the location of where the user command was + defined for verbose messages, use the location of where + the user command was invoked. In the cases of the -count and -register attributes, if the optional argument is supplied, it is removed from the argument list and is available to the @@ -1479,12 +1491,12 @@ The valid escape sequences are Examples: > command! -nargs=+ -complete=file MyEdit \ for f in expand(<q-args>, 0, 1) | - \ exe '<mods> split ' . f | + \ exe '<mods> split ' .. f | \ endfor function! SpecialEdit(files, mods) for f in expand(a:files, 0, 1) - exe a:mods . ' split ' . f + exe a:mods .. ' split ' .. f endfor endfunction command! -nargs=+ -complete=file Sedit @@ -1560,7 +1572,7 @@ This will invoke: > : let i = 0 : while i < argc() : if filereadable(argv(i)) - : execute "e " . argv(i) + : execute "e " .. argv(i) : execute a:command : endif : let i = i + 1 diff --git a/runtime/doc/mbyte.txt b/runtime/doc/mbyte.txt index 3bbf36c642..2aa49cee1e 100644 --- a/runtime/doc/mbyte.txt +++ b/runtime/doc/mbyte.txt @@ -489,8 +489,8 @@ Use the RPM or port for your system. window specific to the input method. -USING XIM *multibyte-input* *E284* *E286* *E287* *E288* - *E285* *E289* +USING XIM *multibyte-input* *E284* *E285* *E286* *E287* + *E288* *E289* Note that Display and Input are independent. It is possible to see your language even though you have no input method for it. But when your Display diff --git a/runtime/doc/message.txt b/runtime/doc/message.txt index 6fbd9ec922..fa1bc6f7da 100644 --- a/runtime/doc/message.txt +++ b/runtime/doc/message.txt @@ -27,8 +27,7 @@ depends on the 'shortmess' option. Clear messages, keeping only the {count} most recent ones. -The number of remembered messages is fixed at 20 for the tiny version and 200 -for other versions. +The number of remembered messages is fixed at 200. *g<* The "g<" command can be used to see the last page of previous command output. @@ -112,7 +111,8 @@ wiped out a buffer which contains a mark or is referenced in another way. *E95* > Buffer with this name already exists -You cannot have two buffers with the same name. +You cannot have two buffers with exactly the same name. This includes the +path leading to the file. *E72* > Close error on swap file @@ -513,10 +513,10 @@ If you type "gq", it will execute this mapping, which will call "gq" again. *E22* > Scripts nested too deep -Scripts can be read with the "-s" command-line argument and with the ":source" -command. The script can then again read another script. This can continue -for about 14 levels. When more nesting is done, Vim assumes that there is a -recursive loop somewhere and stops with this error message. +Scripts can be read with the "-s" command-line argument and with the +`:source!` command. The script can then again read another script. This can +continue for about 14 levels. When more nesting is done, Vim assumes that +there is a recursive loop and stops with this error message. *E300* > Swap file already exists (symlink attack?) @@ -686,6 +686,7 @@ Ex command or function was given an invalid argument. Or |jobstart()| or Trailing characters An argument was given to an Ex command that does not permit one. +Or the argument has invalid characters and has not been recognized. *E477* *E478* > No ! allowed diff --git a/runtime/doc/motion.txt b/runtime/doc/motion.txt index c473244827..20033bd76a 100644 --- a/runtime/doc/motion.txt +++ b/runtime/doc/motion.txt @@ -993,7 +993,7 @@ These commands are not marks themselves, but jump to a mark: :let lnum = line(".") :keepjumps normal gg :call SetLastChange() - :keepjumps exe "normal " . lnum . "G" + :keepjumps exe "normal " .. lnum .. "G" < Note that ":keepjumps" must be used for every command. When invoking a function the commands in that function diff --git a/runtime/doc/nvim_terminal_emulator.txt b/runtime/doc/nvim_terminal_emulator.txt index e1ae138d90..f322764ecf 100644 --- a/runtime/doc/nvim_terminal_emulator.txt +++ b/runtime/doc/nvim_terminal_emulator.txt @@ -90,7 +90,7 @@ Mouse input has the following behavior: - If another window is clicked, terminal focus will be lost and nvim will jump to the clicked window - If the mouse wheel is used while the mouse is positioned in another window, - the terminal wont lose focus and the hovered window will be scrolled. + the terminal won't lose focus and the hovered window will be scrolled. ============================================================================== Configuration *terminal-config* @@ -162,12 +162,11 @@ command name, for example: > This opens two windows: gdb window A terminal window in which "gdb vim" is executed. Here you - can directly interact with gdb. The buffer name is "!gdb". + can directly interact with gdb. program window A terminal window for the executed program. When "run" is used in gdb the program I/O will happen in this window, so - that it does not interfere with controlling gdb. The buffer - name is "gdb program". + that it does not interfere with controlling gdb. The current window is used to show the source code. When gdb pauses the source file location will be displayed, if possible. A sign is used to @@ -428,7 +427,7 @@ When 'background' is "dark": hi debugBreakpoint term=reverse ctermbg=red guibg=red -Shorcuts *termdebug_shortcuts* +Shortcuts *termdebug_shortcuts* You can define your own shortcuts (mappings) to control gdb, that can work in any window, using the TermDebugSendCommand() function. Example: > diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index c929178f5a..82214a2527 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -20,9 +20,13 @@ achieve special effects. These options come in three forms: 1. Setting options *set-option* *E764* *:se* *:set* -:se[t] Show all options that differ from their default value. +:se[t][!] Show all options that differ from their default value. + When [!] is present every option is on a separate + line. -:se[t] all Show all options. +:se[t][!] all Show all options. + When [!] is present every option is on a separate + line. *E518* *E519* :se[t] {option}? Show value of {option}. @@ -235,7 +239,7 @@ happens when the buffer is not loaded, but they are lost when the buffer is wiped out |:bwipe|. *:setl* *:setlocal* -:setl[ocal] ... Like ":set" but set only the value local to the +:setl[ocal][!] ... Like ":set" but set only the value local to the current buffer or window. Not all options have a local value. If the option does not have a local value the global value is set. @@ -257,7 +261,7 @@ wiped out |:bwipe|. {option}, so that the global value will be used. *:setg* *:setglobal* -:setg[lobal] ... Like ":set" but set only the global value for a local +:setg[lobal][!] ... Like ":set" but set only the global value for a local option without changing the local value. When displaying an option, the global value is shown. With the "all" argument: display global values for all @@ -304,7 +308,7 @@ value to the local value, it doesn't switch back to using the global value This will make the local value of 'path' empty, so that the global value is used. Thus it does the same as: > :setlocal path= -Note: In the future more global options can be made global-local. Using +Note: In the future more global options can be made |global-local|. Using ":setlocal" on a global option might work differently then. @@ -686,10 +690,10 @@ A jump table for the options with a short description can be found at |Q_op|. Write the contents of the file, if it has been modified, on each `:next`, `:rewind`, `:last`, `:first`, `:previous`, `:stop`, `:suspend`, `:tag`, `:!`, `:make`, CTRL-] and CTRL-^ command; and when - a :buffer, CTRL-O, CTRL-I, '{A-Z0-9}, or `{A-Z0-9} command takes one + a `:buffer`, CTRL-O, CTRL-I, '{A-Z0-9}, or `{A-Z0-9} command takes one to another file. A buffer is not written if it becomes hidden, e.g. when 'bufhidden' is - set to "hide" and `:next` is used + set to "hide" and `:next` is used. Note that for some commands the 'autowrite' option is not used, see 'autowriteall' for that. Some buffers will not be written, specifically when 'buftype' is @@ -893,7 +897,7 @@ A jump table for the options with a short description can be found at |Q_op|. If you like to keep a lot of backups, you could use a BufWritePre autocommand to change 'backupext' just before writing the file to include a timestamp. > - :au BufWritePre * let &bex = '-' . strftime("%Y%b%d%X") . '~' + :au BufWritePre * let &bex = '-' .. strftime("%Y%b%d%X") .. '~' < Use 'backupdir' to put the backup in a different directory. *'backupskip'* *'bsk'* @@ -916,7 +920,7 @@ A jump table for the options with a short description can be found at |Q_op|. Note that environment variables are not expanded. If you want to use $HOME you must expand it explicitly, e.g.: > - :let backupskip = escape(expand('$HOME'), '\') . '/tmp/*' + :let backupskip = escape(expand('$HOME'), '\') .. '/tmp/*' < Note that the default also makes sure that "crontab -e" works (when a backup would be made by renaming the original file crontab won't see @@ -1181,7 +1185,7 @@ A jump table for the options with a short description can be found at |Q_op|. If the default value taken from $CDPATH is not what you want, include a modified version of the following command in your vimrc file to override it: > - :let &cdpath = ',' . substitute(substitute($CDPATH, '[, ]', '\\\0', 'g'), ':', ',', 'g') + :let &cdpath = ',' .. substitute(substitute($CDPATH, '[, ]', '\\\0', 'g'), ':', ',', 'g') < This option cannot be set from a |modeline| or in the |sandbox|, for security reasons. (parts of 'cdpath' can be passed to the shell to expand file names). @@ -1216,8 +1220,8 @@ A jump table for the options with a short description can be found at |Q_op|. preferred, because it is much faster. 'charconvert' is not used when reading stdin |--|, because there is no file to convert from. You will have to save the text in a file first. - The expression must return zero or an empty string for success, - non-zero for failure. + The expression must return zero, false or an empty string for success, + non-zero or true for failure. See |encoding-names| for possible encoding names. Additionally, names given in 'fileencodings' and 'fileencoding' are used. @@ -1228,8 +1232,8 @@ A jump table for the options with a short description can be found at |Q_op|. set charconvert=CharConvert() fun CharConvert() system("recode " - \ . v:charconvert_from . ".." . v:charconvert_to - \ . " <" . v:fname_in . " >" v:fname_out) + \ .. v:charconvert_from .. ".." .. v:charconvert_to + \ .. " <" .. v:fname_in .. " >" .. v:fname_out) return v:shell_error endfun < The related Vim variables are: @@ -2458,7 +2462,8 @@ A jump table for the options with a short description can be found at |Q_op|. < This is similar to the default, except that these characters will also be used when there is highlighting. - for "stl" and "stlnc" only single-byte values are supported. + For "stl" and "stlnc" single-byte and multibyte characters are + supported. But double-width characters are not supported. The highlighting used for these items: item highlight group ~ @@ -3035,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. @@ -3052,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|: > @@ -3615,7 +3620,7 @@ A jump table for the options with a short description can be found at |Q_op|. global Language to use for menu translation. Tells which file is loaded from the "lang" directory in 'runtimepath': > - "lang/menu_" . &langmenu . ".vim" + "lang/menu_" .. &langmenu .. ".vim" < (without the spaces). For example, to always use the Dutch menus, no matter what $LANG is set to: > :set langmenu=nl_NL.ISO_8859-1 @@ -4517,7 +4522,7 @@ A jump table for the options with a short description can be found at |Q_op|. < To use an environment variable, you probably need to replace the separator. Here is an example to append $INCL, in which directory names are separated with a semi-colon: > - :let &path = &path . "," . substitute($INCL, ';', ',', 'g') + :let &path = &path .. "," .. substitute($INCL, ';', ',', 'g') < Replace the ';' with a ':' or whatever separator is used. Note that this doesn't work when $INCL contains a comma or white space. @@ -4633,26 +4638,11 @@ A jump table for the options with a short description can be found at |Q_op|. nudged to fit on the screen. *'pyxversion'* *'pyx'* -'pyxversion' 'pyx' number (default depends on the build) +'pyxversion' 'pyx' number (default 3) global Specifies the python version used for pyx* functions and commands - |python_x|. The default value is as follows: - - |provider| installed Default ~ - |+python| and |+python3| 0 - only |+python| 2 - only |+python3| 3 - - Available values are 0, 2 and 3. - If 'pyxversion' is 0, it is set to 2 or 3 after the first execution of - any python2/3 commands or functions. E.g. `:py` sets to 2, and `:py3` - sets to 3. `:pyx` sets it to 3 if Python 3 is available, otherwise sets - to 2 if Python 2 is available. - See also: |has-pythonx| - - If only |+python| or |+python3| are available, - 'pyxversion' has no effect. The pyx* functions and commands are - always the same as the installed version. + |python_x|. As only Python 3 is supported, this always has the value + `3`. Setting any other value is an error. This option cannot be set from a |modeline| or in the |sandbox|, for security reasons. @@ -5131,7 +5121,8 @@ A jump table for the options with a short description can be found at |Q_op|. Don't include both "curdir" and "sesdir". When neither is included filenames are stored as absolute paths. - + If you leave out "options" many things won't work well after restoring + the session. *'shada'* *'sd'* *E526* *E527* *E528* 'shada' 'sd' string (Vim default for Win32: !,'100,<50,s10,h,rA:,rB: @@ -5471,7 +5462,7 @@ A jump table for the options with a short description can be found at |Q_op|. flag meaning when present ~ f use "(3 of 5)" instead of "(file 3 of 5)" i use "[noeol]" instead of "[Incomplete last line]" - l use "999L, 888C" instead of "999 lines, 888 characters" + l use "999L, 888B" instead of "999 lines, 888 bytes" m use "[+]" instead of "[Modified]" n use "[New]" instead of "[New File]" r use "[RO]" instead of "[readonly]" @@ -5656,7 +5647,7 @@ A jump table for the options with a short description can be found at |Q_op|. Note regarding 'orphaned signs': with signcolumn numbers higher than 1, deleting lines will also remove the associated signs automatically, in contrast to the default Vim behavior of keeping and grouping them. - This is done in order for the signcolumn appearence not appear weird + This is done in order for the signcolumn appearance not appear weird during line deletion. @@ -5915,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 @@ -6231,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. @@ -6264,10 +6255,11 @@ A jump table for the options with a short description can be found at |Q_op|. 'tabstop' 'ts' number (default 8) local to buffer Number of spaces that a <Tab> in the file counts for. Also see - |:retab| command, and 'softtabstop' option. + the |:retab| command, and the 'softtabstop' option. Note: Setting 'tabstop' to any other value than 8 can make your file - appear wrong in many places (e.g., when printing it). + appear wrong in many places, e.g., when printing it. + The value must be more than 0 and less than 10000. There are four main ways to use tabs in Vim: 1. Always keep 'tabstop' at 8, set 'softtabstop' and 'shiftwidth' to 4 @@ -6578,7 +6570,7 @@ A jump table for the options with a short description can be found at |Q_op|. This option cannot be set in a modeline when 'modelineexpr' is off. Example: > - :auto BufEnter * let &titlestring = hostname() . "/" . expand("%:p") + :auto BufEnter * let &titlestring = hostname() .. "/" .. expand("%:p") :set title titlestring=%<%F%=%l/%L-%P titlelen=70 < The value of 'titlelen' is used to align items in the middle or right of the available space. @@ -6724,8 +6716,8 @@ A jump table for the options with a short description can be found at |Q_op|. global When bigger than zero, Vim will give messages about what it is doing. Currently, these messages are given: - >= 1 When the shada file is read or written. - >= 2 When a file is ":source"'ed. + >= 1 Lua assignments to options,keymaps etc. + >= 2 When a file is ":source"'ed and when the shada file is read or written.. >= 3 UI info, terminal capabilities >= 4 Shell commands. >= 5 Every searched tags file and include file. @@ -6781,12 +6773,16 @@ A jump table for the options with a short description can be found at |Q_op|. *'virtualedit'* *'ve'* 'virtualedit' 've' string (default "") - global + global or local to window |global-local| A comma separated list of these words: block Allow virtual editing in Visual block mode. insert Allow virtual editing in Insert mode. all Allow virtual editing in all modes. onemore Allow the cursor to move just past the end of the line + none When used as the local value, do not allow virtual + editing even when the global value is set. When used + as the global value, "none" is the same as "". + NONE Alternative spelling of "none". Virtual editing means that the cursor can be positioned where there is no actual character. This can be halfway into a tab or beyond the end diff --git a/runtime/doc/pattern.txt b/runtime/doc/pattern.txt index dfed39dba6..c3bd5baff2 100644 --- a/runtime/doc/pattern.txt +++ b/runtime/doc/pattern.txt @@ -304,7 +304,7 @@ the pattern. ============================================================================== 2. The definition of a pattern *search-pattern* *pattern* *[pattern]* *regular-expression* *regexp* *Pattern* - *E76* *E383* *E476* + *E383* *E476* For starters, read chapter 27 of the user manual |usr_27.txt|. @@ -923,7 +923,7 @@ $ At end of pattern or in front of "\|", "\)" or "\n" ('magic' on): update the matches. This means Syntax highlighting quickly becomes wrong. Example, to highlight the line where the cursor currently is: > - :exe '/\%' . line(".") . 'l.*' + :exe '/\%' .. line(".") .. 'l.*' < When 'hlsearch' is set and you move the cursor around and make changes this will clearly show when the match is updated or not. @@ -939,7 +939,7 @@ $ At end of pattern or in front of "\|", "\)" or "\n" ('magic' on): update the matches. This means Syntax highlighting quickly becomes wrong. Example, to highlight the column where the cursor currently is: > - :exe '/\%' . col(".") . 'c' + :exe '/\%' .. col(".") .. 'c' < When 'hlsearch' is set and you move the cursor around and make changes this will clearly show when the match is updated or not. Example for matching a single byte in column 44: > @@ -1036,6 +1036,8 @@ match ASCII characters, as indicated by the range. \(\) A pattern enclosed by escaped parentheses. */\(* */\(\)* */\)* E.g., "\(^a\)" matches 'a' at the start of a line. + There can only be ten of these. You can use "\%(" to add more, but + not counting it as a sub-expression. *E51* *E54* *E55* *E872* *E873* \1 Matches the same string that was matched by */\1* *E65* @@ -1058,7 +1060,7 @@ x A single character, with no special meaning, matches itself \x A backslash followed by a single character, with no special meaning, is reserved for future expansions -[] (with 'nomagic': \[]) */[]* */\[]* */\_[]* */collection* +[] (with 'nomagic': \[]) */[]* */\[]* */\_[]* */collection* *E76* \_[] A collection. This is a sequence of characters enclosed in square brackets. It matches any single character in the collection. @@ -1419,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/pi_msgpack.txt b/runtime/doc/pi_msgpack.txt index 1dbd268038..801c56e49f 100644 --- a/runtime/doc/pi_msgpack.txt +++ b/runtime/doc/pi_msgpack.txt @@ -88,7 +88,7 @@ msgpack#type({msgpack-value}) *msgpack#type()* Returns name of the key in |v:msgpack_types| that represents {msgpack-value} type. Never returns zero: this function returns msgpack type which will be dumped by |msgpackdump()| should it receive - a list with singe {msgpack-value} as input. + a list with single {msgpack-value} as input. msgpack#deepcopy({msgpack-value}) *msgpack#deepcopy()* Like |deepcopy()|, but works correctly with |msgpack-special-dict| diff --git a/runtime/doc/pi_netrw.txt b/runtime/doc/pi_netrw.txt index 3ac61be6f2..8257152b11 100644 --- a/runtime/doc/pi_netrw.txt +++ b/runtime/doc/pi_netrw.txt @@ -968,7 +968,7 @@ itself: fun! NetReadFixup(method, line1, line2) if method == 3 " ftp (no <.netrc>) let fourblanklines= line2 - 3 - silent fourblanklines.",".line2."g/^\s*/d" + silent fourblanklines .. "," .. line2 .. "g/^\s*/d" endif endfunction endif @@ -1975,7 +1975,7 @@ To use this function, simply assign its output to |g:netrw_list_hide| option. > Example: let g:netrw_list_hide= netrw_gitignore#Hide('my_gitignore_file') Function can take additional files with git-ignore patterns. - Example: g:netrw_list_hide= netrw_gitignore#Hide() . '.*\.swp$' + Example: let g:netrw_list_hide= netrw_gitignore#Hide() .. '.*\.swp$' Combining 'netrw_gitignore#Hide' with custom patterns. < @@ -2824,7 +2824,7 @@ your browsing preferences. (see also: |netrw-settings|) Examples: let g:netrw_list_hide= '.*\.swp$' - let g:netrw_list_hide= netrw_gitignore#Hide().'.*\.swp$' + let g:netrw_list_hide= netrw_gitignore#Hide() .. '.*\.swp$' default: "" *g:netrw_localcopycmd* ="cp" Linux/Unix/MacOS/Cygwin diff --git a/runtime/doc/print.txt b/runtime/doc/print.txt index f54d0429a6..924fab175e 100644 --- a/runtime/doc/print.txt +++ b/runtime/doc/print.txt @@ -127,21 +127,21 @@ file: > system(['lpr'] + (empty(&printdevice)?[]:['-P', &printdevice]) + [v:fname_in]) - . delete(v:fname_in) + .. delete(v:fname_in) + v:shell_error On MS-Dos and MS-Windows machines the default is to copy the file to the currently specified printdevice: > system(['copy', v:fname_in, empty(&printdevice)?'LPT1':&printdevice]) - . delete(v:fname_in) + .. delete(v:fname_in) If you change this option, using a function is an easy way to avoid having to escape all the spaces. Example: > :set printexpr=PrintFile(v:fname_in) :function PrintFile(fname) - : call system("ghostview " . a:fname) + : call system("ghostview " .. a:fname) : call delete(a:fname) : return v:shell_error :endfunc diff --git a/runtime/doc/provider.txt b/runtime/doc/provider.txt index b785010699..9fd35f19c5 100644 --- a/runtime/doc/provider.txt +++ b/runtime/doc/provider.txt @@ -20,11 +20,12 @@ Run the |:checkhealth| command, and review the sections below. ============================================================================== Python integration *provider-python* -Nvim supports Python |remote-plugin|s and the Vim legacy |python2| and -|python3| interfaces (which are implemented as remote-plugins). +Nvim supports Python |remote-plugin|s and the Vim legacy |python3| and +|pythonx| interfaces (which are implemented as remote-plugins). Note: Only the Vim 7.3 legacy interface is supported, not later features such -as |python-bindeval| (Vim 7.4); use the Nvim API instead. +as |python-bindeval| (Vim 7.4); use the Nvim API instead. Python 2 is not +supported. PYTHON QUICKSTART ~ @@ -38,11 +39,6 @@ For Python 3 plugins: 2. Install the module (try "python" if "python3" is missing): > python3 -m pip install --user --upgrade pynvim -For Python 2 plugins: -1. Make sure Python 2.7 is available in your $PATH. -2. Install the module (try "python" if "python2" is missing): > - python2 -m pip install --user --upgrade pynvim - The pip `--upgrade` flag ensures that you get the latest version even if a previous version was already installed. @@ -56,22 +52,12 @@ If you run into problems, uninstall _both_ then install "pynvim" again: > PYTHON PROVIDER CONFIGURATION ~ - *g:python_host_prog* -Command to start Python 2 (executable, not directory). Setting this makes -startup faster. Useful for working with virtualenvs. Must be set before any -check for has("python2"). > - let g:python_host_prog = '/path/to/python' -< *g:python3_host_prog* Command to start Python 3 (executable, not directory). Setting this makes startup faster. Useful for working with virtualenvs. Must be set before any check for has("python3"). > let g:python3_host_prog = '/path/to/python3' < - *g:loaded_python_provider* -To disable Python 2 support: > - let g:loaded_python_provider = 0 -< *g:loaded_python3_provider* To disable Python 3 support: > let g:loaded_python3_provider = 0 @@ -81,8 +67,8 @@ PYTHON VIRTUALENVS ~ *python-virtualenv* If you plan to use per-project virtualenvs often, you should assign one virtualenv for Neovim and hard-code the interpreter path via -|g:python3_host_prog| (or |g:python_host_prog|) so that the "pynvim" package -is not required for each virtualenv. +|g:python3_host_prog| so that the "pynvim" package is not required +for each virtualenv. Example using pyenv: > pyenv install 3.4.4 diff --git a/runtime/doc/quickfix.txt b/runtime/doc/quickfix.txt index ba1da209f7..601384a71f 100644 --- a/runtime/doc/quickfix.txt +++ b/runtime/doc/quickfix.txt @@ -232,7 +232,7 @@ processing a quickfix or location list command, it will be aborted. ":qall!" |:qall|, except that Nvim exits non-zero or [count]. - *:cf* *:cfile* + *:cf* *:cfi* *:cfile* :cf[ile][!] [errorfile] Read the error file and jump to the first error. This is done automatically when Vim is started with the -q option. You can use this command when you @@ -341,7 +341,7 @@ processing a quickfix or location list command, it will be aborted. cursor position will not be changed. See |:cexpr| for more information. Example: > - :g/mypattern/caddexpr expand("%") . ":" . line(".") . ":" . getline(".") + :g/mypattern/caddexpr expand("%") .. ":" .. line(".") .. ":" .. getline(".") < *:lad* *:addd* *:laddexpr* :lad[dexpr] {expr} Same as ":caddexpr", except the location list for the @@ -641,6 +641,24 @@ quickfix window. If there already is a window for that file, it is used instead. If the buffer in the used window has changed, and the error is in another file, jumping to the error will fail. You will first have to make sure the window contains a buffer which can be abandoned. + +The following steps are used to find a window to open the file selected from +the quickfix window: +1. If 'switchbuf' contains "usetab", then find a window in any tabpage + (starting with the first tabpage) that has the selected file and jump to + it. +2. Otherwise find a window displaying the selected file in the current tab + page (starting with the window before the quickfix window) and use it. +3. Otherwise find a window displaying a normal buffer ('buftype' is empty) + starting with the window before the quickfix window. If a window is found, + open the file in that window. +4. If a usable window is not found and 'switchbuf' contains "uselast", then + open the file in the last used window. +5. Otherwise open the file in the window before the quickfix window. If there + is no previous window, then open the file in the next window. +6. If a usable window is not found in the above steps, then create a new + horizontally split window above the quickfix window and open the file. + *CTRL-W_<Enter>* *CTRL-W_<CR>* You can use CTRL-W <Enter> to open a new window and jump to the error there. @@ -650,7 +668,7 @@ FileType event (also see |qf.vim|). Then the BufReadPost event is triggered, using "quickfix" for the buffer name. This can be used to perform some action on the listed errors. Example: > au BufReadPost quickfix setlocal modifiable - \ | silent exe 'g/^/s//\=line(".")." "/' + \ | silent exe 'g/^/s//\=line(".") .. " "/' \ | setlocal nomodifiable This prepends the line number to each line. Note the use of "\=" in the substitute string of the ":s" command, which is used to evaluate an @@ -989,7 +1007,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 +1032,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 +1067,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 +1357,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 @@ -1446,7 +1476,7 @@ error message (line numbers are not part of the actual output): 4 Traceback (most recent call last): 5 File "unittests/dbfacadeTest.py", line 89, in testFoo 6 self.assertEquals(34, dtid) - 7 File "/usr/lib/python2.2/unittest.py", line 286, in + 7 File "/usr/lib/python3.8/unittest.py", line 286, in 8 failUnlessEqual 9 raise self.failureException, \ 10 AssertionError: 34 != 33 diff --git a/runtime/doc/quickref.txt b/runtime/doc/quickref.txt index af8301f1a0..e36eb2359f 100644 --- a/runtime/doc/quickref.txt +++ b/runtime/doc/quickref.txt @@ -356,6 +356,7 @@ In Insert or Command-line mode: |v_y| {visual}y yank the highlighted text into a register |yy| N yy yank N lines into a register |Y| N Y yank N lines into a register + Note: Mapped to "y$" by default. |default-mappings| |p| N p put a register after the cursor position (N times) |P| N P put a register before the cursor position (N times) |]p| N ]p like p, but adjust indent to current line 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/sign.txt b/runtime/doc/sign.txt index 5cfa06c33c..a2a5645baa 100644 --- a/runtime/doc/sign.txt +++ b/runtime/doc/sign.txt @@ -87,7 +87,7 @@ the delete is undone the sign does not move back. Here is an example that places a sign "piet", displayed with the text ">>", in line 23 of the current file: > :sign define piet text=>> texthl=Search - :exe ":sign place 2 line=23 name=piet file=" . expand("%:p") + :exe ":sign place 2 line=23 name=piet file=" .. expand("%:p") And here is the command to delete it again: > :sign unplace 2 diff --git a/runtime/doc/spell.txt b/runtime/doc/spell.txt index 03c00c8495..bc45b0e511 100644 --- a/runtime/doc/spell.txt +++ b/runtime/doc/spell.txt @@ -120,8 +120,8 @@ zuG Undo |zW| and |zG|, remove the word from the internal rare as this is a fairly uncommon command and all intuitive commands for this are already taken. If you want you can add mappings with e.g.: > - nnoremap z? :exe ':spellrare ' . expand('<cWORD>')<CR> - nnoremap z/ :exe ':spellrare! ' . expand('<cWORD>')<CR> + nnoremap z? :exe ':spellrare ' .. expand('<cWORD>')<CR> + nnoremap z/ :exe ':spellrare! ' .. expand('<cWORD>')<CR> < |:spellundo|, |zuw|, or |zuW| can be used to undo this. :spellr[rare]! {word} Add {word} as a rare word to the internal word diff --git a/runtime/doc/starting.txt b/runtime/doc/starting.txt index bb775ec884..1d3fa6c2ca 100644 --- a/runtime/doc/starting.txt +++ b/runtime/doc/starting.txt @@ -188,7 +188,7 @@ argument. changes and writing. -e *-e* *-E* --E Start Nvim in Ex mode |gQ|. +-E Start Nvim in Ex mode |gQ|, see |Ex-mode|. If stdin is not a TTY: -e reads/executes stdin as Ex commands. @@ -409,7 +409,12 @@ accordingly, proceeding as follows: 4. Setup |default-mappings| and |default-autocmds|. -5. Load user config (execute Ex commands from files, environment, …). +5. Enable filetype and indent plugins. + This does the same as the command: > + :runtime! ftplugin.vim indent.vim +< Skipped if the "-u NONE" command line argument was given. + +6. Load user config (execute Ex commands from files, environment, …). $VIMINIT environment variable is read as one Ex command line (separate multiple commands with '|' or <NL>). *config* *init.vim* *init.lua* *vimrc* *exrc* @@ -453,21 +458,19 @@ accordingly, proceeding as follows: - The file ".nvimrc" - The file ".exrc" -6. Enable filetype and indent plugins. - This does the same as the commands: > - :runtime! filetype.vim - :runtime! ftplugin.vim - :runtime! indent.vim -< Skipped if ":filetype … off" was called or if the "-u NONE" command - line argument was given. +7. Enable filetype detection. + This does the same as the command: > + :runtime! filetype.lua filetype.vim +< Skipped if ":filetype off" was called or if the "-u NONE" command line + argument was given. -7. Enable syntax highlighting. +8. Enable syntax highlighting. This does the same as the command: > :runtime! syntax/syntax.vim < Skipped if ":syntax off" was called or if the "-u NONE" command line argument was given. -8. Load the plugin scripts. *load-plugins* +9. Load the plugin scripts. *load-plugins* This does the same as the command: > :runtime! plugin/**/*.vim :runtime! plugin/**/*.lua @@ -497,21 +500,21 @@ accordingly, proceeding as follows: if packages have been found, but that should not add a directory ending in "after". -9. Set 'shellpipe' and 'shellredir' +10. Set 'shellpipe' and 'shellredir' The 'shellpipe' and 'shellredir' options are set according to the value of the 'shell' option, unless they have been set before. This means that Nvim will figure out the values of 'shellpipe' and 'shellredir' for you, unless you have set them yourself. -10. Set 'updatecount' to zero, if "-n" command argument used +11. Set 'updatecount' to zero, if "-n" command argument used -11. Set binary options if the |-b| flag was given. +12. Set binary options if the |-b| flag was given. -12. Read the |shada-file|. +13. Read the |shada-file|. -13. Read the quickfix file if the |-q| flag was given, or exit on failure. +14. Read the quickfix file if the |-q| flag was given, or exit on failure. -14. Open all windows +15. Open all windows When the |-o| flag was given, windows will be opened (but not displayed yet). When the |-p| flag was given, tab pages will be created (but not @@ -521,7 +524,7 @@ accordingly, proceeding as follows: Buffers for all windows will be loaded, without triggering |BufAdd| autocommands. -15. Execute startup commands +16. Execute startup commands If a |-t| flag was given, the tag is jumped to. Commands given with |-c| and |+cmd| are executed. If the 'insertmode' option is set, Insert mode is entered. @@ -797,7 +800,7 @@ resulting file, when executed with a ":source" command: After restoring the Session, the full filename of your current Session is available in the internal variable |v:this_session|. An example mapping: > - :nmap <F2> :wa<Bar>exe "mksession! " . v:this_session<CR>:so ~/sessions/ + :nmap <F2> :wa<Bar>exe "mksession! " .. v:this_session<CR>:so ~/sessions/ This saves the current Session, and starts off the command to load another. A session includes all tab pages, unless "tabpages" was removed from diff --git a/runtime/doc/syntax.txt b/runtime/doc/syntax.txt index 0fd481cd83..778f829a4e 100644 --- a/runtime/doc/syntax.txt +++ b/runtime/doc/syntax.txt @@ -616,7 +616,7 @@ evaluate to get a unique string to append to each ID used in a given document, so that the full IDs will be unique even when combined with other content in a larger HTML document. Example, to append _ and the buffer number to each ID: > - :let g:html_id_expr = '"_".bufnr("%")' + :let g:html_id_expr = '"_" .. bufnr("%")' < To append a string "_mystring" to the end of each ID: > @@ -920,12 +920,16 @@ in .../after/syntax/baan.vim (see |after-directory|). Eg: > BASIC *basic.vim* *vb.vim* *ft-basic-syntax* *ft-vb-syntax* -Both Visual Basic and "normal" basic use the extension ".bas". To detect +Both Visual Basic and "normal" BASIC use the extension ".bas". To detect which one should be used, Vim checks for the string "VB_Name" in the first five lines of the file. If it is not found, filetype will be "basic", otherwise "vb". Files with the ".frm" extension will always be seen as Visual Basic. +If the automatic detection doesn't work for you or you only edit, for +example, FreeBASIC files, use this in your startup vimrc: > + :let filetype_bas = "freebasic" + C *c.vim* *ft-c-syntax* @@ -1406,7 +1410,7 @@ add the following line to your startup file: > :let g:filetype_euphoria = "euphoria4" -Elixir and Euphoria share the *.ex file extension. If the filetype is +Elixir and Euphoria share the *.ex file extension. If the filetype is specifically set as Euphoria with the g:filetype_euphoria variable, or the file is determined to be Euphoria based on keywords in the file, then the filetype will be set as Euphoria. Otherwise, the filetype will default to @@ -1437,7 +1441,7 @@ The following file extensions are auto-detected as Elixir file types: *.ex, *.exs, *.eex, *.leex, *.lock -Elixir and Euphoria share the *.ex file extension. If the filetype is +Elixir and Euphoria share the *.ex file extension. If the filetype is specifically set as Euphoria with the g:filetype_euphoria variable, or the file is determined to be Euphoria based on keywords in the file, then the filetype will be set as Euphoria. Otherwise, the filetype will default to @@ -3546,8 +3550,8 @@ Do you want to draw with the mouse? Try the following: > :function! GetPixel() : let c = getline(".")[col(".") - 1] : echo c - : exe "noremap <LeftMouse> <LeftMouse>r".c - : exe "noremap <LeftDrag> <LeftMouse>r".c + : exe "noremap <LeftMouse> <LeftMouse>r" .. c + : exe "noremap <LeftDrag> <LeftMouse>r" .. c :endfunction :noremap <RightMouse> <LeftMouse>:call GetPixel()<CR> :set guicursor=n:hor20 " to see the color beneath the cursor @@ -5359,9 +5363,9 @@ types.vim: *.[ch] And put these lines in your vimrc: > " load the types.vim highlighting file, if it exists - autocmd BufRead,BufNewFile *.[ch] let fname = expand('<afile>:p:h') . '/types.vim' + autocmd BufRead,BufNewFile *.[ch] let fname = expand('<afile>:p:h') .. '/types.vim' autocmd BufRead,BufNewFile *.[ch] if filereadable(fname) - autocmd BufRead,BufNewFile *.[ch] exe 'so ' . fname + autocmd BufRead,BufNewFile *.[ch] exe 'so ' .. fname autocmd BufRead,BufNewFile *.[ch] endif ============================================================================== @@ -5402,7 +5406,7 @@ To test your color setup, a file has been included in the Vim distribution. To use it, execute this command: > :runtime syntax/colortest.vim -Nvim uses 256-color and |true-color| terminal capabilities whereever possible. +Nvim uses 256-color and |true-color| terminal capabilities wherever possible. ============================================================================== 18. When syntax is slow *:syntime* diff --git a/runtime/doc/tabpage.txt b/runtime/doc/tabpage.txt index 7f91fda9f4..c5b61e3a35 100644 --- a/runtime/doc/tabpage.txt +++ b/runtime/doc/tabpage.txt @@ -366,24 +366,24 @@ pages and define labels for them. Then get the label for each tab page. > for i in range(tabpagenr('$')) " select the highlighting if i + 1 == tabpagenr() - let s .= '%#TabLineSel#' + let s ..= '%#TabLineSel#' else - let s .= '%#TabLine#' + let s ..= '%#TabLine#' endif " set the tab page number (for mouse clicks) - let s .= '%' . (i + 1) . 'T' + let s ..= '%' .. (i + 1) .. 'T' " the label is made by MyTabLabel() - let s .= ' %{MyTabLabel(' . (i + 1) . ')} ' + let s ..= ' %{MyTabLabel(' .. (i + 1) .. ')} ' endfor " after the last tab fill with TabLineFill and reset tab page nr - let s .= '%#TabLineFill#%T' + let s ..= '%#TabLineFill#%T' " right-align the label to close the current tab page if tabpagenr('$') > 1 - let s .= '%=%#TabLine#%999Xclose' + let s ..= '%=%#TabLine#%999Xclose' endif return s @@ -446,14 +446,14 @@ windows in the tab page and a '+' if there is a modified buffer: > " Append the number of windows in the tab page if more than one let wincount = tabpagewinnr(v:lnum, '$') if wincount > 1 - let label .= wincount + let label ..= wincount endif if label != '' - let label .= ' ' + let label ..= ' ' endif " Append the buffer name - return label . bufname(bufnrlist[tabpagewinnr(v:lnum) - 1]) + return label .. bufname(bufnrlist[tabpagewinnr(v:lnum) - 1]) endfunction set guitablabel=%{GuiTabLabel()} diff --git a/runtime/doc/tagsrch.txt b/runtime/doc/tagsrch.txt index 4d938c4a23..5a0d16d6b8 100644 --- a/runtime/doc/tagsrch.txt +++ b/runtime/doc/tagsrch.txt @@ -705,7 +705,7 @@ matches the pattern "^# *define" it is not considered to be a comment. If you want to list matches, and then select one to jump to, you could use a mapping to do that for you. Here is an example: > - :map <F4> [I:let nr = input("Which one: ")<Bar>exe "normal " . nr ."[\t"<CR> + :map <F4> [I:let nr = input("Which one: ")<Bar>exe "normal " .. nr .. "[\t"<CR> < *[i* [i Display the first line that contains the keyword diff --git a/runtime/doc/term.txt b/runtime/doc/term.txt index 935d958729..f9b2271756 100644 --- a/runtime/doc/term.txt +++ b/runtime/doc/term.txt @@ -133,7 +133,7 @@ capabilities as if they had been in the terminfo definition. If terminfo does not (yet) have this flag, Nvim will fall back to $TERM and other environment variables. It will add constructed "setrgbf" and "setrgbb" -capabilities in the case of the the "rxvt", "linux", "st", "tmux", and "iterm" +capabilities in the case of the "rxvt", "linux", "st", "tmux", and "iterm" terminal types, or when Konsole, genuine Xterm, a libvte terminal emulator version 0.36 or later, or a terminal emulator that sets the COLORTERM environment variable to "truecolor" is detected. @@ -321,7 +321,7 @@ an #if/#else/#endif block, the selection becomes linewise. For MS-Windows and xterm the time for double clicking can be set with the 'mousetime' option. For the other systems this time is defined outside of Vim. An example, for using a double click to jump to the tag under the cursor: > - :map <2-LeftMouse> :exe "tag ". expand("<cword>")<CR> + :map <2-LeftMouse> :exe "tag " .. expand("<cword>")<CR> Dragging the mouse with a double click (button-down, button-up, button-down and then drag) will result in whole words to be selected. This continues diff --git a/runtime/doc/testing.txt b/runtime/doc/testing.txt index 8ec66d26a4..4e4a908d0f 100644 --- a/runtime/doc/testing.txt +++ b/runtime/doc/testing.txt @@ -12,7 +12,7 @@ and for testing plugins. 1. Testing Vim |testing| 2. Test functions |test-functions-details| -3. Assert funtions |assert-functions-details| +3. Assert functions |assert-functions-details| ============================================================================== 1. Testing Vim *testing* diff --git a/runtime/doc/tips.txt b/runtime/doc/tips.txt index b77c7d9a6d..d913b53c6b 100644 --- a/runtime/doc/tips.txt +++ b/runtime/doc/tips.txt @@ -84,14 +84,14 @@ What you need: create it with the shell command "mkid file1 file2 ..". Put this in your |init.vim|: > - map _u :call ID_search()<Bar>execute "/\\<" . g:word . "\\>"<CR> - map _n :n<Bar>execute "/\\<" . g:word . "\\>"<CR> + map _u :call ID_search()<Bar>execute "/\\<" .. g:word .. "\\>"<CR> + map _n :n<Bar>execute "/\\<" .. g:word .. "\\>"<CR> function! ID_search() let g:word = expand("<cword>") - let x = system("lid --key=none ". g:word) + let x = system("lid --key=none " .. g:word) let x = substitute(x, "\n", " ", "g") - execute "next " . x + execute "next " .. x endfun To use it, place the cursor on a word, type "_u" and vim will load the file @@ -285,13 +285,13 @@ This mapping will format any bullet list. It requires that there is an empty line above and below each list entry. The expression commands are used to be able to give comments to the parts of the mapping. > - :let m = ":map _f :set ai<CR>" " need 'autoindent' set - :let m = m . "{O<Esc>" " add empty line above item - :let m = m . "}{)^W" " move to text after bullet - :let m = m . "i <CR> <Esc>" " add space for indent - :let m = m . "gq}" " format text after the bullet - :let m = m . "{dd" " remove the empty line - :let m = m . "5lDJ" " put text after bullet + :let m = ":map _f :set ai<CR>" " need 'autoindent' set + :let m ..= "{O<Esc>" " add empty line above item + :let m ..= "}{)^W" " move to text after bullet + :let m ..= "i <CR> <Esc>" " add space for indent + :let m ..= "gq}" " format text after the bullet + :let m ..= "{dd" " remove the empty line + :let m ..= "5lDJ" " put text after bullet :execute m |" define the mapping (<> notation |<>|. Note that this is all typed literally. ^W is "^" "W", not @@ -429,15 +429,15 @@ A slightly more advanced version is used in the |matchparen| plugin. let c = '\[' let c2 = '\]' endif - let s_skip ='synIDattr(synID(line("."), col("."), 0), "name") ' . + let s_skip ='synIDattr(synID(line("."), col("."), 0), "name") ' .. \ '=~? "string\\|comment"' execute 'if' s_skip '| let s_skip = 0 | endif' let [m_lnum, m_col] = searchpairpos(c, '', c2, s_flags, s_skip) if m_lnum > 0 && m_lnum >= line('w0') && m_lnum <= line('w$') - exe 'match Search /\(\%' . c_lnum . 'l\%' . c_col . - \ 'c\)\|\(\%' . m_lnum . 'l\%' . m_col . 'c\)/' + exe 'match Search /\(\%' .. c_lnum .. 'l\%' .. c_col .. + \ 'c\)\|\(\%' .. m_lnum .. 'l\%' .. m_col .. 'c\)/' let s:paren_hl_on = 1 endif endfunction diff --git a/runtime/doc/treesitter.txt b/runtime/doc/treesitter.txt index 8f7241dd46..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,15 +95,15 @@ 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 - wether they are named or not. + whether they are named or not. Returns the child node plus the eventual field name corresponding to this child node. @@ -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. @@ -155,22 +158,22 @@ tsnode:sexpr() *tsnode:sexpr()* Get an S-expression representing the node as a string. tsnode:id() *tsnode:id()* - Get an unique identier for the node inside its own tree. + Get an unique identifier for the node inside its own tree. - No guarantees are made about this identifer'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,41 +195,41 @@ 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 evaluted 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. Here is a list of built-in predicates : `eq?` *ts-predicate-eq?* - This predicate will check text correspondance between nodes or + This predicate will check text correspondence between nodes or strings : > ((identifier) @foo (#eq? @foo "foo")) ((node1) @left (node2) @right (#eq? @left @right)) < `match?` *ts-predicate-match?* - `vim-match?` *ts-predicate-vim-match?* - This will match if the provived vim regex matches the text + `vim-match?` *ts-predicate-vim-match?* + This will match if the provided vim regex matches the text corresponding to a node : > - ((idenfitier) @constant (#match? @constant "^[A-Z_]+$")) + ((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: @@ -267,9 +270,9 @@ Here is a list of built-in directives: `offset!` *ts-predicate-offset!* Takes the range of the captured node and applies the offsets to it's range : > - ((idenfitier) @constant (#offset! @constant 0 1 0 -1)) -< This will generate a range object for the captured node with the - offsets applied. The arguments are + ((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 `({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,14 +465,15 @@ 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. Parameters: ~ - {lang} The language - {query} A string containing the query (s-expr syntax) + {lang} string The language + {query} string A string containing the query (s-expr + syntax) Return: ~ The query @@ -608,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, @@ -621,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} @@ -665,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 an updated tree. + Determines whether this tree is valid. If the tree is invalid, + call `parse()` . This will return the updated tree. Parameters: ~ {self} @@ -678,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| @@ -694,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/undo.txt b/runtime/doc/undo.txt index b11d7581ed..a853aea995 100644 --- a/runtime/doc/undo.txt +++ b/runtime/doc/undo.txt @@ -272,12 +272,12 @@ history file. E.g.: > au BufReadPost * call ReadUndo() au BufWritePost * call WriteUndo() func ReadUndo() - if filereadable(expand('%:h'). '/UNDO/' . expand('%:t')) + if filereadable(expand('%:h') .. '/UNDO/' .. expand('%:t')) rundo %:h/UNDO/%:t endif endfunc func WriteUndo() - let dirname = expand('%:h') . '/UNDO' + let dirname = expand('%:h') .. '/UNDO' if !isdirectory(dirname) call mkdir(dirname) endif diff --git a/runtime/doc/usr_04.txt b/runtime/doc/usr_04.txt index b2dd617542..c7c900274b 100644 --- a/runtime/doc/usr_04.txt +++ b/runtime/doc/usr_04.txt @@ -349,15 +349,17 @@ Notice that "yw" includes the white space after a word. If you don't want this, use "ye". The "yy" command yanks a whole line, just like "dd" deletes a whole line. -Unexpectedly, while "D" deletes from the cursor to the end of the line, "Y" -works like "yy", it yanks the whole line. Watch out for this inconsistency! -Use "y$" to yank to the end of the line. a text line yy a text line a text line line 2 line 2 p line 2 last line last line a text line last line +"Y" was originally equivalent to "yank the entire line", as opposed to "D" +which is "delete to end of the line". "Y" has thus been remapped to mean +"yank to end of the line" to make it consistent with the behavior of "D". +Mappings will be covered in later chapters. + ============================================================================== *04.7* Using the clipboard diff --git a/runtime/doc/usr_05.txt b/runtime/doc/usr_05.txt index 2edef0ca23..b1ef563e43 100644 --- a/runtime/doc/usr_05.txt +++ b/runtime/doc/usr_05.txt @@ -11,13 +11,12 @@ Vim's capabilities. Or define your own macros. |05.1| The vimrc file |05.2| The example vimrc file explained -|05.3| The defaults.vim file explained -|05.4| Simple mappings -|05.5| Adding a package -|05.6| Adding a plugin -|05.7| Adding a help file -|05.8| The option window -|05.9| Often used options +|05.3| Simple mappings +|05.4| Adding a package +|05.5| Adding a plugin +|05.6| Adding a help file +|05.7| The option window +|05.8| Often used options Next chapter: |usr_06.txt| Using syntax highlighting Previous chapter: |usr_04.txt| Making small changes @@ -132,7 +131,7 @@ it worked before Vim 5.0. Otherwise the "Q" command starts Ex mode, but you will not need it. > - vnoremap _g y:exe "grep /" . escape(@", '\\/') . "/ *.c *.h"<CR> + vnoremap _g y:exe "grep /" .. escape(@", '\\/') .. "/ *.c *.h"<CR> This mapping yanks the visually selected text and searches for it in C files. This is a complicated mapping. You can see that mappings can be used to do @@ -200,7 +199,7 @@ mapping. If set (default), this may break plugins (but it's backward compatible). See 'langremap'. ============================================================================== -*05.4* Simple mappings +*05.3* Simple mappings A mapping enables you to bind a set of Vim commands to a single key. Suppose, for example, that you need to surround certain words with curly braces. In @@ -247,7 +246,7 @@ The ":map" command (with no arguments) lists your current mappings. At least the ones for Normal mode. More about mappings in section |40.1|. ============================================================================== -*05.5* Adding a package *add-package* *vimball-install* +*05.4* Adding a package *add-package* *vimball-install* A package is a set of files that you can add to Vim. There are two kinds of packages: optional and automatically loaded on startup. @@ -287,7 +286,7 @@ an archive or as a repository. For an archive you can follow these steps: More information about packages can be found here: |packages|. ============================================================================== -*05.6* Adding a plugin *add-plugin* *plugin* +*05.5* Adding a plugin *add-plugin* *plugin* Vim's functionality can be extended by adding plugins. A plugin is nothing more than a Vim script file that is loaded automatically when Vim starts. You @@ -423,7 +422,7 @@ Further reading: |new-filetype| How to detect a new file type. ============================================================================== -*05.7* Adding a help file *add-local-help* +*05.6* Adding a help file *add-local-help* If you are lucky, the plugin you installed also comes with a help file. We will explain how to install the help file, so that you can easily find help @@ -456,7 +455,7 @@ them through the tag. For writing a local help file, see |write-local-help|. ============================================================================== -*05.8* The option window +*05.7* The option window If you are looking for an option that does what you want, you can search in the help files here: |options|. Another way is by using this command: > @@ -495,7 +494,7 @@ border. This is what the 'scrolloff' option does, it specifies an offset from the window border where scrolling starts. ============================================================================== -*05.9* Often used options +*05.8* Often used options There are an awful lot of options. Most of them you will hardly ever use. Some of the more useful ones will be mentioned here. Don't forget you can diff --git a/runtime/doc/usr_07.txt b/runtime/doc/usr_07.txt index 649be8d7ce..ebf5c3d7b8 100644 --- a/runtime/doc/usr_07.txt +++ b/runtime/doc/usr_07.txt @@ -336,7 +336,7 @@ there. > Of course you can use many other commands to yank the text. For example, to select whole lines start Visual mode with "V". Or use CTRL-V to select a -rectangular block. Or use "Y" to yank a single line, "yaw" to yank-a-word, +rectangular block. Or use "yy" to yank a single line, "yaw" to yank-a-word, etc. The "p" command puts the text after the cursor. Use "P" to put the text before the cursor. Notice that Vim remembers if you yanked a whole line or a @@ -359,7 +359,7 @@ the text should be placed in the f register. This must come just before the yank command. Now yank three whole lines to the l register (l for line): > - "l3Y + "l3yy The count could be before the "l just as well. To yank a block of text to the b (for block) register: > diff --git a/runtime/doc/usr_10.txt b/runtime/doc/usr_10.txt index 5365f90314..8844671e01 100644 --- a/runtime/doc/usr_10.txt +++ b/runtime/doc/usr_10.txt @@ -132,11 +132,11 @@ This works both with recording and with yank and delete commands. For example, you want to collect a sequence of lines into the a register. Yank the first line with: > - "aY + "ayy Now move to the second line, and type: > - "AY + "Ayy Repeat this command for all lines. The a register now contains all those lines, in the order you yanked them. diff --git a/runtime/doc/usr_41.txt b/runtime/doc/usr_41.txt index 7e611a47f3..bf024315f6 100644 --- a/runtime/doc/usr_41.txt +++ b/runtime/doc/usr_41.txt @@ -549,7 +549,7 @@ A "&" character is prepended to "path", thus the argument to eval() is Vim defines many functions and provides a large amount of functionality that way. A few examples will be given in this section. You can find the whole -list here: |functions|. +list below: |function-list|. A function is called with the ":call" command. The parameters are passed in between parentheses separated by commas. Example: > @@ -588,8 +588,8 @@ after the substitute() call. FUNCTIONS *function-list* There are many functions. We will mention them here, grouped by what they are -used for. You can find an alphabetical list here: |functions|. Use CTRL-] on -the function name to jump to detailed help on it. +used for. You can find an alphabetical list here: |builtin-function-list|. +Use CTRL-] on the function name to jump to detailed help on it. String manipulation: *string-functions* nr2char() get a character by its number value @@ -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 @@ -746,6 +748,11 @@ Cursor and mark position: *cursor-functions* *mark-functions* screenchar() get character code at a screen line/row screenchars() get character codes at a screen line/row screenstring() get string of characters at a screen line/row + charcol() character number of the cursor or a mark + getcharpos() get character position of cursor, mark, etc. + setcharpos() set character position of cursor, mark, etc. + getcursorcharpos() get character position of the cursor + setcursorcharpos() set character position of the cursor Working with text in the current buffer: *text-functions* getline() get a line or list of lines from the buffer @@ -837,6 +844,8 @@ Buffers, windows and the argument list: win_gotoid() go to window with ID win_id2tabwin() get tab and window nr from window ID win_id2win() get window nr from window ID + win_move_separator() move window vertical separator + win_move_statusline() move window status line getbufinfo() get a list with buffer information gettabinfo() get a list with tab page information getwininfo() get a list with window information diff --git a/runtime/doc/usr_toc.txt b/runtime/doc/usr_toc.txt index f466a8ece9..bf9c02882c 100644 --- a/runtime/doc/usr_toc.txt +++ b/runtime/doc/usr_toc.txt @@ -99,13 +99,12 @@ Read this from start to end to learn the essential commands. |usr_05.txt| Set your settings |05.1| The vimrc file |05.2| The example vimrc file explained - |05.3| The defaults.vim file explained - |05.4| Simple mappings - |05.5| Adding a package - |05.6| Adding a plugin - |05.7| Adding a help file - |05.8| The option window - |05.9| Often used options + |05.3| Simple mappings + |05.4| Adding a package + |05.5| Adding a plugin + |05.6| Adding a help file + |05.7| The option window + |05.8| Often used options |usr_06.txt| Using syntax highlighting |06.1| Switching it on diff --git a/runtime/doc/various.txt b/runtime/doc/various.txt index 8a4468a130..19e429fde2 100644 --- a/runtime/doc/various.txt +++ b/runtime/doc/various.txt @@ -249,7 +249,7 @@ g8 Print the hex values of the bytes used in the To enter |Terminal-mode| automatically: > autocmd TermOpen * startinsert < - *:!cmd* *:!* *E34* + *:!cmd* *:!* :!{cmd} Execute {cmd} with 'shell'. See also |:terminal|. The command runs in a non-interactive shell connected @@ -261,6 +261,7 @@ g8 Print the hex values of the bytes used in the Use |jobstart()| instead. > :call jobstart('foo', {'detach':1}) < + *E34* Any "!" in {cmd} is replaced with the previous external command (see also 'cpoptions'), unless escaped by a backslash. Example: ":!ls" followed by @@ -274,7 +275,7 @@ g8 Print the hex values of the bytes used in the Special characters are not escaped, use quotes or |shellescape()|: > :!ls "%" - :exe "!ls " . shellescape(expand("%")) + :exe "!ls " .. shellescape(expand("%")) < Newline character ends {cmd} unless a backslash precedes the newline. What follows is interpreted as @@ -357,19 +358,19 @@ g8 Print the hex values of the bytes used in the :redi[r] END End redirecting messages. *:filt* *:filter* -:filt[er][!] {pat} {command} -:filt[er][!] /{pat}/ {command} +:filt[er][!] {pattern} {command} +:filt[er][!] /{pattern}/ {command} Restrict the output of {command} to lines matching - with {pat}. For example, to list only xml files: > + with {pattern}. For example, to list only xml files: > :filter /\.xml$/ oldfiles < If the [!] is given, restrict the output of {command} - to lines that do NOT match {pat}. + to lines that do NOT match {pattern}. - {pat} is a Vim search pattern. Instead of enclosing + {pattern} is a Vim search pattern. Instead of enclosing it in / any non-ID character (see |'isident'|) can be - used, so long as it does not appear in {pat}. Without - the enclosing character the pattern cannot include the - bar character. 'ignorecase' is not used. + used, so long as it does not appear in {pattern}. + Without the enclosing character the pattern cannot + include the bar character. 'ignorecase' is not used. The pattern is matched against the relevant part of the output, not necessarily the whole line. Only some @@ -387,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 @@ -429,7 +432,7 @@ g8 Print the hex values of the bytes used in the used. In this example |:silent| is used to avoid the message about reading the file and |:unsilent| to be able to list the first line of each file. > - :silent argdo unsilent echo expand('%') . ": " . getline(1) + :silent argdo unsilent echo expand('%') .. ": " .. getline(1) < *:verb* *:verbose* @@ -458,10 +461,11 @@ g8 Print the hex values of the bytes used in the *:verbose-cmd* When 'verbose' is non-zero, listing the value of a Vim option or a key map or an abbreviation or a user-defined function or a command or a highlight group -or an autocommand will also display where it was last defined. If it was -defined manually then there will be no "Last set" message. When it was -defined while executing a function, user command or autocommand, the script in -which it was defined is reported. +or an autocommand will also display where it was last defined. If they were +defined in Lua they will only be located if 'verbose' is set. So Start +nvim with -V1 arg to see them. If it was defined manually then there +will be no "Last set" message. When it was defined while executing a function, +user command or autocommand, the script in which it was defined is reported. *K* [count]K Runs the program given by 'keywordprg' to lookup the diff --git a/runtime/doc/vim_diff.txt b/runtime/doc/vim_diff.txt index 4fcaf15717..90f56e2566 100644 --- a/runtime/doc/vim_diff.txt +++ b/runtime/doc/vim_diff.txt @@ -23,8 +23,10 @@ centralized reference of the differences. ============================================================================== 2. Defaults *nvim-defaults* -- Syntax highlighting is enabled by default -- ":filetype plugin indent on" is enabled by default +- Filetype detection is enabled by default. This can be disabled by adding + ":filetype off" to |init.vim|. +- Syntax highlighting is enabled by default. This can be disabled by adding + ":syntax off" to |init.vim|. - 'autoindent' is enabled - 'autoread' is enabled @@ -320,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 @@ -354,6 +358,10 @@ Macro/|recording| behavior macros and 'keymap' at the same time. This also means you can use |:imap| on the results of keys from 'keymap'. +Mappings: +- A mapping whose {lhs} starts with <Plug> is always applied even if mapping + is disallowed by |nore|. + Motion: The |jumplist| avoids useless/phantom jumps. @@ -429,7 +437,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 @@ -481,7 +490,6 @@ Commands: :tearoff Compile-time features: - EBCDIC Emacs tags support X11 integration (see |x11-selection|) @@ -490,6 +498,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. @@ -572,6 +583,7 @@ Test functions: test_scrollbar() test_setmouse() test_settime() + test_srand_seed() TUI: *t_xx* *termcap-options* *t_AB* *t_Sb* *t_vb* *t_SI* diff --git a/runtime/doc/visual.txt b/runtime/doc/visual.txt index 4a69fc989b..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| @@ -478,6 +479,10 @@ Commands in Select mode: - ESC stops Select mode. - CTRL-O switches to Visual mode for the duration of one command. *v_CTRL-O* - CTRL-G switches to Visual mode. +- CTRL-R {register} selects the register to be used for the text that is + deleted when typing text. *v_CTRL-R* + Unless you specify the "_" (black hole) register, the unnamed register is + also overwritten. Otherwise, typed characters are handled as in Visual mode. diff --git a/runtime/doc/windows.txt b/runtime/doc/windows.txt index bb31895c96..bd29cd1d7a 100644 --- a/runtime/doc/windows.txt +++ b/runtime/doc/windows.txt @@ -124,7 +124,7 @@ highlight group (|hl-EndOfBuffer|) can be used to change the highlighting of the filler characters. ============================================================================== -3. Opening and closing a window *opening-window* *E36* +3. Opening and closing a window *opening-window* CTRL-W s *CTRL-W_s* CTRL-W S *CTRL-W_S* @@ -223,6 +223,10 @@ CTRL-W ge *CTRL-W_ge* Note that the 'splitbelow' and 'splitright' options influence where a new window will appear. + *E36* +Creating a window will fail if there is not enough room. Every window needs +at least one screen line and column, sometimes more. Options 'winminheight' +and 'winminwidth' are relevant. *:vert* *:vertical* :vert[ical] {cmd} @@ -443,7 +447,7 @@ These commands can also be executed with ":wincmd": the |CursorHold| autocommand event). Or when a Normal mode command is inconvenient. The count can also be a window number. Example: > - :exe nr . "wincmd w" + :exe nr .. "wincmd w" < This goes to window "nr". ============================================================================== @@ -905,12 +909,12 @@ CTRL-W g } *CTRL-W_g}* cursor. This is less clever than using |:ptag|, but you don't need a tags file and it will also find matches in system include files. Example: > - :au! CursorHold *.[ch] ++nested exe "silent! psearch " . expand("<cword>") + :au! CursorHold *.[ch] ++nested exe "silent! psearch " .. expand("<cword>") < Warning: This can be slow. Example *CursorHold-example* > - :au! CursorHold *.[ch] ++nested exe "silent! ptag " . expand("<cword>") + :au! CursorHold *.[ch] ++nested exe "silent! ptag " .. expand("<cword>") This will cause a ":ptag" to be executed for the keyword under the cursor, when the cursor hasn't moved for the time set with 'updatetime'. "++nested" @@ -933,14 +937,14 @@ is no word under the cursor, and a few other things: > : : " Delete any existing highlight before showing another tag : silent! wincmd P " jump to preview window - : if &previewwindow " if we really get there... + : if &previewwindow " if we really get there... : match none " delete existing highlight : wincmd p " back to old window : endif : : " Try displaying a matching tag for the word under the cursor : try - : exe "ptag " . w + : exe "ptag " .. w : catch : return : endtry @@ -952,10 +956,10 @@ is no word under the cursor, and a few other things: > : endif : call search("$", "b") " to end of previous line : let w = substitute(w, '\\', '\\\\', "") - : call search('\<\V' . w . '\>') " position cursor on match + : call search('\<\V' .. w .. '\>') " position cursor on match : " Add a match highlight to the word at this position : hi previewWord term=bold ctermbg=green guibg=green - : exe 'match previewWord "\%' . line(".") . 'l\%' . col(".") . 'c\k*"' + : exe 'match previewWord "\%' .. line(".") .. 'l\%' .. col(".") .. 'c\k*"' : wincmd p " back to old window : endif : endif diff --git a/runtime/filetype.lua b/runtime/filetype.lua new file mode 100644 index 0000000000..74e427c358 --- /dev/null +++ b/runtime/filetype.lua @@ -0,0 +1,33 @@ +if vim.g.did_load_filetypes and vim.g.did_load_filetypes ~= 0 then + return +end + +-- For now, make this opt-in with a global variable +if vim.g.do_filetype_lua ~= 1 then + return +end + +-- TODO: Remove vim.cmd once Lua autocommands land +vim.cmd [[ +augroup filetypedetect +au BufRead,BufNewFile * call v:lua.vim.filetype.match(expand('<afile>')) + +" These *must* be sourced after the autocommand above is created +runtime! ftdetect/*.vim +runtime! ftdetect/*.lua + +" Set a marker so that the ftdetect scripts are not sourced a second time by filetype.vim +let g:did_load_ftdetect = 1 + +" If filetype.vim is disabled, set up the autocmd to use scripts.vim +if exists('did_load_filetypes') + au BufRead,BufNewFile * if !did_filetype() && expand('<amatch>') !~ g:ft_ignore_pat | runtime! scripts.vim | endif + au StdinReadPost * if !did_filetype() | runtime! scripts.vim | endif +endif + +augroup END +]] + +if not vim.g.ft_ignore_pat then + vim.g.ft_ignore_pat = "\\.\\(Z\\|gz\\|bz2\\|zip\\|tgz\\)$" +end diff --git a/runtime/filetype.vim b/runtime/filetype.vim index 2c5064489b..8114ad4092 100644 --- a/runtime/filetype.vim +++ b/runtime/filetype.vim @@ -1,7 +1,7 @@ " Vim support file to detect file types " " Maintainer: Bram Moolenaar <Bram@vim.org> -" Last Change: 2021 Dec 22 +" Last Change: 2022 Jan 31 " Listen very carefully, I will say this only once if exists("did_load_filetypes") @@ -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 @@ -189,7 +189,8 @@ au BufNewFile,BufRead *.awk,*.gawk setf awk au BufNewFile,BufRead *.mch,*.ref,*.imp setf b " BASIC or Visual Basic -au BufNewFile,BufRead *.bas call dist#ft#FTVB("basic") +au BufNewFile,BufRead *.bas call dist#ft#FTbas() +au BufNewFile,BufRead *.bi,*.bm call dist#ft#FTbas() " Visual Basic Script (close to Visual Basic) or Visual Basic .NET au BufNewFile,BufRead *.vb,*.vbs,*.dsm,*.ctl setf vb @@ -198,7 +199,7 @@ au BufNewFile,BufRead *.vb,*.vbs,*.dsm,*.ctl setf vb au BufNewFile,BufRead *.iba,*.ibi setf ibasic " FreeBasic file (similar to QBasic) -au BufNewFile,BufRead *.fb,*.bi setf freebasic +au BufNewFile,BufRead *.fb setf freebasic " Batch file for MSDOS. au BufNewFile,BufRead *.bat,*.sys setf dosbatch @@ -224,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 @@ -392,7 +396,8 @@ au BufNewFile,BufRead configure.in,configure.ac setf config " CUDA Compute Unified Device Architecture au BufNewFile,BufRead *.cu,*.cuh setf cuda -" Dockerfilb; Podman uses the same syntax with name Containerfile +" Dockerfile; Podman uses the same syntax with name Containerfile +" Also see Dockerfile.* below. au BufNewFile,BufRead Containerfile,Dockerfile,*.Dockerfile setf dockerfile " WildPackets EtherPeek Decoder @@ -475,6 +480,7 @@ au BufNewFile,BufRead */etc/dnsmasq.conf setf dnsmasq au BufNewFile,BufRead *.desc setf desc " the D language or dtrace +au BufNewFile,BufRead */dtrace/*.d setf dtrace au BufNewFile,BufRead *.d call dist#ft#DtraceCheck() " Desktop files @@ -492,7 +498,7 @@ au BufNewFile,BufRead */debian/patches/* call dist#ft#Dep3patch() " Diff files au BufNewFile,BufRead *.diff,*.rej setf diff au BufNewFile,BufRead *.patch - \ if getline(1) =~ '^From [0-9a-f]\{40\} Mon Sep 17 00:00:00 2001$' | + \ if getline(1) =~# '^From [0-9a-f]\{40,\} Mon Sep 17 00:00:00 2001$' | \ setf gitsendemail | \ else | \ setf diff | @@ -648,6 +654,9 @@ au BufNewFile,BufRead *.fsl setf framescript " FStab au BufNewFile,BufRead fstab,mtab setf fstab +" Fusion +au BufRead,BufNewFile *.fusion setf fusion + " F# or Forth au BufNewFile,BufRead *.fs call dist#ft#FTfs() @@ -660,6 +669,12 @@ au BufNewFile,BufRead .gdbinit,gdbinit setf gdb " GDMO au BufNewFile,BufRead *.mo,*.gdmo setf gdmo +" GDscript +au BufNewFile,BufRead *.gd setf gdscript + +" Godot resource +au BufRead,BufNewFile *.tscn,*.tres setf gdresource + " Gedcom au BufNewFile,BufRead *.ged,lltxxxxx.txt setf gedcom @@ -671,26 +686,28 @@ autocmd BufRead,BufNewFile *.gift setf gift " Git au BufNewFile,BufRead COMMIT_EDITMSG,MERGE_MSG,TAG_EDITMSG setf gitcommit -au BufNewFile,BufRead *.git/config,.gitconfig,/etc/gitconfig setf gitconfig +au BufNewFile,BufRead NOTES_EDITMSG,EDIT_DESCRIPTION setf gitcommit +au BufNewFile,BufRead *.git/config,.gitconfig,*/etc/gitconfig setf gitconfig au BufNewFile,BufRead */.config/git/config setf gitconfig +au BufNewFile,BufRead *.git/config.worktree setf gitconfig +au BufNewFile,BufRead *.git/worktrees/*/config.worktree setf gitconfig au BufNewFile,BufRead .gitmodules,*.git/modules/*/config setf gitconfig if !empty($XDG_CONFIG_HOME) au BufNewFile,BufRead $XDG_CONFIG_HOME/git/config setf gitconfig endif au BufNewFile,BufRead git-rebase-todo setf gitrebase au BufRead,BufNewFile .gitsendemail.msg.?????? setf gitsendemail -au BufNewFile,BufRead .msg.[0-9]* - \ if getline(1) =~ '^From.*# This line is ignored.$' | - \ setf gitsendemail | - \ endif au BufNewFile,BufRead *.git/* - \ if getline(1) =~ '^\x\{40\}\>\|^ref: ' | + \ if getline(1) =~# '^\x\{40,\}\>\|^ref: ' | \ setf git | \ endif " Gkrellmrc au BufNewFile,BufRead gkrellmrc,gkrellmrc_? setf gkrellmrc +" GLSL +au BufNewFile,BufRead *.glsl setf glsl + " GP scripts (2.0 and onward) au BufNewFile,BufRead *.gp,.gprc setf gp @@ -710,16 +727,24 @@ 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 setf gnuplot +au BufNewFile,BufRead *.gpi,.gnuplot setf gnuplot " Go (Google) au BufNewFile,BufRead *.go setf go au BufNewFile,BufRead Gopkg.lock setf toml +au BufRead,BufNewFile go.work setf gowork " GrADS scripts au BufNewFile,BufRead *.gs setf grads +" GraphQL +au BufNewFile,BufRead *.graphql,*.graphqls,*.gql setf graphql + " Gretl au BufNewFile,BufRead *.gretl setf gretl @@ -735,12 +760,18 @@ au BufNewFile,BufRead */etc/group,*/etc/group-,*/etc/group.edit,*/etc/gshadow,*/ " GTK RC au BufNewFile,BufRead .gtkrc,gtkrc setf gtkrc +" Hack +au BufRead,BufNewFile *.hack,*.hackpartial setf hack + " Haml au BufNewFile,BufRead *.haml setf haml " Hamster Classic | Playground files au BufNewFile,BufRead *.hsm setf hamster +" Handlebars +au BufNewFile,BufRead *.hbs setf handlebars + " Haskell au BufNewFile,BufRead *.hs,*.hsc,*.hs-boot,*.hsig setf haskell au BufNewFile,BufRead *.lhs setf lhaskell @@ -753,12 +784,21 @@ au BufNewFile,BufRead cabal.config setf cabalconfig au BufNewFile,BufRead *.ht setf haste au BufNewFile,BufRead *.htpp setf hastepreproc +" HCL +au BufRead,BufNewFile *.hcl setf hcl + " Hercules au BufNewFile,BufRead *.vc,*.ev,*.sum,*.errsum setf hercules +" HEEx +au BufRead,BufNewFile *.heex setf heex + " HEX (Intel) au BufNewFile,BufRead *.hex,*.h32 setf hex +" Hjson +au BufNewFile,BufRead *.hjson setf hjson + " Hollywood au BufRead,BufNewFile *.hws setf hollywood @@ -883,6 +923,9 @@ au BufNewFile,BufRead *.jov,*.j73,*.jovial setf jovial " JSON au BufNewFile,BufRead *.json,*.jsonp,*.webmanifest setf json +" JSON5 +au BufNewFile,BufRead *.json5 setf json5 + " JSON Patch (RFC 6902) au BufNewFile,BufRead *.json-patch setf json @@ -934,6 +977,9 @@ au BufNewFile,BufRead *.ldif setf ldif " Ld loader au BufNewFile,BufRead *.ld setf ld +" Ledger +au BufRead,BufNewFile *.ldg,*.ledger,*.journal setf ledger + " Less au BufNewFile,BufRead *.less setf less @@ -958,9 +1004,9 @@ au BufNewFile,BufRead lilo.conf setf lilo " Lisp (*.el = ELisp, *.cl = Common Lisp) " *.jl was removed, it's also used for Julia, better skip than guess wrong. if has("fname_case") - au BufNewFile,BufRead *.lsp,*.lisp,*.el,*.cl,*.L,.emacs,.sawfishrc setf lisp + au BufNewFile,BufRead *.lsp,*.lisp,*.asd,*.el,*.cl,*.L,.emacs,.sawfishrc setf lisp else - au BufNewFile,BufRead *.lsp,*.lisp,*.el,*.cl,.emacs,.sawfishrc setf lisp + au BufNewFile,BufRead *.lsp,*.lisp,*.asd,*.el,*.cl,.emacs,.sawfishrc setf lisp endif " SBCL implementation of Common Lisp @@ -1171,6 +1217,9 @@ au BufNewFile,BufRead *.nginx,nginx*.conf,*nginx.conf,*/etc/nginx/*,*/usr/local/ " Ninja file au BufNewFile,BufRead *.ninja setf ninja +" Nix +au BufRead,BufNewFile *.nix setf nix + " NPM RC file au BufNewFile,BufRead npmrc,.npmrc setf dosini @@ -1212,6 +1261,9 @@ au BufNewFile,BufRead *.xom,*.xin setf omnimark " OPAM au BufNewFile,BufRead opam,*.opam,*.opam.template setf opam +" OpenFOAM +au BufNewFile,BufRead [a-zA-Z0-9]*Dict\(.*\)\=,[a-zA-Z]*Properties\(.*\)\=,*Transport\(.*\),fvSchemes,fvSolution,fvConstrains,fvModels,*/constant/g,*/0\(\.orig\)\=/* call dist#ft#FTfoam() + " OpenROAD au BufNewFile,BufRead *.or setf openroad @@ -1353,6 +1405,9 @@ au BufNewFile,BufRead *printcap au BufNewFile,BufRead *termcap \ let b:ptcap_type = "term" | setf ptcap +" Prisma +au BufRead,BufNewFile *.prisma setf prisma + " PCCTS / ANTLR "au BufNewFile,BufRead *.g setf antlr au BufNewFile,BufRead *.g setf pccts @@ -1360,6 +1415,9 @@ au BufNewFile,BufRead *.g setf pccts " PPWizard au BufNewFile,BufRead *.it,*.ih setf ppwiz +" Pug +au BufRead,BufNewFile *.pug setf pug + " Puppet au BufNewFile,BufRead Puppetfile setf ruby @@ -1425,6 +1483,9 @@ au BufNewFile,BufRead *.pyx,*.pxd setf pyrex au BufNewFile,BufRead *.py,*.pyw,.pythonstartup,.pythonrc setf python au BufNewFile,BufRead *.ptl,*.pyi,SConstruct setf python +" QL +au BufRead,BufNewFile *.ql,*.qll setf ql + " Radiance au BufNewFile,BufRead *.rad,*.mat setf radiance @@ -1490,6 +1551,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 @@ -1664,7 +1728,7 @@ au BufNewFile,BufRead .zshrc,.zshenv,.zlogin,.zlogout,.zcompdump setf zsh au BufNewFile,BufRead *.zsh setf zsh " Scheme -au BufNewFile,BufRead *.scm,*.ss,*.rkt,*.rktd,*.rktl setf scheme +au BufNewFile,BufRead *.scm,*.ss,*.sld,*.rkt,*.rktd,*.rktl setf scheme " Screen RC au BufNewFile,BufRead .screenrc,screenrc setf screen @@ -1732,6 +1796,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 @@ -1773,8 +1840,8 @@ au BufNewFile,BufRead *.sqr,*.sqi setf sqr au BufNewFile,BufRead *.nut setf squirrel " OpenSSH configuration -au BufNewFile,BufRead ssh_config,*/.ssh/config setf sshconfig -au BufNewFile,BufRead */etc/ssh/ssh_config.d/*.conf setf sshconfig +au BufNewFile,BufRead ssh_config,*/.ssh/config,*/.ssh/*.conf setf sshconfig +au BufNewFile,BufRead */etc/ssh/ssh_config.d/*.conf setf sshconfig " OpenSSH server configuration au BufNewFile,BufRead sshd_config setf sshdconfig @@ -1829,6 +1896,9 @@ au BufNewFile,BufRead */etc/sudoers,sudoers.tmp setf sudoers " SVG (Scalable Vector Graphics) au BufNewFile,BufRead *.svg setf svg +" Surface +au BufRead,BufNewFile *.sface setf surface + " Tads (or Nroff or Perl test file) au BufNewFile,BufRead *.t \ if !dist#ft#FTnroff() && !dist#ft#FTperl() | setf tads | endif @@ -1846,6 +1916,9 @@ au BufRead,BufNewFile *.task setf taskedit " Tcl (JACL too) au BufNewFile,BufRead *.tcl,*.tm,*.tk,*.itcl,*.itk,*.jacl,.tclshrc,.wishrc setf tcl +" Teal +au BufRead,BufNewFile *.tl setf teal + " TealInfo au BufNewFile,BufRead *.tli setf tli @@ -1863,6 +1936,9 @@ au BufRead,BufNewFile *.ttl " Terminfo au BufNewFile,BufRead *.ti setf terminfo +" Terraform +au BufRead,BufNewFile *.tfvars setf terraform + " TeX au BufNewFile,BufRead *.latex,*.sty,*.dtx,*.ltx,*.bbl setf tex au BufNewFile,BufRead *.tex call dist#ft#FTtex() @@ -1880,7 +1956,13 @@ au BufNewFile,BufRead texmf.cnf setf texmf au BufNewFile,BufRead .tidyrc,tidyrc,tidy.conf setf tidy " TF mud client -au BufNewFile,BufRead *.tf,.tfrc,tfrc setf tf +au BufNewFile,BufRead .tfrc,tfrc setf tf + +" TF mud client or terraform +au BufNewFile,BufRead *.tf call dist#ft#FTtf() + +" TLA+ +au BufNewFile,BufRead *.tla setf tla " tmux configuration au BufNewFile,BufRead {.,}tmux*.conf setf tmux @@ -1889,7 +1971,7 @@ au BufNewFile,BufRead {.,}tmux*.conf setf tmux au BufNewFile,BufRead *.toml setf toml " TPP - Text Presentation Program -au BufNewFile,BufReadPost *.tpp setf tpp +au BufNewFile,BufRead *.tpp setf tpp " Treetop au BufRead,BufNewFile *.treetop setf treetop @@ -1949,6 +2031,9 @@ au BufNewFile,BufRead */.init/*.conf,*/.init/*.override setf upstart au BufNewFile,BufRead */.config/upstart/*.conf setf upstart au BufNewFile,BufRead */.config/upstart/*.override setf upstart +" Vala +au BufNewFile,BufRead *.vala setf vala + " Vera au BufNewFile,BufRead *.vr,*.vri,*.vrh setf vera @@ -1979,7 +2064,7 @@ au BufRead,BufNewFile *.hw,*.module,*.pkg \ endif " Visual Basic (also uses *.bas) or FORM -au BufNewFile,BufRead *.frm call dist#ft#FTVB("form") +au BufNewFile,BufRead *.frm call dist#ft#FTfrm() " SaxBasic is close to Visual Basic au BufNewFile,BufRead *.sba setf vb @@ -2143,6 +2228,9 @@ au BufNewFile,BufRead *.raml setf raml " yum conf (close enough to dosini) au BufNewFile,BufRead */etc/yum.conf setf dosini +" YANG +au BufRead,BufNewFile *.yang setf yang + " Zimbu au BufNewFile,BufRead *.zu setf zimbu " Zimbu Templates @@ -2237,6 +2325,9 @@ au BufNewFile,BufRead crontab,crontab.*,*/etc/cron.d/* call s:StarSetf('crontab " dnsmasq(8) configuration au BufNewFile,BufRead */etc/dnsmasq.d/* call s:StarSetf('dnsmasq') +" Dockerfile +au BufNewFile,BufRead Dockerfile.*,Containerfile.* call s:StarSetf('dockerfile') + " Dracula au BufNewFile,BufRead drac.* call s:StarSetf('dracula') @@ -2281,6 +2372,9 @@ au BufNewFile,BufRead Kconfig.* call s:StarSetf('kconfig') " Lilo: Linux loader au BufNewFile,BufRead lilo.conf* call s:StarSetf('lilo') +" Libsensors +au BufNewFile,BufRead */etc/sensors.d/[^.]* call s:StarSetf('sensors') + " Logcheck au BufNewFile,BufRead */etc/logcheck/*.d*/* call s:StarSetf('logcheck') @@ -2403,10 +2497,12 @@ au BufNewFile,BufRead *.txt \| setf text \| endif -" Use the filetype detect plugins. They may overrule any of the previously -" detected filetypes. -runtime! ftdetect/*.vim -runtime! ftdetect/*.lua +if !exists('g:did_load_ftdetect') + " Use the filetype detect plugins. They may overrule any of the previously + " detected filetypes. + runtime! ftdetect/*.vim + runtime! ftdetect/*.lua +endif " NOTE: The above command could have ended the filetypedetect autocmd group " and started another one. Let's make sure it has ended to get to a consistent @@ -2432,7 +2528,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/ftplugin/basic.vim b/runtime/ftplugin/basic.vim index c6ec254dfc..a8f6b088d1 100644 --- a/runtime/ftplugin/basic.vim +++ b/runtime/ftplugin/basic.vim @@ -1,7 +1,7 @@ " Vim filetype plugin file -" Language: BASIC +" Language: BASIC (QuickBASIC 4.5) " Maintainer: Doug Kearns <dougkearns@gmail.com> -" Last Change: 2015 Jan 10 +" Last Change: 2021 Mar 16 if exists("b:did_ftplugin") finish @@ -11,17 +11,46 @@ let b:did_ftplugin = 1 let s:cpo_save = &cpo set cpo&vim -setlocal comments=:REM,:' +setlocal comments=:REM\ ,:Rem\ ,:rem\ ,:' setlocal commentstring='\ %s setlocal formatoptions-=t formatoptions+=croql +" TODO: support exit ... as middle matches? +if exists("loaded_matchit") && !exists("b:match_words") + let s:line_start = '\%(^\s*\)\@<=' + let s:not_end = '\%(end\s\+\)\@<!' + let s:not_end_or_exit = '\%(\%(end\|exit\)\s\+\)\@<!' + + let b:match_ignorecase = 1 + let b:match_words = + \ s:not_end_or_exit .. '\<def\s\+fn:\<end\s\+def\>,' .. + \ s:not_end_or_exit .. '\<function\>:\<end\s\+function\>,' .. + \ s:not_end_or_exit .. '\<sub\>:\<end\s\+sub\>,' .. + \ s:not_end .. '\<type\>:\<end\s\+type\>,' .. + \ s:not_end .. '\<select\>:\%(select\s\+\)\@<!\<case\%(\s\+\%(else\|is\)\)\=\>:\<end\s\+select\>,' .. + \ '\<do\>:\<loop\>,' .. + \ '\<for\>\%(\s\+\%(input\|output\|random\|append\|binary\)\)\@!:\<next\>,' .. + \ '\<while\>:\<wend\>,' .. + \ s:line_start .. 'if\%(.*\<then\s*\%($\|''\)\)\@=:\<\%(' .. s:line_start .. 'else\|elseif\)\>:\<end\s\+if\>,' .. + \ '\<lock\>:\<unlock\>' + + let b:match_skip = 'synIDattr(synID(line("."),col("."),1),"name") =~? "comment\\|string" || ' .. + \ 'strpart(getline("."), 0, col(".") ) =~? "\\<exit\\s\\+"' + + unlet s:line_start s:not_end s:not_end_or_exit +endif + if (has("gui_win32") || has("gui_gtk")) && !exists("b:browsefilter") - let b:browsefilter = "BASIC Source Files (*.bas)\t*.bas\n" . - \ "All Files (*.*)\t*.*\n" + let b:browsefilter = "BASIC Source Files (*.bas)\t*.bas\n" .. + \ "BASIC Include Files (*.bi, *.bm)\t*.bi;*.bm\n" .. + \ "All Files (*.*)\t*.*\n" endif -let b:undo_ftplugin = "setl fo< com< cms< sua<" . - \ " | unlet! b:browsefilter" +let b:undo_ftplugin = "setl fo< com< cms<" .. + \ " | unlet! b:match_ignorecase b:match_skip b:match_words" .. + \ " | unlet! b:browsefilter" let &cpo = s:cpo_save unlet s:cpo_save + +" vim: nowrap sw=2 sts=2 ts=8 noet fdm=marker: diff --git a/runtime/ftplugin/freebasic.vim b/runtime/ftplugin/freebasic.vim index a2bb459f20..58c2b4c9e2 100644 --- a/runtime/ftplugin/freebasic.vim +++ b/runtime/ftplugin/freebasic.vim @@ -1,13 +1,65 @@ " Vim filetype plugin file -" Language: FreeBasic +" Language: FreeBASIC " Maintainer: Doug Kearns <dougkearns@gmail.com> -" Last Change: 2015 Jan 10 +" Last Change: 2021 Mar 16 +" Setup {{{1 if exists("b:did_ftplugin") finish endif -let b:did_ftplugin = 1 + +let s:cpo_save = &cpo +set cpo&vim runtime! ftplugin/basic.vim -" vim: ts=8 +let s:dialect = freebasic#GetDialect() + +" Comments {{{1 +" add ''comments before 'comments +let &l:comments = "sO:*\ -,mO:*\ \ ,exO:*/,s1:/',mb:',ex:'/,:''," .. &l:comments + +" Match words {{{1 +if exists("loaded_matchit") + let s:not_end = '\%(end\s\+\)\@<!' + + let b:match_words ..= ',' + + if s:dialect == 'fb' + let b:match_words ..= s:not_end .. '\<constructor\>:\<end\s\+constructor\>,' .. + \ s:not_end .. '\<destructor\>:\<end\s\+destructor\>,' .. + \ s:not_end .. '\<property\>:\<end\s\+property\>,' .. + \ s:not_end .. '\<operator\>:\<end\s\+operator\>,' .. + \ s:not_end .. '\<extern\%(\s\+"\)\@=:\<end\s\+extern\>,' + endif + + if s:dialect == 'fb' || s:dialect == 'deprecated' + let b:match_words ..= s:not_end .. '\<scope\>:\<end\s\+scope\>,' + endif + + if s:dialect == 'qb' + let b:match_words ..= s:not_end .. '\<__asm\>:\<end\s\+__asm\>,' .. + \ s:not_end .. '\<__union\>:\<end\s\+__union\>,' .. + \ s:not_end .. '\<__with\>:\<end\s\+__with\>,' + else + let b:match_words ..= s:not_end .. '\<asm\>:\<end\s\+asm\>,' .. + \ s:not_end .. '\<namespace\>:\<end\s\+namespace\>,' .. + \ s:not_end .. '\<union\>:\<end\s\+union\>,' .. + \ s:not_end .. '\<with\>:\<end\s\+with\>,' + endif + + let b:match_words ..= s:not_end .. '\<enum\>:\<end\s\+enum\>,' .. + \ '^#\s*\%(if\|ifdef\|ifndef\)\>:^#\s*\%(else\|elseif\)\>:^#\s*endif\>,' .. + \ '^#\s*macro\>:^#\s*endmacro\>' + + " skip "function = <retval>" + let b:match_skip ..= '|| strpart(getline("."), col(".") - 1) =~? "^\\<function\\s\\+="' + + unlet s:not_end +endif + +" Cleanup {{{1 +let &cpo = s:cpo_save +unlet s:cpo_save + +" vim: nowrap sw=2 sts=2 ts=8 noet fdm=marker: diff --git a/runtime/ftplugin/git.vim b/runtime/ftplugin/git.vim deleted file mode 100644 index 75b20f021e..0000000000 --- a/runtime/ftplugin/git.vim +++ /dev/null @@ -1,41 +0,0 @@ -" Vim filetype plugin -" Language: generic git output -" Maintainer: Tim Pope <vimNOSPAM@tpope.org> -" Last Change: 2019 Dec 05 - -" Only do this when not done yet for this buffer -if (exists("b:did_ftplugin")) - finish -endif -let b:did_ftplugin = 1 - -if !exists('b:git_dir') - if expand('%:p') =~# '[\/]\.git[\/]modules[\/]\|:[\/][\/]\|^\a\a\+:' - " Stay out of the way - elseif expand('%:p') =~# '[\/]\.git[\/]worktrees' - let b:git_dir = matchstr(expand('%:p'),'.*\.git[\/]worktrees[\/][^\/]\+\>') - elseif expand('%:p') =~# '\.git\>' - let b:git_dir = matchstr(expand('%:p'),'.*\.git\>') - elseif $GIT_DIR != '' - let b:git_dir = $GIT_DIR - endif - if (has('win32') || has('win64')) && exists('b:git_dir') - let b:git_dir = substitute(b:git_dir,'\\','/','g') - endif -endif - -if exists('*shellescape') && exists('b:git_dir') && b:git_dir != '' - if b:git_dir =~# '/\.git$' " Not a bare repository - let &l:path = escape(fnamemodify(b:git_dir,':h'),'\, ').','.&l:path - endif - let &l:path = escape(b:git_dir,'\, ').','.&l:path - let &l:keywordprg = 'git --git-dir='.shellescape(b:git_dir).' show' -else - setlocal keywordprg=git\ show -endif -if has('gui_running') - let &l:keywordprg = substitute(&l:keywordprg,'^git\>','git --no-pager','') -endif - -setlocal includeexpr=substitute(v:fname,'^[^/]\\+/','','') -let b:undo_ftplugin = "setl keywordprg< path< includeexpr<" diff --git a/runtime/ftplugin/gitcommit.vim b/runtime/ftplugin/gitcommit.vim index 9b1998acaa..9342799b56 100644 --- a/runtime/ftplugin/gitcommit.vim +++ b/runtime/ftplugin/gitcommit.vim @@ -1,66 +1,57 @@ " Vim filetype plugin " Language: git commit file " Maintainer: Tim Pope <vimNOSPAM@tpope.org> -" Last Change: 2019 Dec 05 +" Last Change: 2022 Jan 05 " Only do this when not done yet for this buffer if (exists("b:did_ftplugin")) finish endif -runtime! ftplugin/git.vim let b:did_ftplugin = 1 -setlocal comments=:# commentstring=#\ %s setlocal nomodeline tabstop=8 formatoptions+=tl textwidth=72 setlocal formatoptions-=c formatoptions-=r formatoptions-=o formatoptions-=q formatoptions+=n setlocal formatlistpat+=\\\|^\\s*[-*+]\\s\\+ +setlocal include=^+++ +setlocal includeexpr=substitute(v:fname,'^[bi]/','','') -let b:undo_ftplugin = 'setl modeline< tabstop< formatoptions< tw< com< cms< formatlistpat<' +let b:undo_ftplugin = 'setl modeline< tabstop< formatoptions< tw< com< cms< formatlistpat< inc< inex<' -if exists("g:no_gitcommit_commands") || v:version < 700 - finish -endif +let s:l = search('\C\m^[#;@!$%^&|:] -\{24,\} >8 -\{24,\}$', 'cnW', '', 100) +let &l:comments = ':' . (matchstr(getline(s:l ? s:l : '$'), '^[#;@!$%^&|:]\S\@!') . '#')[0] +let &l:commentstring = &l:comments[1] . ' %s' +unlet s:l -if !exists("b:git_dir") - let b:git_dir = expand("%:p:h") +if exists("g:no_gitcommit_commands") + finish endif -command! -bang -bar -buffer -complete=custom,s:diffcomplete -nargs=* DiffGitCached :call s:gitdiffcached(<bang>0,b:git_dir,<f-args>) +command! -bang -bar -buffer -complete=custom,s:diffcomplete -nargs=* DiffGitCached :call s:gitdiffcached(<bang>0, <f-args>) let b:undo_ftplugin = b:undo_ftplugin . "|delc DiffGitCached" -function! s:diffcomplete(A,L,P) +function! s:diffcomplete(A, L, P) abort let args = "" if a:P <= match(a:L." -- "," -- ")+3 let args = args . "-p\n--stat\n--shortstat\n--summary\n--patch-with-stat\n--no-renames\n-B\n-M\n-C\n" end - if exists("b:git_dir") && a:A !~ '^-' - let tree = fnamemodify(b:git_dir,':h') - if strpart(getcwd(),0,strlen(tree)) == tree - let args = args."\n".system("git diff --cached --name-only") - endif + if a:A !~ '^-' && !empty(getftype('.git')) + let args = args."\n".system("git diff --cached --name-only") endif return args endfunction -function! s:gitdiffcached(bang,gitdir,...) - let tree = fnamemodify(a:gitdir,':h') +function! s:gitdiffcached(bang, ...) abort let name = tempname() - let git = "git" - if strpart(getcwd(),0,strlen(tree)) != tree - let git .= " --git-dir=".(exists("*shellescape") ? shellescape(a:gitdir) : '"'.a:gitdir.'"') - endif if a:0 - let extra = join(map(copy(a:000),exists("*shellescape") ? 'shellescape(v:val)' : "'\"'.v:val.'\"'")) + let extra = join(map(copy(a:000), 'shellescape(v:val)')) else let extra = "-p --stat=".&columns endif - call system(git." diff --cached --no-color --no-ext-diff ".extra." > ".(exists("*shellescape") ? shellescape(name) : name)) - exe "pedit ".(exists("*fnameescape") ? fnameescape(name) : name) + call system("git diff --cached --no-color --no-ext-diff ".extra." > ".shellescape(name)) + exe "pedit " . fnameescape(name) wincmd P - let b:git_dir = a:gitdir - command! -bang -bar -buffer -complete=custom,s:diffcomplete -nargs=* DiffGitCached :call s:gitdiffcached(<bang>0,b:git_dir,<f-args>) - nnoremap <buffer> <silent> q :q<CR> + command! -bang -bar -buffer -complete=custom,s:diffcomplete -nargs=* DiffGitCached :call s:gitdiffcached(<bang>0, <f-args>) setlocal buftype=nowrite nobuflisted noswapfile nomodifiable filetype=git endfunction diff --git a/runtime/ftplugin/gitrebase.vim b/runtime/ftplugin/gitrebase.vim index 2fed53c829..143f86a251 100644 --- a/runtime/ftplugin/gitrebase.vim +++ b/runtime/ftplugin/gitrebase.vim @@ -1,22 +1,20 @@ " Vim filetype plugin " Language: git rebase --interactive " Maintainer: Tim Pope <vimNOSPAM@tpope.org> -" Last Change: 2019 Dec 05 +" Last Change: 2022 Jan 05 " Only do this when not done yet for this buffer if (exists("b:did_ftplugin")) finish endif -runtime! ftplugin/git.vim let b:did_ftplugin = 1 -setlocal comments=:# commentstring=#\ %s formatoptions-=t +let &l:comments = ':' . (matchstr(getline('$'), '^[#;@!$%^&|:]\S\@!') . '#')[0] +let &l:commentstring = &l:comments[1] . ' %s' +setlocal formatoptions-=t setlocal nomodeline -if !exists("b:undo_ftplugin") - let b:undo_ftplugin = "" -endif -let b:undo_ftplugin = b:undo_ftplugin."|setl com< cms< fo< ml<" +let b:undo_ftplugin = "setl com< cms< fo< ml<" function! s:choose(word) abort s/^\(\w\+\>\)\=\(\s*\)\ze\x\{4,40\}\>/\=(strlen(submatch(1)) == 1 ? a:word[0] : a:word) . substitute(submatch(2),'^$',' ','')/e @@ -41,8 +39,7 @@ if exists("g:no_plugin_maps") || exists("g:no_gitrebase_maps") finish endif -nnoremap <buffer> <expr> K col('.') < 7 && expand('<Lt>cword>') =~ '\X' && getline('.') =~ '^\w\+\s\+\x\+\>' ? 'wK' : 'K' nnoremap <buffer> <silent> <C-A> :<C-U><C-R>=v:count1<CR>Cycle<CR> nnoremap <buffer> <silent> <C-X> :<C-U><C-R>=v:count1<CR>Cycle!<CR> -let b:undo_ftplugin = b:undo_ftplugin . "|exe 'nunmap <buffer> K'|exe 'nunmap <buffer> <C-A>'|exe 'nunmap <buffer> <C-X>'" +let b:undo_ftplugin = b:undo_ftplugin . "|exe 'nunmap <buffer> <C-A>'|exe 'nunmap <buffer> <C-X>'" diff --git a/runtime/ftplugin/qb64.vim b/runtime/ftplugin/qb64.vim new file mode 100644 index 0000000000..0fa36fc3d2 --- /dev/null +++ b/runtime/ftplugin/qb64.vim @@ -0,0 +1,26 @@ +" Vim filetype plugin file +" Language: QB64 +" Maintainer: Doug Kearns <dougkearns@gmail.com> + +if exists("b:did_ftplugin") + finish +endif + +let s:cpo_save = &cpo +set cpo&vim + +runtime! ftplugin/basic.vim + +let s:not_end = '\%(end\s\+\)\@<!' + +let b:match_words ..= ',' .. + \ s:not_end .. '\<declare\>:\<end\s\+declare\>,' .. + \ '\<select\s\+everycase\>:\%(select\s\+\)\@<!\<case\%(\s\+\%(else\|is\)\)\=\>:\<end\s\+select\>,' .. + \ '$IF\>:$\%(ELSEIF\|ELSE\)\>:$END\s*IF\>' + +unlet s:not_end + +let &cpo = s:cpo_save +unlet s:cpo_save + +" vim: nowrap sw=2 sts=2 ts=8 noet fdm=marker: diff --git a/runtime/indent/basic.vim b/runtime/indent/basic.vim new file mode 100644 index 0000000000..7228772251 --- /dev/null +++ b/runtime/indent/basic.vim @@ -0,0 +1,11 @@ +" Vim indent file +" Language: BASIC (QuickBASIC 4.5) +" Maintainer: Doug Kearns <dougkearns@gmail.com> +" Last Change: 2022 Jan 24 + +" Only load this indent file when no other was loaded. +if exists("b:did_indent") + finish +endif + +runtime! indent/vb.vim diff --git a/runtime/indent/freebasic.vim b/runtime/indent/freebasic.vim new file mode 100644 index 0000000000..248b928635 --- /dev/null +++ b/runtime/indent/freebasic.vim @@ -0,0 +1,11 @@ +" Vim indent file +" Language: FreeBASIC +" Maintainer: Doug Kearns <dougkearns@gmail.com> +" Last Change: 2022 Jan 24 + +" Only load this indent file when no other was loaded. +if exists("b:did_indent") + finish +endif + +runtime! indent/vb.vim diff --git a/runtime/indent/html.vim b/runtime/indent/html.vim index d4b91f6421..a3c32d6342 100644 --- a/runtime/indent/html.vim +++ b/runtime/indent/html.vim @@ -1,7 +1,7 @@ " Vim indent script for HTML " Maintainer: Bram Moolenaar " Original Author: Andy Wokula <anwoku@yahoo.de> -" Last Change: 2021 Jun 13 +" Last Change: 2022 Jan 31 " Version: 1.0 "{{{ " Description: HTML indent script with cached state for faster indenting on a " range of lines. @@ -149,6 +149,15 @@ func HtmlIndent_CheckUserSettings() let b:html_indent_line_limit = 200 endif endif + + if exists('b:html_indent_attribute') + let b:hi_attr_indent = b:html_indent_attribute + elseif exists('g:html_indent_attribute') + let b:hi_attr_indent = g:html_indent_attribute + else + let b:hi_attr_indent = 2 + endif + endfunc "}}} " Init Script Vars @@ -946,11 +955,11 @@ func s:InsideTag(foundHtmlString) let idx = match(text, '<' . s:tagname . '\s\+\zs\w') endif if idx == -1 - " after just "<tag" indent two levels more + " after just "<tag" indent two levels more by default let idx = match(text, '<' . s:tagname . '$') if idx >= 0 call cursor(lnum, idx + 1) - return virtcol('.') - 1 + shiftwidth() * 2 + return virtcol('.') - 1 + shiftwidth() * b:hi_attr_indent endif endif if idx > 0 diff --git a/runtime/indent/qb64.vim b/runtime/indent/qb64.vim new file mode 100644 index 0000000000..09f815c43d --- /dev/null +++ b/runtime/indent/qb64.vim @@ -0,0 +1,11 @@ +" Vim indent file +" Language: QB64 +" Maintainer: Doug Kearns <dougkearns@gmail.com> +" Last Change: 2022 Jan 24 + +" Only load this indent file when no other was loaded. +if exists("b:did_indent") + finish +endif + +runtime! indent/vb.vim diff --git a/runtime/indent/testdir/html.in b/runtime/indent/testdir/html.in index 1acf8c0402..b62c67ddb2 100644 --- a/runtime/indent/testdir/html.in +++ b/runtime/indent/testdir/html.in @@ -1,4 +1,4 @@ -" vim: set ft=html sw=4 : +" vim: set ft=html sw=4 ts=8 : " START_INDENT @@ -41,6 +41,11 @@ dd text dt text </dt> </dl> +<div +class="test" +style="color: yellow"> +text +</div> </body> </html> @@ -50,6 +55,7 @@ dt text % START_INDENT % INDENT_EXE let g:html_indent_style1 = "inc" % INDENT_EXE let g:html_indent_script1 = "zero" +% INDENT_EXE let g:html_indent_attribute = 1 % INDENT_EXE call HtmlIndent_CheckUserSettings() <html> <body> @@ -61,6 +67,11 @@ div#d2 { color: green; } var v1 = "v1"; var v2 = "v2"; </script> +<div +class="test" +style="color: yellow"> +text +</div> </body> </html> % END_INDENT diff --git a/runtime/indent/testdir/html.ok b/runtime/indent/testdir/html.ok index c0dfc9dc72..938e965d8c 100644 --- a/runtime/indent/testdir/html.ok +++ b/runtime/indent/testdir/html.ok @@ -1,4 +1,4 @@ -" vim: set ft=html sw=4 : +" vim: set ft=html sw=4 ts=8 : " START_INDENT @@ -41,6 +41,11 @@ div#d2 { color: green; } dt text </dt> </dl> + <div + class="test" + style="color: yellow"> + text + </div> </body> </html> @@ -50,6 +55,7 @@ div#d2 { color: green; } % START_INDENT % INDENT_EXE let g:html_indent_style1 = "inc" % INDENT_EXE let g:html_indent_script1 = "zero" +% INDENT_EXE let g:html_indent_attribute = 1 % INDENT_EXE call HtmlIndent_CheckUserSettings() <html> <body> @@ -61,6 +67,11 @@ div#d2 { color: green; } var v1 = "v1"; var v2 = "v2"; </script> + <div + class="test" + style="color: yellow"> + text + </div> </body> </html> % END_INDENT diff --git a/runtime/indent/vim.vim b/runtime/indent/vim.vim index 7c03ff2873..f5a94940bd 100644 --- a/runtime/indent/vim.vim +++ b/runtime/indent/vim.vim @@ -1,7 +1,7 @@ " Vim indent file " Language: Vim script " Maintainer: Bram Moolenaar <Bram@vim.org> -" Last Change: 2021 Nov 27 +" Last Change: 2022 Feb 23 " Only load this indent file when no other was loaded. if exists("b:did_indent") @@ -10,7 +10,7 @@ endif let b:did_indent = 1 setlocal indentexpr=GetVimIndent() -setlocal indentkeys+==end,=},=else,=cat,=finall,=END,0\\,0=\"\\\ +setlocal indentkeys+==endif,=enddef,=endfu,=endfor,=endwh,=endtry,=},=else,=cat,=finall,=END,0\\,0=\"\\\ setlocal indentkeys-=0# setlocal indentkeys-=: @@ -103,10 +103,11 @@ function GetVimIndentIntern() " A line starting with :au does not increment/decrement indent. " A { may start a block or a dict. Assume that when a } follows it's a " terminated dict. + " ":function" starts a block but "function(" doesn't. if prev_text !~ '^\s*au\%[tocmd]' && prev_text !~ '^\s*{.*}' - let i = match(prev_text, '\(^\||\)\s*\(export\s\+\)\?\({\|\(if\|wh\%[ile]\|for\|try\|cat\%[ch]\|fina\|finall\%[y]\|fu\%[nction]\|def\|el\%[seif]\)\>\)') + let i = match(prev_text, '\(^\||\)\s*\(export\s\+\)\?\({\|\(if\|wh\%[ile]\|for\|try\|cat\%[ch]\|fina\|finall\%[y]\|def\|el\%[seif]\)\>\|fu\%[nction]\s\)') if i >= 0 - let ind += shiftwidth() + let ind += shiftwidth() if strpart(prev_text, i, 1) == '|' && has('syntax_items') \ && synIDattr(synID(lnum, i, 1), "name") =~ '\(Comment\|String\|PatSep\)$' let ind -= shiftwidth() @@ -170,10 +171,15 @@ function GetVimIndentIntern() let ind = ind + shiftwidth() endif - " Subtract a 'shiftwidth' on a :endif, :endwhile, :catch, :finally, :endtry, - " :endfun, :else and :augroup END. - if cur_text =~ '^\s*\(ene\@!\|cat\|finall\|el\|aug\%[roup]\s\+[eE][nN][dD]\)' + " Subtract a 'shiftwidth' on a :endif, :endwhile, :endfor, :catch, :finally, + " :endtry, :endfun, :enddef, :else and :augroup END. + " Although ":en" would be enough only match short command names as in + " 'indentkeys'. + if cur_text =~ '^\s*\(endif\|endwh\|endfor\|endtry\|endfu\|enddef\|cat\|finall\|else\|aug\%[roup]\s\+[eE][nN][dD]\)' let ind = ind - shiftwidth() + if ind < 0 + let ind = 0 + endif endif return ind diff --git a/runtime/lua/vim/_load_package.lua b/runtime/lua/vim/_load_package.lua new file mode 100644 index 0000000000..525f603438 --- /dev/null +++ b/runtime/lua/vim/_load_package.lua @@ -0,0 +1,49 @@ +-- prevents luacheck from making lints for setting things on vim +local vim = assert(vim) + +local pathtrails = {} +vim._so_trails = {} +for s in (package.cpath..';'):gmatch('[^;]*;') do + s = s:sub(1, -2) -- Strip trailing semicolon + -- Find out path patterns. pathtrail should contain something like + -- /?.so, \?.dll. This allows not to bother determining what correct + -- suffixes are. + local pathtrail = s:match('[/\\][^/\\]*%?.*$') + if pathtrail and not pathtrails[pathtrail] then + pathtrails[pathtrail] = true + table.insert(vim._so_trails, pathtrail) + end +end + +function vim._load_package(name) + local basename = name:gsub('%.', '/') + local paths = {"lua/"..basename..".lua", "lua/"..basename.."/init.lua"} + local found = vim.api.nvim__get_runtime(paths, false, {is_lua=true}) + if #found > 0 then + local f, err = loadfile(found[1]) + return f or error(err) + end + + local so_paths = {} + for _,trail in ipairs(vim._so_trails) do + local path = "lua"..trail:gsub('?', basename) -- so_trails contains a leading slash + table.insert(so_paths, path) + end + + found = vim.api.nvim__get_runtime(so_paths, false, {is_lua=true}) + if #found > 0 then + -- Making function name in Lua 5.1 (see src/loadlib.c:mkfuncname) is + -- a) strip prefix up to and including the first dash, if any + -- b) replace all dots by underscores + -- c) prepend "luaopen_" + -- So "foo-bar.baz" should result in "luaopen_bar_baz" + local dash = name:find("-", 1, true) + local modname = dash and name:sub(dash + 1) or name + local f, err = package.loadlib(found[1], "luaopen_"..modname:gsub("%.", "_")) + return f or error(err) + end + return nil +end + +-- Insert vim._load_package after the preloader at position 2 +table.insert(package.loaders, 2, vim._load_package) diff --git a/runtime/lua/vim/diagnostic.lua b/runtime/lua/vim/diagnostic.lua index 742ebf69b2..fcb1e61764 100644 --- a/runtime/lua/vim/diagnostic.lua +++ b/runtime/lua/vim/diagnostic.lua @@ -30,7 +30,7 @@ M.handlers = setmetatable({}, { __newindex = function(t, name, handler) vim.validate { handler = {handler, "t" } } rawset(t, name, handler) - if not global_diagnostic_options[name] then + if global_diagnostic_options[name] == nil then global_diagnostic_options[name] = true end end, @@ -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 @@ -552,17 +552,24 @@ end --- - `table`: Enable this feature with overrides. Use an empty table to use default values. --- - `function`: Function with signature (namespace, bufnr) that returns any of the above. --- ----@param opts table Configuration table with the following keys: +---@param opts table|nil When omitted or "nil", retrieve the current configuration. Otherwise, a +--- configuration table with the following keys: --- - underline: (default true) Use underline for diagnostics. Options: --- * severity: Only underline diagnostics matching the given severity --- |diagnostic-severity| ---- - virtual_text: (default true) Use virtual text for diagnostics. Options: +--- - virtual_text: (default true) Use virtual text for diagnostics. If multiple diagnostics +--- are set for a namespace, one prefix per diagnostic + the last diagnostic +--- message are shown. +--- Options: --- * severity: Only show virtual text for diagnostics matching the given --- severity |diagnostic-severity| --- * source: (boolean or string) Include the diagnostic source in virtual --- text. Use "if_many" to only show sources if there is more than --- one diagnostic source in the buffer. Otherwise, any truthy value --- means to always show the diagnostic source. +--- * spacing: (number) Amount of empty spaces inserted at the beginning +--- of the virtual text. +--- * prefix: (string) Prepend diagnostic message with prefix. --- * format: (function) A function that takes a diagnostic as input and --- returns a string. The return value is the text used to display --- the diagnostic. Example: @@ -593,7 +600,7 @@ end --- global diagnostic options. function M.config(opts, namespace) vim.validate { - opts = { opts, 't' }, + opts = { opts, 't', true }, namespace = { namespace, 'n', true }, } @@ -605,10 +612,13 @@ function M.config(opts, namespace) t = global_diagnostic_options end - for opt in pairs(global_diagnostic_options) do - if opts[opt] ~= nil then - t[opt] = opts[opt] - end + if not opts then + -- Return current config + return vim.deepcopy(t) + end + + for k, v in pairs(opts) do + t[k] = v end if namespace then @@ -638,7 +648,11 @@ function M.set(namespace, bufnr, diagnostics, opts) vim.validate { namespace = {namespace, 'n'}, bufnr = {bufnr, 'n'}, - diagnostics = {diagnostics, 't'}, + diagnostics = { + diagnostics, + vim.tbl_islist, + "a list of diagnostics", + }, opts = {opts, 't', true}, } @@ -804,11 +818,16 @@ M.handlers.signs = { vim.validate { namespace = {namespace, 'n'}, bufnr = {bufnr, 'n'}, - diagnostics = {diagnostics, 't'}, + diagnostics = { + diagnostics, + vim.tbl_islist, + "a list of diagnostics", + }, opts = {opts, 't', true}, } bufnr = get_bufnr(bufnr) + opts = opts or {} if opts.signs and opts.signs.severity then diagnostics = filter_by_severity(opts.signs.severity, diagnostics) @@ -867,11 +886,16 @@ M.handlers.underline = { vim.validate { namespace = {namespace, 'n'}, bufnr = {bufnr, 'n'}, - diagnostics = {diagnostics, 't'}, + diagnostics = { + diagnostics, + vim.tbl_islist, + "a list of diagnostics", + }, opts = {opts, 't', true}, } bufnr = get_bufnr(bufnr) + opts = opts or {} if opts.underline and opts.underline.severity then diagnostics = filter_by_severity(opts.underline.severity, diagnostics) @@ -896,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) @@ -915,11 +940,16 @@ M.handlers.virtual_text = { vim.validate { namespace = {namespace, 'n'}, bufnr = {bufnr, 'n'}, - diagnostics = {diagnostics, 't'}, + diagnostics = { + diagnostics, + vim.tbl_islist, + "a list of diagnostics", + }, opts = {opts, 't', true}, } bufnr = get_bufnr(bufnr) + opts = opts or {} local severity if opts.virtual_text then @@ -1075,7 +1105,11 @@ function M.show(namespace, bufnr, diagnostics, opts) vim.validate { namespace = { namespace, 'n', true }, bufnr = { bufnr, 'n', true }, - diagnostics = { diagnostics, 't', true }, + diagnostics = { + diagnostics, + function(v) return v == nil or vim.tbl_islist(v) end, + "a list of diagnostics", + }, opts = { opts, 't', true }, } @@ -1346,16 +1380,16 @@ function M.reset(namespace, bufnr) diagnostic_cache[iter_bufnr][iter_namespace] = nil M.hide(iter_namespace, iter_bufnr) end - end - vim.api.nvim_buf_call(bufnr, function() - vim.api.nvim_command( - string.format( - "doautocmd <nomodeline> DiagnosticChanged %s", - vim.fn.fnameescape(vim.api.nvim_buf_get_name(bufnr)) + vim.api.nvim_buf_call(iter_bufnr, function() + vim.api.nvim_command( + string.format( + "doautocmd <nomodeline> DiagnosticChanged %s", + vim.fn.fnameescape(vim.api.nvim_buf_get_name(iter_bufnr)) + ) ) - ) - end) + end) + end end --- Add all diagnostics to the quickfix list. @@ -1520,7 +1554,13 @@ local errlist_type_map = { ---@param diagnostics table List of diagnostics |diagnostic-structure|. ---@return array of quickfix list items |setqflist-what| function M.toqflist(diagnostics) - vim.validate { diagnostics = {diagnostics, 't'} } + vim.validate { + diagnostics = { + diagnostics, + vim.tbl_islist, + "a list of diagnostics", + }, + } local list = {} for _, v in ipairs(diagnostics) do @@ -1551,7 +1591,13 @@ end --- |getloclist()|. ---@return array of diagnostics |diagnostic-structure| function M.fromqflist(list) - vim.validate { list = {list, 't'} } + vim.validate { + list = { + list, + vim.tbl_islist, + "a list of quickfix items", + }, + } local diagnostics = {} for _, item in ipairs(list) do diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua new file mode 100644 index 0000000000..f5e4dabfb6 --- /dev/null +++ b/runtime/lua/vim/filetype.lua @@ -0,0 +1,1625 @@ +local api = vim.api + +local M = {} + +---@private +local function starsetf(ft) + return {function(path) + if not vim.g.fg_ignore_pat then + return ft + end + + local re = vim.regex(vim.g.fg_ignore_pat) + if re:match_str(path) then + return ft + end + end, { + -- Starset matches should always have lowest priority + priority = -math.huge, + }} +end + +---@private +local function getline(bufnr, lnum) + return api.nvim_buf_get_lines(bufnr, lnum-1, lnum, false)[1] +end + +-- Filetypes based on file extension +-- luacheck: push no unused args +local extension = { + -- BEGIN EXTENSION + ["8th"] = "8th", + ["a65"] = "a65", + aap = "aap", + abap = "abap", + abc = "abc", + abl = "abel", + wrm = "acedb", + ads = "ada", + ada = "ada", + gpr = "ada", + adb = "ada", + tdf = "ahdl", + aidl = "aidl", + aml = "aml", + run = "ampl", + scpt = "applescript", + ino = "arduino", + pde = "arduino", + art = "art", + asciidoc = "asciidoc", + adoc = "asciidoc", + ["asn1"] = "asn", + asn = "asn", + atl = "atlas", + as = "atlas", + ahk = "autohotkey", + ["au3"] = "autoit", + ave = "ave", + gawk = "awk", + awk = "awk", + ref = "b", + imp = "b", + mch = "b", + bc = "bc", + bdf = "bdf", + beancount = "beancount", + bib = "bib", + bicep = "bicep", + bl = "blank", + bsdl = "bsdl", + bst = "bst", + bzl = "bzl", + bazel = "bzl", + BUILD = "bzl", + qc = "c", + cabal = "cabal", + cdl = "cdl", + toc = "cdrtoc", + cfc = "cf", + cfm = "cf", + cfi = "cf", + cfg = "cfg", + hgrc = "cfg", + chf = "ch", + chai = "chaiscript", + chs = "chaskell", + chopro = "chordpro", + crd = "chordpro", + crdpro = "chordpro", + cho = "chordpro", + chordpro = "chordpro", + eni = "cl", + dcl = "clean", + icl = "clean", + cljx = "clojure", + clj = "clojure", + cljc = "clojure", + cljs = "clojure", + cmake = "cmake", + cmod = "cmod", + lib = "cobol", + cob = "cobol", + cbl = "cobol", + atg = "coco", + recipe = "conaryrecipe", + mklx = "context", + mkiv = "context", + mkii = "context", + mkxl = "context", + mkvi = "context", + moc = "cpp", + hh = "cpp", + tlh = "cpp", + inl = "cpp", + ipp = "cpp", + ["c++"] = "cpp", + C = "cpp", + cxx = "cpp", + H = "cpp", + tcc = "cpp", + hxx = "cpp", + hpp = "cpp", + cpp = function(path, bufnr) + if vim.g.cynlib_syntax_for_cc then + return "cynlib" + end + return "cpp" + end, + cc = function(path, bufnr) + if vim.g.cynlib_syntax_for_cc then + return "cynlib" + end + return "cpp" + end, + crm = "crm", + csx = "cs", + cs = "cs", + csc = "csc", + csdl = "csdl", + fdr = "csp", + csp = "csp", + css = "css", + con = "cterm", + feature = "cucumber", + cuh = "cuda", + cu = "cuda", + pld = "cupl", + si = "cuplsim", + cyn = "cynpp", + dart = "dart", + drt = "dart", + ds = "datascript", + dcd = "dcd", + def = "def", + desc = "desc", + directory = "desktop", + desktop = "desktop", + diff = "diff", + rej = "diff", + Dockerfile = "dockerfile", + sys = "dosbatch", + bat = "dosbatch", + wrap = "dosini", + ini = "dosini", + dot = "dot", + gv = "dot", + drac = "dracula", + drc = "dracula", + dtd = "dtd", + dts = "dts", + dtsi = "dts", + dylan = "dylan", + intr = "dylanintr", + lid = "dylanlid", + ecd = "ecd", + eex = "eelixir", + leex = "eelixir", + exs = "elixir", + elm = "elm", + epp = "epuppet", + erl = "erlang", + hrl = "erlang", + yaws = "erlang", + erb = "eruby", + rhtml = "eruby", + ec = "esqlc", + EC = "esqlc", + strl = "esterel", + exp = "expect", + factor = "factor", + fal = "falcon", + fan = "fan", + fwt = "fan", + fnl = "fennel", + ["m4gl"] = "fgl", + ["4gl"] = "fgl", + ["4gh"] = "fgl", + fish = "fish", + focexec = "focexec", + fex = "focexec", + fth = "forth", + ft = "forth", + FOR = "fortran", + ["f77"] = "fortran", + ["f03"] = "fortran", + fortran = "fortran", + ["F95"] = "fortran", + ["f90"] = "fortran", + ["F03"] = "fortran", + fpp = "fortran", + FTN = "fortran", + ftn = "fortran", + ["for"] = "fortran", + ["F90"] = "fortran", + ["F77"] = "fortran", + ["f95"] = "fortran", + FPP = "fortran", + f = "fortran", + F = "fortran", + ["F08"] = "fortran", + ["f08"] = "fortran", + fpc = "fpcmake", + fsl = "framescript", + fb = "freebasic", + fsi = "fsharp", + fsx = "fsharp", + fusion = "fusion", + gdmo = "gdmo", + mo = "gdmo", + tres = "gdresource", + tscn = "gdresource", + gd = "gdscript", + ged = "gedcom", + gmi = "gemtext", + gemini = "gemtext", + gift = "gift", + glsl = "glsl", + gpi = "gnuplot", + gnuplot = "gnuplot", + go = "go", + gp = "gp", + gs = "grads", + gql = "graphql", + graphql = "graphql", + graphqls = "graphql", + gretl = "gretl", + gradle = "groovy", + groovy = "groovy", + gsp = "gsp", + gjs = "javascript.glimmer", + gts = "typescript.glimmer", + hack = "hack", + hackpartial = "hack", + haml = "haml", + hsm = "hamster", + hbs = "handlebars", + ["hs-boot"] = "haskell", + hsig = "haskell", + hsc = "haskell", + hs = "haskell", + ht = "haste", + htpp = "hastepreproc", + hb = "hb", + sum = "hercules", + errsum = "hercules", + ev = "hercules", + vc = "hercules", + hcl = "hcl", + heex = "heex", + hex = "hex", + ["h32"] = "hex", + hjson = "hjson", + hog = "hog", + hws = "hollywood", + htt = "httest", + htb = "httest", + iba = "ibasic", + ibi = "ibasic", + icn = "icon", + inf = "inform", + INF = "inform", + ii = "initng", + iss = "iss", + mst = "ist", + ist = "ist", + ijs = "j", + JAL = "jal", + jal = "jal", + jpr = "jam", + jpl = "jam", + jav = "java", + java = "java", + jj = "javacc", + jjt = "javacc", + es = "javascript", + mjs = "javascript", + javascript = "javascript", + js = "javascript", + cjs = "javascript", + jsx = "javascriptreact", + clp = "jess", + jgr = "jgraph", + ["j73"] = "jovial", + jov = "jovial", + jovial = "jovial", + properties = "jproperties", + slnf = "json", + json = "json", + jsonp = "json", + webmanifest = "json", + ipynb = "json", + ["json-patch"] = "json", + json5 = "json5", + jsonc = "jsonc", + jsp = "jsp", + jl = "julia", + kv = "kivy", + kix = "kix", + kts = "kotlin", + kt = "kotlin", + ktm = "kotlin", + ks = "kscript", + k = "kwt", + ACE = "lace", + ace = "lace", + latte = "latte", + lte = "latte", + ld = "ld", + ldif = "ldif", + journal = "ledger", + ldg = "ledger", + ledger = "ledger", + less = "less", + lex = "lex", + lxx = "lex", + ["l++"] = "lex", + l = "lex", + lhs = "lhaskell", + ll = "lifelines", + liquid = "liquid", + cl = "lisp", + L = "lisp", + lisp = "lisp", + el = "lisp", + lsp = "lisp", + asd = "lisp", + lt = "lite", + lite = "lite", + lgt = "logtalk", + lotos = "lotos", + lot = "lotos", + lout = "lout", + lou = "lout", + ulpc = "lpc", + lpc = "lpc", + sig = "lprolog", + lsl = "lsl", + lss = "lss", + nse = "lua", + rockspec = "lua", + lua = "lua", + quake = "m3quake", + at = "m4", + eml = "mail", + mk = "make", + mak = "make", + dsp = "make", + page = "mallard", + map = "map", + mws = "maple", + mpl = "maple", + mv = "maple", + mkdn = "markdown", + md = "markdown", + mdwn = "markdown", + mkd = "markdown", + markdown = "markdown", + mdown = "markdown", + mhtml = "mason", + comp = "mason", + mason = "mason", + master = "master", + mas = "master", + mel = "mel", + mf = "mf", + mgl = "mgl", + mgp = "mgp", + my = "mib", + mib = "mib", + mix = "mix", + mixal = "mix", + nb = "mma", + mmp = "mmp", + DEF = "modula2", + ["m2"] = "modula2", + MOD = "modula2", + mi = "modula2", + ssc = "monk", + monk = "monk", + tsc = "monk", + isc = "monk", + moo = "moo", + mp = "mp", + mof = "msidl", + odl = "msidl", + msql = "msql", + mu = "mupad", + mush = "mush", + mysql = "mysql", + ["n1ql"] = "n1ql", + nql = "n1ql", + nanorc = "nanorc", + ncf = "ncf", + nginx = "nginx", + ninja = "ninja", + nix = "nix", + nqc = "nqc", + roff = "nroff", + tmac = "nroff", + man = "nroff", + mom = "nroff", + nr = "nroff", + tr = "nroff", + nsi = "nsis", + nsh = "nsis", + obj = "obj", + mlt = "ocaml", + mly = "ocaml", + mll = "ocaml", + mlp = "ocaml", + mlip = "ocaml", + mli = "ocaml", + ml = "ocaml", + occ = "occam", + xom = "omnimark", + xin = "omnimark", + opam = "opam", + ["or"] = "openroad", + ora = "ora", + pxsl = "papp", + papp = "papp", + pxml = "papp", + pas = "pascal", + lpr = "pascal", + dpr = "pascal", + pbtxt = "pbtxt", + g = "pccts", + pcmk = "pcmk", + pdf = "pdf", + plx = "perl", + prisma = "prisma", + psgi = "perl", + al = "perl", + ctp = "php", + php = "php", + phtml = "php", + pike = "pike", + pmod = "pike", + rcp = "pilrc", + pli = "pli", + ["pl1"] = "pli", + ["p36"] = "plm", + plm = "plm", + pac = "plm", + plp = "plp", + pls = "plsql", + plsql = "plsql", + po = "po", + pot = "po", + pod = "pod", + pk = "poke", + ps = "postscr", + epsi = "postscr", + afm = "postscr", + epsf = "postscr", + eps = "postscr", + pfa = "postscr", + ai = "postscr", + pov = "pov", + ppd = "ppd", + it = "ppwiz", + ih = "ppwiz", + action = "privoxy", + pc = "proc", + pdb = "prolog", + pml = "promela", + proto = "proto", + ["psd1"] = "ps1", + ["psm1"] = "ps1", + ["ps1"] = "ps1", + pssc = "ps1", + ["ps1xml"] = "ps1xml", + psf = "psf", + psl = "psl", + pug = "pug", + arr = "pyret", + pxd = "pyrex", + pyx = "pyrex", + pyw = "python", + py = "python", + pyi = "python", + ptl = "python", + ql = "ql", + qll = "ql", + rad = "radiance", + mat = "radiance", + ["pod6"] = "raku", + rakudoc = "raku", + rakutest = "raku", + rakumod = "raku", + ["pm6"] = "raku", + raku = "raku", + ["t6"] = "raku", + ["p6"] = "raku", + raml = "raml", + rbs = "rbs", + rego = "rego", + rem = "remind", + remind = "remind", + res = "rescript", + resi = "rescript", + frt = "reva", + testUnit = "rexx", + rex = "rexx", + orx = "rexx", + rexx = "rexx", + jrexx = "rexx", + rxj = "rexx", + rexxj = "rexx", + testGroup = "rexx", + rxo = "rexx", + Rd = "rhelp", + rd = "rhelp", + rib = "rib", + Rmd = "rmd", + rmd = "rmd", + smd = "rmd", + Smd = "rmd", + rnc = "rnc", + rng = "rng", + rnw = "rnoweb", + snw = "rnoweb", + Rnw = "rnoweb", + Snw = "rnoweb", + rsc = "routeros", + x = "rpcgen", + rpl = "rpl", + Srst = "rrst", + srst = "rrst", + Rrst = "rrst", + rrst = "rrst", + rst = "rst", + rtf = "rtf", + rjs = "ruby", + rxml = "ruby", + rb = "ruby", + rant = "ruby", + ru = "ruby", + rbw = "ruby", + gemspec = "ruby", + builder = "ruby", + rake = "ruby", + rs = "rust", + sas = "sas", + sass = "sass", + sa = "sather", + sbt = "sbt", + scala = "scala", + sc = "scala", + scd = "scdoc", + ss = "scheme", + scm = "scheme", + sld = "scheme", + rkt = "scheme", + rktd = "scheme", + rktl = "scheme", + sce = "scilab", + sci = "scilab", + scss = "scss", + sd = "sd", + sdc = "sdc", + pr = "sdl", + sdl = "sdl", + sed = "sed", + sexp = "sexplib", + sieve = "sieve", + siv = "sieve", + sil = "sil", + sim = "simula", + ["s85"] = "sinda", + sin = "sinda", + ssm = "sisu", + sst = "sisu", + ssi = "sisu", + ["_sst"] = "sisu", + ["-sst"] = "sisu", + il = "skill", + ils = "skill", + cdf = "skill", + sl = "slang", + ice = "slice", + score = "slrnsc", + sol = "solidity", + tpl = "smarty", + ihlp = "smcl", + smcl = "smcl", + hlp = "smcl", + smith = "smith", + smt = "smith", + sml = "sml", + spt = "snobol4", + sno = "snobol4", + sln = "solution", + sparql = "sparql", + rq = "sparql", + spec = "spec", + spice = "spice", + sp = "spice", + spd = "spup", + spdata = "spup", + speedup = "spup", + spi = "spyce", + spy = "spyce", + tyc = "sql", + typ = "sql", + pkb = "sql", + tyb = "sql", + pks = "sql", + sqlj = "sqlj", + sqi = "sqr", + sqr = "sqr", + nut = "squirrel", + ["s28"] = "srec", + ["s37"] = "srec", + srec = "srec", + mot = "srec", + ["s19"] = "srec", + st = "st", + imata = "stata", + ["do"] = "stata", + mata = "stata", + ado = "stata", + stp = "stp", + sface = "surface", + svelte = "svelte", + svg = "svg", + swift = "swift", + svh = "systemverilog", + sv = "systemverilog", + tak = "tak", + task = "taskedit", + tm = "tcl", + tcl = "tcl", + itk = "tcl", + itcl = "tcl", + tk = "tcl", + jacl = "tcl", + tl = "teal", + tmpl = "template", + ti = "terminfo", + dtx = "tex", + ltx = "tex", + bbl = "tex", + latex = "tex", + sty = "tex", + texi = "texinfo", + txi = "texinfo", + texinfo = "texinfo", + text = "text", + tfvars = "terraform", + tla = "tla", + tli = "tli", + toml = "toml", + tpp = "tpp", + treetop = "treetop", + slt = "tsalt", + tsscl = "tsscl", + tssgm = "tssgm", + tssop = "tssop", + tutor = "tutor", + twig = "twig", + ts = function(path, bufnr) + if getline(bufnr, 1):find("<%?xml") then + return "xml" + else + return "typescript" + end + end, + tsx = "typescriptreact", + uc = "uc", + uit = "uil", + uil = "uil", + sba = "vb", + vb = "vb", + dsm = "vb", + ctl = "vb", + vbs = "vb", + vr = "vera", + vri = "vera", + vrh = "vera", + v = "verilog", + va = "verilogams", + vams = "verilogams", + vhdl = "vhdl", + vst = "vhdl", + vhd = "vhdl", + hdl = "vhdl", + vho = "vhdl", + vbe = "vhdl", + vim = "vim", + vba = "vim", + mar = "vmasm", + cm = "voscm", + wrl = "vrml", + vroom = "vroom", + vue = "vue", + wat = "wast", + wast = "wast", + wm = "webmacro", + wbt = "winbatch", + wml = "wml", + wsml = "wsml", + ad = "xdefaults", + xhtml = "xhtml", + xht = "xhtml", + msc = "xmath", + msf = "xmath", + ["psc1"] = "xml", + tpm = "xml", + xliff = "xml", + atom = "xml", + xul = "xml", + cdxml = "xml", + mpd = "xml", + rss = "xml", + fsproj = "xml", + ui = "xml", + vbproj = "xml", + xlf = "xml", + wsdl = "xml", + csproj = "xml", + wpl = "xml", + xmi = "xml", + ["xpm2"] = "xpm2", + xqy = "xquery", + xqm = "xquery", + xquery = "xquery", + xq = "xquery", + xql = "xquery", + xs = "xs", + xsd = "xsd", + xsl = "xslt", + xslt = "xslt", + yy = "yacc", + ["y++"] = "yacc", + yxx = "yacc", + yml = "yaml", + yaml = "yaml", + yang = "yang", + ["z8a"] = "z8a", + zig = "zig", + zu = "zimbu", + zut = "zimbutempl", + zsh = "zsh", + vala = "vala", + E = function() vim.fn["dist#ft#FTe"]() end, + EU = function() vim.fn["dist#ft#EuphoriaCheck"]() end, + EW = function() vim.fn["dist#ft#EuphoriaCheck"]() end, + EX = function() vim.fn["dist#ft#EuphoriaCheck"]() end, + EXU = function() vim.fn["dist#ft#EuphoriaCheck"]() end, + EXW = function() vim.fn["dist#ft#EuphoriaCheck"]() end, + PL = function() vim.fn["dist#ft#FTpl"]() end, + R = function() vim.fn["dist#ft#FTr"]() end, + asm = function() vim.fn["dist#ft#FTasm"]() end, + bas = function() vim.fn["dist#ft#FTbas"]() end, + bi = function() vim.fn["dist#ft#FTbas"]() end, + bm = function() vim.fn["dist#ft#FTbas"]() end, + bash = function() vim.fn["dist#ft#SetFileTypeSH"]("bash") end, + btm = function() vim.fn["dist#ft#FTbtm"]() end, + c = function() vim.fn["dist#ft#FTlpc"]() end, + ch = function() vim.fn["dist#ft#FTchange"]() end, + com = function() vim.fn["dist#ft#BindzoneCheck"]('dcl') end, + cpt = function() vim.fn["dist#ft#FThtml"]() end, + csh = function() vim.fn["dist#ft#CSH"]() end, + d = function() vim.fn["dist#ft#DtraceCheck"]() end, + db = function() vim.fn["dist#ft#BindzoneCheck"]('') end, + dtml = function() vim.fn["dist#ft#FThtml"]() end, + e = function() vim.fn["dist#ft#FTe"]() end, + ebuild = function() vim.fn["dist#ft#SetFileTypeSH"]("bash") end, + eclass = function() vim.fn["dist#ft#SetFileTypeSH"]("bash") end, + ent = function() vim.fn["dist#ft#FTent"]() end, + env = function() vim.fn["dist#ft#SetFileTypeSH"](vim.fn.getline(1)) end, + eu = function() vim.fn["dist#ft#EuphoriaCheck"]() end, + ew = function() vim.fn["dist#ft#EuphoriaCheck"]() end, + ex = function() vim.fn["dist#ft#ExCheck"]() end, + exu = function() vim.fn["dist#ft#EuphoriaCheck"]() end, + exw = function() vim.fn["dist#ft#EuphoriaCheck"]() end, + frm = function() vim.fn["dist#ft#FTfrm"]() end, + fs = function() vim.fn["dist#ft#FTfs"]() end, + h = function() vim.fn["dist#ft#FTheader"]() end, + htm = function() vim.fn["dist#ft#FThtml"]() end, + html = function() vim.fn["dist#ft#FThtml"]() end, + i = function() vim.fn["dist#ft#FTprogress_asm"]() end, + idl = function() vim.fn["dist#ft#FTidl"]() end, + inc = function() vim.fn["dist#ft#FTinc"]() end, + inp = function() vim.fn["dist#ft#Check_inp"]() end, + ksh = function() vim.fn["dist#ft#SetFileTypeSH"]("ksh") end, + lst = function() vim.fn["dist#ft#FTasm"]() end, + m = function() vim.fn["dist#ft#FTm"]() end, + mac = function() vim.fn["dist#ft#FTasm"]() end, + mc = function() vim.fn["dist#ft#McSetf"]() end, + mm = function() vim.fn["dist#ft#FTmm"]() end, + mms = function() vim.fn["dist#ft#FTmms"]() end, + p = function() vim.fn["dist#ft#FTprogress_pascal"]() end, + patch = function(path, bufnr) + local firstline = getline(bufnr, 1) + if string.find(firstline, "^From " .. string.rep("%x", 40) .. "+ Mon Sep 17 00:00:00 2001$") then + return "gitsendemail" + else + return "diff" + end + end, + pl = function() vim.fn["dist#ft#FTpl"]() end, + pp = function() vim.fn["dist#ft#FTpp"]() end, + pro = function() vim.fn["dist#ft#ProtoCheck"]('idlang') end, + pt = function() vim.fn["dist#ft#FThtml"]() end, + r = function() vim.fn["dist#ft#FTr"]() end, + rdf = function() vim.fn["dist#ft#Redif"]() end, + rules = function() vim.fn["dist#ft#FTRules"]() end, + sh = function() vim.fn["dist#ft#SetFileTypeSH"](vim.fn.getline(1)) end, + shtml = function() vim.fn["dist#ft#FThtml"]() end, + sql = function() vim.fn["dist#ft#SQL"]() end, + stm = function() vim.fn["dist#ft#FThtml"]() end, + tcsh = function() vim.fn["dist#ft#SetFileTypeShell"]("tcsh") end, + tex = function() vim.fn["dist#ft#FTtex"]() end, + tf = function() vim.fn["dist#ft#FTtf"]() end, + w = function() vim.fn["dist#ft#FTprogress_cweb"]() end, + xml = function() vim.fn["dist#ft#FTxml"]() end, + y = function() vim.fn["dist#ft#FTy"]() end, + zsql = function() vim.fn["dist#ft#SQL"]() end, + txt = function(path, bufnr) + --helpfiles match *.txt, but should have a modeline as last line + if not getline(bufnr, -1):match("vim:.*ft=help") then + return "text" + end + end, + -- END EXTENSION +} + +local filename = { + -- BEGIN FILENAME + ["a2psrc"] = "a2ps", + ["/etc/a2ps.cfg"] = "a2ps", + [".a2psrc"] = "a2ps", + [".asoundrc"] = "alsaconf", + ["/usr/share/alsa/alsa.conf"] = "alsaconf", + ["/etc/asound.conf"] = "alsaconf", + ["build.xml"] = "ant", + [".htaccess"] = "apache", + ["apt.conf"] = "aptconf", + ["/.aptitude/config"] = "aptconf", + ["=tagging-method"] = "arch", + [".arch-inventory"] = "arch", + ["GNUmakefile.am"] = "automake", + ["named.root"] = "bindzone", + WORKSPACE = "bzl", + BUILD = "bzl", + ["cabal.config"] = "cabalconfig", + ["cabal.project"] = "cabalproject", + calendar = "calendar", + catalog = "catalog", + ["/etc/cdrdao.conf"] = "cdrdaoconf", + [".cdrdao"] = "cdrdaoconf", + ["/etc/default/cdrdao"] = "cdrdaoconf", + ["/etc/defaults/cdrdao"] = "cdrdaoconf", + ["cfengine.conf"] = "cfengine", + ["CMakeLists.txt"] = "cmake", + ["auto.master"] = "conf", + ["configure.in"] = "config", + ["configure.ac"] = "config", + [".cvsrc"] = "cvsrc", + ["/debian/changelog"] = "debchangelog", + ["changelog.dch"] = "debchangelog", + ["changelog.Debian"] = "debchangelog", + ["NEWS.dch"] = "debchangelog", + ["NEWS.Debian"] = "debchangelog", + ["/debian/control"] = "debcontrol", + ["/debian/copyright"] = "debcopyright", + ["/etc/apt/sources.list"] = "debsources", + ["denyhosts.conf"] = "denyhosts", + ["dict.conf"] = "dictconf", + [".dictrc"] = "dictconf", + ["/etc/DIR_COLORS"] = "dircolors", + [".dir_colors"] = "dircolors", + [".dircolors"] = "dircolors", + ["/etc/dnsmasq.conf"] = "dnsmasq", + Containerfile = "dockerfile", + Dockerfile = "dockerfile", + npmrc = "dosini", + ["/etc/yum.conf"] = "dosini", + ["/etc/pacman.conf"] = "dosini", + [".npmrc"] = "dosini", + [".editorconfig"] = "dosini", + dune = "dune", + jbuild = "dune", + ["dune-workspace"] = "dune", + ["dune-project"] = "dune", + ["elinks.conf"] = "elinks", + ["mix.lock"] = "elixir", + ["filter-rules"] = "elmfilt", + ["exim.conf"] = "exim", + exports = "exports", + [".fetchmailrc"] = "fetchmail", + fvSchemes = function() vim.fn["dist#ft#FTfoam"]() end, + fvSolution = function() vim.fn["dist#ft#FTfoam"]() end, + fvConstraints = function() vim.fn["dist#ft#FTfoam"]() end, + fvModels = function() vim.fn["dist#ft#FTfoam"]() end, + fstab = "fstab", + mtab = "fstab", + [".gdbinit"] = "gdb", + gdbinit = "gdb", + ["lltxxxxx.txt"] = "gedcom", + ["TAG_EDITMSG"] = "gitcommit", + ["MERGE_MSG"] = "gitcommit", + ["COMMIT_EDITMSG"] = "gitcommit", + ["NOTES_EDITMSG"] = "gitcommit", + ["EDIT_DESCRIPTION"] = "gitcommit", + [".gitconfig"] = "gitconfig", + [".gitmodules"] = "gitconfig", + ["gitolite.conf"] = "gitolite", + ["git-rebase-todo"] = "gitrebase", + gkrellmrc = "gkrellmrc", + [".gnashrc"] = "gnash", + [".gnashpluginrc"] = "gnash", + gnashpluginrc = "gnash", + gnashrc = "gnash", + ["go.work"] = "gowork", + [".gprc"] = "gp", + ["/.gnupg/gpg.conf"] = "gpg", + ["/.gnupg/options"] = "gpg", + ["/var/backups/gshadow.bak"] = "group", + ["/etc/gshadow"] = "group", + ["/etc/group-"] = "group", + ["/etc/gshadow.edit"] = "group", + ["/etc/gshadow-"] = "group", + ["/etc/group"] = "group", + ["/var/backups/group.bak"] = "group", + ["/etc/group.edit"] = "group", + ["/boot/grub/menu.lst"] = "grub", + ["/etc/grub.conf"] = "grub", + ["/boot/grub/grub.conf"] = "grub", + [".gtkrc"] = "gtkrc", + gtkrc = "gtkrc", + ["snort.conf"] = "hog", + ["vision.conf"] = "hog", + ["/etc/host.conf"] = "hostconf", + ["/etc/hosts.allow"] = "hostsaccess", + ["/etc/hosts.deny"] = "hostsaccess", + ["/i3/config"] = "i3config", + ["/sway/config"] = "i3config", + ["/.sway/config"] = "i3config", + ["/.i3/config"] = "i3config", + ["/.icewm/menu"] = "icemenu", + [".indent.pro"] = "indent", + indentrc = "indent", + inittab = "inittab", + ["ipf.conf"] = "ipfilter", + ["ipf6.conf"] = "ipfilter", + ["ipf.rules"] = "ipfilter", + [".eslintrc"] = "json", + [".babelrc"] = "json", + ["Pipfile.lock"] = "json", + [".firebaserc"] = "json", + [".prettierrc"] = "json", + Kconfig = "kconfig", + ["Kconfig.debug"] = "kconfig", + ["lftp.conf"] = "lftp", + [".lftprc"] = "lftp", + ["/.libao"] = "libao", + ["/etc/libao.conf"] = "libao", + ["lilo.conf"] = "lilo", + ["/etc/limits"] = "limits", + [".emacs"] = "lisp", + sbclrc = "lisp", + [".sbclrc"] = "lisp", + [".sawfishrc"] = "lisp", + ["/etc/login.access"] = "loginaccess", + ["/etc/login.defs"] = "logindefs", + ["lynx.cfg"] = "lynx", + ["m3overrides"] = "m3build", + ["m3makefile"] = "m3build", + ["cm3.cfg"] = "m3quake", + [".followup"] = "mail", + [".article"] = "mail", + [".letter"] = "mail", + ["/etc/aliases"] = "mailaliases", + ["/etc/mail/aliases"] = "mailaliases", + mailcap = "mailcap", + [".mailcap"] = "mailcap", + ["/etc/man.conf"] = "manconf", + ["man.config"] = "manconf", + ["meson.build"] = "meson", + ["meson_options.txt"] = "meson", + ["/etc/conf.modules"] = "modconf", + ["/etc/modules"] = "modconf", + ["/etc/modules.conf"] = "modconf", + ["/.mplayer/config"] = "mplayerconf", + ["mplayer.conf"] = "mplayerconf", + mrxvtrc = "mrxvtrc", + [".mrxvtrc"] = "mrxvtrc", + ["/etc/nanorc"] = "nanorc", + Neomuttrc = "neomuttrc", + [".netrc"] = "netrc", + [".ocamlinit"] = "ocaml", + [".octaverc"] = "octave", + octaverc = "octave", + ["octave.conf"] = "octave", + opam = "opam", + ["/etc/pam.conf"] = "pamconf", + ["pam_env.conf"] = "pamenv", + [".pam_environment"] = "pamenv", + ["/var/backups/passwd.bak"] = "passwd", + ["/var/backups/shadow.bak"] = "passwd", + ["/etc/passwd"] = "passwd", + ["/etc/passwd-"] = "passwd", + ["/etc/shadow.edit"] = "passwd", + ["/etc/shadow-"] = "passwd", + ["/etc/shadow"] = "passwd", + ["/etc/passwd.edit"] = "passwd", + ["pf.conf"] = "pf", + ["main.cf"] = "pfmain", + pinerc = "pine", + [".pinercex"] = "pine", + [".pinerc"] = "pine", + pinercex = "pine", + ["/etc/pinforc"] = "pinfo", + ["/.pinforc"] = "pinfo", + [".povrayrc"] = "povini", + [".procmailrc"] = "procmail", + [".procmail"] = "procmail", + ["/etc/protocols"] = "protocols", + [".pythonstartup"] = "python", + [".pythonrc"] = "python", + SConstruct = "python", + ratpoisonrc = "ratpoison", + [".ratpoisonrc"] = "ratpoison", + v = "rcs", + inputrc = "readline", + [".inputrc"] = "readline", + [".reminders"] = "remind", + ["resolv.conf"] = "resolv", + ["robots.txt"] = "robots", + Gemfile = "ruby", + Puppetfile = "ruby", + [".irbrc"] = "ruby", + irbrc = "ruby", + ["smb.conf"] = "samba", + screenrc = "screen", + [".screenrc"] = "screen", + ["/etc/sensors3.conf"] = "sensors", + ["/etc/sensors.conf"] = "sensors", + ["/etc/services"] = "services", + ["/etc/serial.conf"] = "setserial", + ["/etc/udev/cdsymlinks.conf"] = "sh", + ["/etc/slp.conf"] = "slpconf", + ["/etc/slp.reg"] = "slpreg", + ["/etc/slp.spi"] = "slpspi", + [".slrnrc"] = "slrnrc", + ["sendmail.cf"] = "sm", + ["squid.conf"] = "squid", + ["/.ssh/config"] = "sshconfig", + ["ssh_config"] = "sshconfig", + ["sshd_config"] = "sshdconfig", + ["/etc/sudoers"] = "sudoers", + ["sudoers.tmp"] = "sudoers", + ["/etc/sysctl.conf"] = "sysctl", + tags = "tags", + [".tclshrc"] = "tcl", + [".wishrc"] = "tcl", + ["tclsh.rc"] = "tcl", + ["texmf.cnf"] = "texmf", + COPYING = "text", + README = "text", + LICENSE = "text", + AUTHORS = "text", + tfrc = "tf", + [".tfrc"] = "tf", + ["tidy.conf"] = "tidy", + tidyrc = "tidy", + [".tidyrc"] = "tidy", + [".tmux.conf"] = "tmux", + ["/.cargo/config"] = "toml", + Pipfile = "toml", + ["Gopkg.lock"] = "toml", + ["/.cargo/credentials"] = "toml", + ["Cargo.lock"] = "toml", + ["trustees.conf"] = "trustees", + ["/etc/udev/udev.conf"] = "udevconf", + ["/etc/updatedb.conf"] = "updatedb", + ["fdrupstream.log"] = "upstreamlog", + vgrindefs = "vgrindefs", + [".exrc"] = "vim", + ["_exrc"] = "vim", + ["_viminfo"] = "viminfo", + [".viminfo"] = "viminfo", + [".wgetrc"] = "wget", + wgetrc = "wget", + [".wvdialrc"] = "wvdial", + ["wvdial.conf"] = "wvdial", + [".Xresources"] = "xdefaults", + [".Xpdefaults"] = "xdefaults", + ["xdm-config"] = "xdefaults", + [".Xdefaults"] = "xdefaults", + ["/etc/xinetd.conf"] = "xinetd", + fglrxrc = "xml", + ["/etc/blkid.tab"] = "xml", + ["/etc/blkid.tab.old"] = "xml", + ["/etc/zprofile"] = "zsh", + [".zlogin"] = "zsh", + [".zlogout"] = "zsh", + [".zshrc"] = "zsh", + [".zprofile"] = "zsh", + [".zcompdump"] = "zsh", + [".zshenv"] = "zsh", + [".zfbfmarks"] = "zsh", + [".alias"] = function() vim.fn["dist#ft#CSH"]() end, + [".bashrc"] = function() vim.fn["dist#ft#SetFileTypeSH"]("bash") end, + [".cshrc"] = function() vim.fn["dist#ft#CSH"]() end, + [".env"] = function() vim.fn["dist#ft#SetFileTypeSH"](vim.fn.getline(1)) end, + [".kshrc"] = function() vim.fn["dist#ft#SetFileTypeSH"]("ksh") end, + [".login"] = function() vim.fn["dist#ft#CSH"]() end, + [".profile"] = function() vim.fn["dist#ft#SetFileTypeSH"](vim.fn.getline(1)) end, + [".tcshrc"] = function() vim.fn["dist#ft#SetFileTypeShell"]("tcsh") end, + ["/etc/profile"] = function() vim.fn["dist#ft#SetFileTypeSH"](vim.fn.getline(1)) end, + APKBUILD = function() vim.fn["dist#ft#SetFileTypeSH"]("bash") end, + PKGBUILD = function() vim.fn["dist#ft#SetFileTypeSH"]("bash") end, + ["bash.bashrc"] = function() vim.fn["dist#ft#SetFileTypeSH"]("bash") end, + bashrc = function() vim.fn["dist#ft#SetFileTypeSH"]("bash") end, + crontab = starsetf('crontab'), + ["csh.cshrc"] = function() vim.fn["dist#ft#CSH"]() end, + ["csh.login"] = function() vim.fn["dist#ft#CSH"]() end, + ["csh.logout"] = function() vim.fn["dist#ft#CSH"]() end, + ["indent.pro"] = function() vim.fn["dist#ft#ProtoCheck"]('indent') end, + ["tcsh.login"] = function() vim.fn["dist#ft#SetFileTypeShell"]("tcsh") end, + ["tcsh.tcshrc"] = function() vim.fn["dist#ft#SetFileTypeShell"]("tcsh") end, + -- END FILENAME +} + +local pattern = { + -- BEGIN PATTERN + [".*/etc/a2ps/.*%.cfg"] = "a2ps", + [".*/etc/a2ps%.cfg"] = "a2ps", + [".*/usr/share/alsa/alsa%.conf"] = "alsaconf", + [".*/etc/asound%.conf"] = "alsaconf", + [".*/etc/apache2/sites%-.*/.*%.com"] = "apache", + [".*/etc/httpd/.*%.conf"] = "apache", + [".*/%.aptitude/config"] = "aptconf", + ["[mM]akefile%.am"] = "automake", + [".*bsd"] = "bsdl", + ["bzr_log%..*"] = "bzr", + [".*enlightenment/.*%.cfg"] = "c", + [".*/etc/defaults/cdrdao"] = "cdrdaoconf", + [".*/etc/cdrdao%.conf"] = "cdrdaoconf", + [".*/etc/default/cdrdao"] = "cdrdaoconf", + [".*hgrc"] = "cfg", + [".*%.%.ch"] = "chill", + [".*%.cmake%.in"] = "cmake", + [".*/debian/changelog"] = "debchangelog", + [".*/debian/control"] = "debcontrol", + [".*/debian/copyright"] = "debcopyright", + [".*/etc/apt/sources%.list%.d/.*%.list"] = "debsources", + [".*/etc/apt/sources%.list"] = "debsources", + ["dictd.*%.conf"] = "dictdconf", + [".*/etc/DIR_COLORS"] = "dircolors", + [".*/etc/dnsmasq%.conf"] = "dnsmasq", + ["php%.ini%-.*"] = "dosini", + [".*/etc/pacman%.conf"] = "dosini", + [".*/etc/yum%.conf"] = "dosini", + [".*lvs"] = "dracula", + [".*lpe"] = "dracula", + [".*/dtrace/.*%.d"] = "dtrace", + [".*esmtprc"] = "esmtprc", + [".*Eterm/.*%.cfg"] = "eterm", + [".*%.git/modules/.*/config"] = "gitconfig", + [".*%.git/config"] = "gitconfig", + [".*/etc/gitconfig"] = "gitconfig", + [".*/%.config/git/config"] = "gitconfig", + [".*%.git/config%.worktree"] = "gitconfig", + [".*%.git/worktrees/.*/config%.worktree"] = "gitconfig", + ["%.gitsendemail%.msg%......."] = "gitsendemail", + ["gkrellmrc_."] = "gkrellmrc", + [".*/usr/.*/gnupg/options%.skel"] = "gpg", + [".*/%.gnupg/options"] = "gpg", + [".*/%.gnupg/gpg%.conf"] = "gpg", + [".*/etc/group"] = "group", + [".*/etc/gshadow"] = "group", + [".*/etc/group%.edit"] = "group", + [".*/var/backups/gshadow%.bak"] = "group", + [".*/etc/group-"] = "group", + [".*/etc/gshadow-"] = "group", + [".*/var/backups/group%.bak"] = "group", + [".*/etc/gshadow%.edit"] = "group", + [".*/boot/grub/grub%.conf"] = "grub", + [".*/boot/grub/menu%.lst"] = "grub", + [".*/etc/grub%.conf"] = "grub", + ["hg%-editor%-.*%.txt"] = "hgcommit", + [".*/etc/host%.conf"] = "hostconf", + [".*/etc/hosts%.deny"] = "hostsaccess", + [".*/etc/hosts%.allow"] = "hostsaccess", + [".*%.html%.m4"] = "htmlm4", + [".*/%.i3/config"] = "i3config", + [".*/sway/config"] = "i3config", + [".*/i3/config"] = "i3config", + [".*/%.sway/config"] = "i3config", + [".*/%.icewm/menu"] = "icemenu", + [".*/etc/initng/.*/.*%.i"] = "initng", + [".*%.properties_.."] = "jproperties", + [".*%.properties_.._.."] = "jproperties", + [".*lftp/rc"] = "lftp", + [".*/%.libao"] = "libao", + [".*/etc/libao%.conf"] = "libao", + [".*/etc/.*limits%.conf"] = "limits", + [".*/etc/limits"] = "limits", + [".*/etc/.*limits%.d/.*%.conf"] = "limits", + [".*/LiteStep/.*/.*%.rc"] = "litestep", + [".*/etc/login%.access"] = "loginaccess", + [".*/etc/login%.defs"] = "logindefs", + [".*/etc/mail/aliases"] = "mailaliases", + [".*/etc/aliases"] = "mailaliases", + [".*[mM]akefile"] = "make", + [".*/etc/man%.conf"] = "manconf", + [".*/etc/modules%.conf"] = "modconf", + [".*/etc/conf%.modules"] = "modconf", + [".*/etc/modules"] = "modconf", + [".*%.[mi][3g]"] = "modula3", + [".*/%.mplayer/config"] = "mplayerconf", + ["rndc.*%.conf"] = "named", + ["rndc.*%.key"] = "named", + ["named.*%.conf"] = "named", + [".*/etc/nanorc"] = "nanorc", + [".*%.NS[ACGLMNPS]"] = "natural", + ["nginx.*%.conf"] = "nginx", + [".*/etc/nginx/.*"] = "nginx", + [".*nginx%.conf"] = "nginx", + [".*/nginx/.*%.conf"] = "nginx", + [".*/usr/local/nginx/conf/.*"] = "nginx", + [".*%.ml%.cppo"] = "ocaml", + [".*%.mli%.cppo"] = "ocaml", + [".*%.opam%.template"] = "opam", + [".*%.[Oo][Pp][Ll]"] = "opl", + [".*/etc/pam%.conf"] = "pamconf", + [".*/etc/passwd-"] = "passwd", + [".*/etc/shadow"] = "passwd", + [".*/etc/shadow%.edit"] = "passwd", + [".*/var/backups/shadow%.bak"] = "passwd", + [".*/var/backups/passwd%.bak"] = "passwd", + [".*/etc/passwd"] = "passwd", + [".*/etc/passwd%.edit"] = "passwd", + [".*/etc/shadow-"] = "passwd", + [".*/%.pinforc"] = "pinfo", + [".*/etc/pinforc"] = "pinfo", + [".*/etc/protocols"] = "protocols", + [".*baseq[2-3]/.*%.cfg"] = "quake", + [".*quake[1-3]/.*%.cfg"] = "quake", + [".*id1/.*%.cfg"] = "quake", + ["[rR]antfile"] = "ruby", + ["[rR]akefile"] = "ruby", + [".*/etc/sensors%.conf"] = "sensors", + [".*/etc/sensors3%.conf"] = "sensors", + [".*/etc/services"] = "services", + [".*/etc/serial%.conf"] = "setserial", + [".*/etc/udev/cdsymlinks%.conf"] = "sh", + [".*%._sst%.meta"] = "sisu", + [".*%.%-sst%.meta"] = "sisu", + [".*%.sst%.meta"] = "sisu", + [".*/etc/slp%.conf"] = "slpconf", + [".*/etc/slp%.reg"] = "slpreg", + [".*/etc/slp%.spi"] = "slpspi", + [".*/etc/ssh/ssh_config%.d/.*%.conf"] = "sshconfig", + [".*/%.ssh/config"] = "sshconfig", + [".*/etc/ssh/sshd_config%.d/.*%.conf"] = "sshdconfig", + [".*/etc/sudoers"] = "sudoers", + ["svn%-commit.*%.tmp"] = "svn", + [".*%.swift%.gyb"] = "swiftgyb", + [".*/etc/sysctl%.conf"] = "sysctl", + [".*/etc/sysctl%.d/.*%.conf"] = "sysctl", + [".*/etc/systemd/.*%.conf%.d/.*%.conf"] = "systemd", + [".*/%.config/systemd/user/.*%.d/.*%.conf"] = "systemd", + [".*/etc/systemd/system/.*%.d/.*%.conf"] = "systemd", + [".*%.t%.html"] = "tilde", + ["%.?tmux.*%.conf"] = "tmux", + ["%.?tmux.*%.conf.*"] = { "tmux", { priority = -1 } }, + [".*/%.cargo/config"] = "toml", + [".*/%.cargo/credentials"] = "toml", + [".*/etc/udev/udev%.conf"] = "udevconf", + [".*/etc/udev/permissions%.d/.*%.permissions"] = "udevperm", + [".*/etc/updatedb%.conf"] = "updatedb", + [".*/%.init/.*%.override"] = "upstart", + [".*/usr/share/upstart/.*%.conf"] = "upstart", + [".*/%.config/upstart/.*%.override"] = "upstart", + [".*/etc/init/.*%.conf"] = "upstart", + [".*/etc/init/.*%.override"] = "upstart", + [".*/%.config/upstart/.*%.conf"] = "upstart", + [".*/%.init/.*%.conf"] = "upstart", + [".*/usr/share/upstart/.*%.override"] = "upstart", + [".*%.ws[fc]"] = "wsh", + [".*/etc/xinetd%.conf"] = "xinetd", + [".*/etc/blkid%.tab"] = "xml", + [".*/etc/blkid%.tab%.old"] = "xml", + [".*%.vbproj%.user"] = "xml", + [".*%.fsproj%.user"] = "xml", + [".*%.csproj%.user"] = "xml", + [".*/etc/xdg/menus/.*%.menu"] = "xml", + [".*Xmodmap"] = "xmodmap", + [".*/etc/zprofile"] = "zsh", + ["%.bash[_-]aliases"] = function() vim.fn["dist#ft#SetFileTypeSH"]("bash") end, + ["%.bash[_-]logout"] = function() vim.fn["dist#ft#SetFileTypeSH"]("bash") end, + ["%.bash[_-]profile"] = function() vim.fn["dist#ft#SetFileTypeSH"]("bash") end, + ["%.cshrc.*"] = function() vim.fn["dist#ft#CSH"]() end, + ["%.gtkrc.*"] = starsetf('gtkrc'), + ["%.kshrc.*"] = function() vim.fn["dist#ft#SetFileTypeSH"]("ksh") end, + ["%.login.*"] = function() vim.fn["dist#ft#CSH"]() end, + ["%.neomuttrc.*"] = starsetf('neomuttrc'), + ["%.profile.*"] = function() vim.fn["dist#ft#SetFileTypeSH"](vim.fn.getline(1)) end, + ["%.reminders.*"] = starsetf('remind'), + ["%.tcshrc.*"] = function() vim.fn["dist#ft#SetFileTypeShell"]("tcsh") end, + ["%.zcompdump.*"] = starsetf('zsh'), + ["%.zlog.*"] = starsetf('zsh'), + ["%.zsh.*"] = starsetf('zsh'), + [".*%.[1-9]"] = function() vim.fn["dist#ft#FTnroff"]() end, + [".*%.[aA]"] = function() vim.fn["dist#ft#FTasm"]() end, + [".*%.[sS]"] = function() vim.fn["dist#ft#FTasm"]() end, + [".*%.properties_.._.._.*"] = starsetf('jproperties'), + [".*%.vhdl_[0-9].*"] = starsetf('vhdl'), + [".*/%.fvwm/.*"] = starsetf('fvwm'), + [".*/%.gitconfig%.d/.*"] = starsetf('gitconfig'), + [".*/%.neomutt/neomuttrc.*"] = starsetf('neomuttrc'), + [".*/Xresources/.*"] = starsetf('xdefaults'), + [".*/app%-defaults/.*"] = starsetf('xdefaults'), + [".*/bind/db%..*"] = starsetf('bindzone'), + [".*/debian/patches/.*"] = function() vim.fn["dist#ft#Dep3patch"]() end, + [".*/etc/Muttrc%.d/.*"] = starsetf('muttrc'), + [".*/etc/apache2/.*%.conf.*"] = starsetf('apache'), + [".*/etc/apache2/conf%..*/.*"] = starsetf('apache'), + [".*/etc/apache2/mods%-.*/.*"] = starsetf('apache'), + [".*/etc/apache2/sites%-.*/.*"] = starsetf('apache'), + [".*/etc/cron%.d/.*"] = starsetf('crontab'), + [".*/etc/dnsmasq%.d/.*"] = starsetf('dnsmasq'), + [".*/etc/httpd/conf%..*/.*"] = starsetf('apache'), + [".*/etc/httpd/conf%.d/.*%.conf.*"] = starsetf('apache'), + [".*/etc/httpd/mods%-.*/.*"] = starsetf('apache'), + [".*/etc/httpd/sites%-.*/.*"] = starsetf('apache'), + [".*/etc/logcheck/.*%.d.*/.*"] = starsetf('logcheck'), + [".*/etc/modprobe%..*"] = starsetf('modconf'), + [".*/etc/pam%.d/.*"] = starsetf('pamconf'), + [".*/etc/profile"] = function() vim.fn["dist#ft#SetFileTypeSH"](vim.fn.getline(1)) end, + [".*/etc/proftpd/.*%.conf.*"] = starsetf('apachestyle'), + [".*/etc/proftpd/conf%..*/.*"] = starsetf('apachestyle'), + [".*/etc/sudoers%.d/.*"] = starsetf('sudoers'), + [".*/etc/xinetd%.d/.*"] = starsetf('xinetd'), + [".*/etc/yum%.repos%.d/.*"] = starsetf('dosini'), + [".*/gitolite%-admin/conf/.*"] = starsetf('gitolite'), + [".*/named/db%..*"] = starsetf('bindzone'), + [".*/tmp/lltmp.*"] = starsetf('gedcom'), + [".*asterisk.*/.*voicemail%.conf.*"] = starsetf('asteriskvm'), + [".*asterisk/.*%.conf.*"] = starsetf('asterisk'), + [".*vimrc.*"] = starsetf('vim'), + [".*xmodmap.*"] = starsetf('xmodmap'), + ["/etc/gitconfig%.d/.*"] = starsetf('gitconfig'), + ["/etc/hostname%..*"] = starsetf('config'), + ["Containerfile%..*"] = starsetf('dockerfile'), + ["Dockerfile%..*"] = starsetf('dockerfile'), + ["JAM.*%..*"] = starsetf('jam'), + ["Kconfig%..*"] = starsetf('kconfig'), + ["Neomuttrc.*"] = starsetf('neomuttrc'), + ["Prl.*%..*"] = starsetf('jam'), + ["Xresources.*"] = starsetf('xdefaults'), + ["[mM]akefile.*"] = starsetf('make'), + ["[rR]akefile.*"] = starsetf('ruby'), + ["access%.conf.*"] = starsetf('apache'), + ["apache%.conf.*"] = starsetf('apache'), + ["apache2%.conf.*"] = starsetf('apache'), + ["bash%-fc[-%.]"] = function() vim.fn["dist#ft#SetFileTypeSH"]("bash") end, + ["cabal%.project%..*"] = starsetf('cabalproject'), + ["crontab%..*"] = starsetf('crontab'), + ["drac%..*"] = starsetf('dracula'), + ["gtkrc.*"] = starsetf('gtkrc'), + ["httpd%.conf.*"] = starsetf('apache'), + ["lilo%.conf.*"] = starsetf('lilo'), + ["neomuttrc.*"] = starsetf('neomuttrc'), + ["proftpd%.conf.*"] = starsetf('apachestyle'), + ["reportbug%-.*"] = starsetf('mail'), + ["sgml%.catalog.*"] = starsetf('catalog'), + ["srm%.conf.*"] = starsetf('apache'), + ["tmac%..*"] = starsetf('nroff'), + ["zlog.*"] = starsetf('zsh'), + ["zsh.*"] = starsetf('zsh'), + ["ae%d+%.txt"] = 'mail', + ["snd%.%d+"] = "mail", + ["%.letter%.%d+"] = "mail", + ["%.article%.%d+"] = "mail", + ["pico%.%d+"] = "mail", + ["mutt%-.*%-%w+"] = "mail", + ["neomutt%-.*%-%w+"] = "mail", + ["muttng%-.*%-%w+"] = "mail", + ["mutt" .. string.rep("[%w_-]", 6)] = "mail", + ["neomutt" .. string.rep("[%w_-]", 6)] = "mail", + ["/tmp/SLRN[0-9A-Z.]+"] = "mail", + ["[a-zA-Z0-9].*Dict"] = function() vim.fn["dist#ft#FTfoam"]() end, + ["[a-zA-Z0-9].*Dict%..*"] = function() vim.fn["dist#ft#FTfoam"]() end, + ["[a-zA-Z].*Properties"] = function() vim.fn["dist#ft#FTfoam"]() end, + ["[a-zA-Z].*Properties%..*"] = function() vim.fn["dist#ft#FTfoam"]() end, + [".*Transport%..*"] = function() vim.fn["dist#ft#FTfoam"]() end, + [".*/constant/g"] = function() vim.fn["dist#ft#FTfoam"]() end, + [".*/0/.*"] = function() vim.fn["dist#ft#FTfoam"]() end, + [".*/0%.orig/.*"] = function() vim.fn["dist#ft#FTfoam"]() end, + [".*/etc/sensors%.d/[^.].*"] = starsetf('sensors'), + [".*%.git/.*"] = function(path, bufnr) + local firstline = getline(bufnr, 1) + if firstline:find("^" .. string.rep("%x", 40) .. "+ ") or firstline:sub(1, 5) == "ref: " then + return "git" + end + end, + -- END PATTERN +} +-- luacheck: pop + +---@private +local function sort_by_priority(t) + local sorted = {} + for k, v in pairs(t) do + local ft = type(v) == "table" and v[1] or v + assert(type(ft) == "string" or type(ft) == "function", "Expected string or function for filetype") + + local opts = (type(v) == "table" and type(v[2]) == "table") and v[2] or {} + if not opts.priority then + opts.priority = 0 + end + table.insert(sorted, { [k] = { ft, opts } }) + end + table.sort(sorted, function(a, b) + return a[next(a)][2].priority > b[next(b)][2].priority + end) + return sorted +end + +local pattern_sorted = sort_by_priority(pattern) + +---@private +local function normalize_path(path) + return (path:gsub("\\", "/"):gsub("^~", vim.env.HOME)) +end + +--- Add new filetype mappings. +--- +--- Filetype mappings can be added either by extension or by filename (either +--- the "tail" or the full file path). The full file path is checked first, +--- followed by the file name. If a match is not found using the filename, then +--- the filename is matched against the list of patterns (sorted by priority) +--- until a match is found. Lastly, if pattern matching does not find a +--- filetype, then the file extension is used. +--- +--- The filetype can be either a string (in which case it is used as the +--- filetype directly) or a function. If a function, it takes the full path and +--- buffer number of the file as arguments (along with captures from the matched +--- pattern, if any) and should return a string that will be used as the +--- buffer's filetype. +--- +--- Filename patterns can specify an optional priority to resolve cases when a +--- file path matches multiple patterns. Higher priorities are matched first. +--- When omitted, the priority defaults to 0. +--- +--- See $VIMRUNTIME/lua/vim/filetype.lua for more examples. +--- +--- Note that Lua filetype detection is only enabled when |g:do_filetype_lua| is +--- set to 1. +--- +--- Example: +--- <pre> +--- vim.filetype.add({ +--- extension = { +--- foo = "fooscript", +--- bar = function(path, bufnr) +--- if some_condition() then +--- return "barscript" +--- end +--- return "bar" +--- end, +--- }, +--- filename = { +--- [".foorc"] = "toml", +--- ["/etc/foo/config"] = "toml", +--- }, +--- pattern = { +--- [".*/etc/foo/.*"] = "fooscript", +--- -- Using an optional priority +--- [".*/etc/foo/.*%.conf"] = { "dosini", { priority = 10 } }, +--- ["README.(%a+)$"] = function(path, bufnr, ext) +--- if ext == "md" then +--- return "markdown" +--- elseif ext == "rst" then +--- return "rst" +--- end +--- end, +--- }, +--- }) +--- </pre> +--- +---@param filetypes table A table containing new filetype maps (see example). +function M.add(filetypes) + for k, v in pairs(filetypes.extension or {}) do + extension[k] = v + end + + for k, v in pairs(filetypes.filename or {}) do + filename[normalize_path(k)] = v + end + + for k, v in pairs(filetypes.pattern or {}) do + pattern[normalize_path(k)] = v + end + + if filetypes.pattern then + pattern_sorted = sort_by_priority(pattern) + end +end + +---@private +local function dispatch(ft, path, bufnr, ...) + if type(ft) == "function" then + ft = ft(path, bufnr, ...) + end + + if type(ft) == "string" then + api.nvim_buf_set_option(bufnr, "filetype", ft) + return true + end + + -- Any non-falsey value (that is, anything other than 'nil' or 'false') will + -- end filetype matching. This is useful for e.g. the dist#ft functions that + -- return 0, but set the buffer's filetype themselves + return ft +end + +---@private +local function match_pattern(name, path, tail, pat) + -- If the pattern contains a / match against the full path, otherwise just the tail + local fullpat = "^" .. pat .. "$" + local matches + if pat:find("/") then + -- Similar to |autocmd-pattern|, if the pattern contains a '/' then check for a match against + -- both the short file name (as typed) and the full file name (after expanding to full path + -- and resolving symlinks) + matches = name:match(fullpat) or path:match(fullpat) + else + matches = tail:match(fullpat) + end + return matches +end + +--- Set the filetype for the given buffer from a file name. +--- +---@param name string File name (can be an absolute or relative path) +---@param bufnr number|nil The buffer to set the filetype for. Defaults to the current buffer. +function M.match(name, bufnr) + -- When fired from the main filetypedetect autocommand the {bufnr} argument is omitted, so we use + -- the current buffer. The {bufnr} argument is provided to allow extensibility in case callers + -- wish to perform filetype detection on buffers other than the current one. + bufnr = bufnr or api.nvim_get_current_buf() + + name = normalize_path(name) + + -- First check for the simple case where the full path exists as a key + local path = vim.fn.resolve(vim.fn.fnamemodify(name, ":p")) + if dispatch(filename[path], path, bufnr) then + return + end + + -- Next check against just the file name + local tail = vim.fn.fnamemodify(name, ":t") + if dispatch(filename[tail], path, bufnr) then + return + end + + -- Next, check the file path against available patterns with non-negative priority + local j = 1 + for i, v in ipairs(pattern_sorted) do + local k = next(v) + local opts = v[k][2] + if opts.priority < 0 then + j = i + break + end + + local ft = v[k][1] + local matches = match_pattern(name, path, tail, k) + if matches then + if dispatch(ft, path, bufnr, matches) then + return + end + end + end + + -- Next, check file extension + local ext = vim.fn.fnamemodify(name, ":e") + if dispatch(extension[ext], path, bufnr) then + return + end + + -- Finally, check patterns with negative priority + for i = j, #pattern_sorted do + local v = pattern_sorted[i] + local k = next(v) + + local ft = v[k][1] + local matches = match_pattern(name, path, tail, k) + if matches then + if dispatch(ft, path, bufnr, matches) then + return + end + end + end +end + +return M diff --git a/runtime/lua/vim/highlight.lua b/runtime/lua/vim/highlight.lua index 236f3165f2..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,33 +20,50 @@ 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) -function highlight.range(bufnr, ns, higroup, start, finish, rtype, inclusive) - rtype = rtype or 'v' - inclusive = inclusive or false +---@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 - api.nvim_buf_add_highlight(bufnr, ns, higroup, linenr, cols[1], cols[2]) + local end_row + if cols[2] == -1 then + end_row = linenr + 1 + cols[2] = 0 + end + api.nvim_buf_set_extmark(bufnr, ns, linenr, cols[1], { + hl_group = higroup, + end_row = end_row, + end_col = cols[2], + priority = priority, + 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 @@ -49,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 @@ -79,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]} + 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) - - 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/keymap.lua b/runtime/lua/vim/keymap.lua new file mode 100644 index 0000000000..df49eff4b6 --- /dev/null +++ b/runtime/lua/vim/keymap.lua @@ -0,0 +1,135 @@ +local keymap = {} + +--- Add a new |mapping|. +--- Examples: +--- <pre> +--- -- Can add mapping to Lua functions +--- vim.keymap.set('n', 'lhs', function() print("real lua function") end) +--- +--- -- Can use it to map multiple modes +--- vim.keymap.set({'n', 'v'}, '<leader>lr', vim.lsp.buf.references, { buffer=true }) +--- +--- -- Can add mapping for specific buffer +--- vim.keymap.set('n', '<leader>w', "<cmd>w<cr>", { silent = true, buffer = 5 }) +--- +--- -- Expr mappings +--- vim.keymap.set('i', '<Tab>', function() +--- return vim.fn.pumvisible() == 1 and "<C-n>" or "<Tab>" +--- end, { expr = true }) +--- -- <Plug> mappings +--- vim.keymap.set('n', '[%%', '<Plug>(MatchitNormalMultiBackward)') +--- </pre> +--- +--- Note that in a mapping like: +--- <pre> +--- vim.keymap.set('n', 'asdf', require('jkl').my_fun) +--- </pre> +--- +--- the require('jkl') gets evaluated during this call in order to access the function. If you want to +--- avoid this cost at startup you can wrap it in a function, for example: +--- <pre> +--- vim.keymap.set('n', 'asdf', function() return require('jkl').my_fun() end) +--- </pre> +--- +---@param mode string|table Same mode short names as |nvim_set_keymap()|. +--- Can also be list of modes to create mapping on multiple modes. +---@param lhs string Left-hand side |{lhs}| of the mapping. +---@param rhs string|function Right-hand side |{rhs}| of the mapping. Can also be a Lua function. +-- +---@param opts table A table of |:map-arguments| such as "silent". In addition to the options +--- listed in |nvim_set_keymap()|, this table also accepts the following keys: +--- - replace_keycodes: (boolean, default true) When both this and expr is "true", +--- |nvim_replace_termcodes()| is applied to the result of Lua expr maps. +--- - remap: (boolean) Make the mapping recursive. This is the +--- inverse of the "noremap" option from |nvim_set_keymap()|. +--- Default `false`. +---@see |nvim_set_keymap()| +function keymap.set(mode, lhs, rhs, opts) + vim.validate { + mode = {mode, {'s', 't'}}, + lhs = {lhs, 's'}, + rhs = {rhs, {'s', 'f'}}, + opts = {opts, 't', true} + } + + opts = vim.deepcopy(opts) or {} + local is_rhs_luaref = type(rhs) == "function" + mode = type(mode) == 'string' and {mode} or mode + + if is_rhs_luaref and opts.expr and opts.replace_keycodes ~= false then + local user_rhs = rhs + rhs = function () + return vim.api.nvim_replace_termcodes(user_rhs(), true, true, true) + end + end + -- clear replace_keycodes from opts table + opts.replace_keycodes = nil + + if opts.remap == nil then + -- default remap value is false + opts.noremap = true + else + -- remaps behavior is opposite of noremap option. + opts.noremap = not opts.remap + opts.remap = nil + end + + if is_rhs_luaref then + opts.callback = rhs + rhs = '' + end + + if opts.buffer then + local bufnr = opts.buffer == true and 0 or opts.buffer + opts.buffer = nil + for _, m in ipairs(mode) do + vim.api.nvim_buf_set_keymap(bufnr, m, lhs, rhs, opts) + end + else + opts.buffer = nil + for _, m in ipairs(mode) do + vim.api.nvim_set_keymap(m, lhs, rhs, opts) + end + end +end + +--- Remove an existing mapping. +--- Examples: +--- <pre> +--- vim.keymap.del('n', 'lhs') +--- +--- vim.keymap.del({'n', 'i', 'v'}, '<leader>w', { buffer = 5 }) +--- </pre> +---@param opts table A table of optional arguments: +--- - buffer: (number or boolean) Remove a mapping from the given buffer. +--- When "true" or 0, use the current buffer. +---@see |vim.keymap.set()| +--- +function keymap.del(modes, lhs, opts) + vim.validate { + mode = {modes, {'s', 't'}}, + lhs = {lhs, 's'}, + opts = {opts, 't', true} + } + + opts = opts or {} + modes = type(modes) == 'string' and {modes} or modes + + local buffer = false + if opts.buffer ~= nil then + buffer = opts.buffer == true and 0 or opts.buffer + opts.buffer = nil + end + + if buffer == false then + for _, mode in ipairs(modes) do + vim.api.nvim_del_keymap(mode, lhs) + end + else + for _, mode in ipairs(modes) do + vim.api.nvim_buf_del_keymap(buffer, mode, lhs) + end + end +end + +return keymap diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index 00839ec181..8d11b4621c 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -256,7 +256,7 @@ local function validate_client_config(config) (not config.flags or not config.flags.debounce_text_changes or type(config.flags.debounce_text_changes) == 'number'), - "flags.debounce_text_changes must be nil or a number with the debounce time in milliseconds" + "flags.debounce_text_changes must be a number with the debounce time in milliseconds" ) local cmd, cmd_args = lsp._cmd_parts(config.cmd) @@ -290,7 +290,7 @@ end --- Memoizes a function. On first run, the function return value is saved and --- immediately returned on subsequent runs. If the function returns a multival, --- only the first returned value will be memoized and returned. The function will only be run once, ---- even if it has side-effects. +--- even if it has side effects. --- ---@param fn (function) Function to run ---@returns (function) Memoized function @@ -306,70 +306,138 @@ local function once(fn) end end - local changetracking = {} do --@private --- client_id → state --- --- state + --- use_incremental_sync: bool + --- buffers: bufnr -> buffer_state + --- + --- buffer_state --- pending_change?: function that the timer starts to trigger didChange --- pending_changes: table (uri -> list of pending changeset tables)); - -- Only set if incremental_sync is used - --- use_incremental_sync: bool - --- buffers?: table (bufnr → lines); for incremental sync only + --- Only set if incremental_sync is used + --- --- timer?: uv_timer + --- lines: table local state_by_client = {} ---@private function changetracking.init(client, bufnr) + local use_incremental_sync = ( + if_nil(client.config.flags.allow_incremental_sync, true) + and client.resolved_capabilities.text_document_did_change == protocol.TextDocumentSyncKind.Incremental + ) local state = state_by_client[client.id] if not state then state = { - pending_changes = {}; - use_incremental_sync = ( - if_nil(client.config.flags.allow_incremental_sync, true) - and client.resolved_capabilities.text_document_did_change == protocol.TextDocumentSyncKind.Incremental - ); + buffers = {}; + debounce = client.config.flags.debounce_text_changes or 150, + use_incremental_sync = use_incremental_sync; } state_by_client[client.id] = state end - if not state.use_incremental_sync then - return - end - if not state.buffers then - state.buffers = {} + if not state.buffers[bufnr] then + local buf_state = {} + state.buffers[bufnr] = buf_state + if use_incremental_sync then + buf_state.lines = nvim_buf_get_lines(bufnr, 0, -1, true) + buf_state.lines_tmp = {} + buf_state.pending_changes = {} + end end - state.buffers[bufnr] = nvim_buf_get_lines(bufnr, 0, -1, true) end ---@private function changetracking.reset_buf(client, bufnr) - changetracking.flush(client) + changetracking.flush(client, bufnr) local state = state_by_client[client.id] if state and state.buffers then + local buf_state = state.buffers[bufnr] state.buffers[bufnr] = nil + if buf_state and buf_state.timer then + buf_state.timer:stop() + buf_state.timer:close() + buf_state.timer = nil + end end end ---@private function changetracking.reset(client_id) local state = state_by_client[client_id] - if state then - state_by_client[client_id] = nil - changetracking._reset_timer(state) + if not state then + return end + for _, buf_state in pairs(state.buffers) do + if buf_state.timer then + buf_state.timer:stop() + buf_state.timer:close() + buf_state.timer = nil + end + end + state.buffers = {} + end + + ---@private + -- + -- Adjust debounce time by taking time of last didChange notification into + -- consideration. If the last didChange happened more than `debounce` time ago, + -- debounce can be skipped and otherwise maybe reduced. + -- + -- This turns the debounce into a kind of client rate limiting + local function next_debounce(debounce, buf_state) + if debounce == 0 then + return 0 + end + local ns_to_ms = 0.000001 + if not buf_state.last_flush then + return debounce + end + local now = uv.hrtime() + local ms_since_last_flush = (now - buf_state.last_flush) * ns_to_ms + return math.max(debounce - ms_since_last_flush, 0) end ---@private function changetracking.prepare(bufnr, firstline, lastline, new_lastline) - local incremental_changes = function(client) - local cached_buffers = state_by_client[client.id].buffers - local curr_lines = nvim_buf_get_lines(bufnr, 0, -1, true) + local incremental_changes = function(client, buf_state) + + local prev_lines = buf_state.lines + local curr_lines = buf_state.lines_tmp + + local changed_lines = nvim_buf_get_lines(bufnr, firstline, new_lastline, true) + for i = 1, firstline do + curr_lines[i] = prev_lines[i] + end + for i = firstline + 1, new_lastline do + curr_lines[i] = changed_lines[i - firstline] + end + for i = lastline + 1, #prev_lines do + curr_lines[i - lastline + new_lastline] = prev_lines[i] + end + if tbl_isempty(curr_lines) then + -- Can happen when deleting the entire contents of a buffer, see https://github.com/neovim/neovim/issues/16259. + curr_lines[1] = '' + end + local line_ending = buf_get_line_ending(bufnr) local incremental_change = sync.compute_diff( - cached_buffers[bufnr], curr_lines, firstline, lastline, new_lastline, client.offset_encoding or 'utf-16', line_ending) - cached_buffers[bufnr] = curr_lines + buf_state.lines, curr_lines, firstline, lastline, new_lastline, client.offset_encoding or 'utf-16', line_ending) + + -- Double-buffering of lines tables is used to reduce the load on the garbage collector. + -- At this point the prev_lines table is useless, but its internal storage has already been allocated, + -- so let's keep it around for the next didChange event, in which it will become the next + -- curr_lines table. Note that setting elements to nil doesn't actually deallocate slots in the + -- internal storage - it merely marks them as free, for the GC to deallocate them. + for i in ipairs(prev_lines) do + prev_lines[i] = nil + end + buf_state.lines = curr_lines + buf_state.lines_tmp = prev_lines + return incremental_change end local full_changes = once(function() @@ -383,75 +451,68 @@ do return end local state = state_by_client[client.id] - local debounce = client.config.flags.debounce_text_changes - if not debounce then - local changes = state.use_incremental_sync and incremental_changes(client) or full_changes() - client.notify("textDocument/didChange", { - textDocument = { - uri = uri; - version = util.buf_versions[bufnr]; - }; - contentChanges = { changes, } - }) - return - end - changetracking._reset_timer(state) + local buf_state = state.buffers[bufnr] + changetracking._reset_timer(buf_state) + local debounce = next_debounce(state.debounce, buf_state) if state.use_incremental_sync then -- This must be done immediately and cannot be delayed -- The contents would further change and startline/endline may no longer fit - if not state.pending_changes[uri] then - state.pending_changes[uri] = {} - end - table.insert(state.pending_changes[uri], incremental_changes(client)) + table.insert(buf_state.pending_changes, incremental_changes(client, buf_state)) end - state.pending_change = function() - state.pending_change = nil + buf_state.pending_change = function() + buf_state.pending_change = nil + buf_state.last_flush = uv.hrtime() if client.is_stopped() or not vim.api.nvim_buf_is_valid(bufnr) then return end - if state.use_incremental_sync then - for change_uri, content_changes in pairs(state.pending_changes) do - client.notify("textDocument/didChange", { - textDocument = { - uri = change_uri; - version = util.buf_versions[vim.uri_to_bufnr(change_uri)]; - }; - contentChanges = content_changes, - }) - end - state.pending_changes = {} - else - client.notify("textDocument/didChange", { - textDocument = { - uri = uri; - version = util.buf_versions[bufnr]; - }; - contentChanges = { full_changes() }, - }) - end + local changes = state.use_incremental_sync and buf_state.pending_changes or { full_changes() } + client.notify("textDocument/didChange", { + textDocument = { + uri = uri, + version = util.buf_versions[bufnr], + }, + contentChanges = changes, + }) + buf_state.pending_changes = {} + end + if debounce == 0 then + buf_state.pending_change() + else + local timer = vim.loop.new_timer() + buf_state.timer = timer + -- Must use schedule_wrap because `full_changes()` calls nvim_buf_get_lines + timer:start(debounce, 0, vim.schedule_wrap(buf_state.pending_change)) end - state.timer = vim.loop.new_timer() - -- Must use schedule_wrap because `full_changes()` calls nvim_buf_get_lines - state.timer:start(debounce, 0, vim.schedule_wrap(state.pending_change)) end end - function changetracking._reset_timer(state) - if state.timer then - state.timer:stop() - state.timer:close() - state.timer = nil + function changetracking._reset_timer(buf_state) + if buf_state.timer then + buf_state.timer:stop() + buf_state.timer:close() + buf_state.timer = nil end end --- Flushes any outstanding change notification. ---@private - function changetracking.flush(client) + function changetracking.flush(client, bufnr) local state = state_by_client[client.id] - if state then - changetracking._reset_timer(state) - if state.pending_change then - state.pending_change() + if not state then + return + end + if bufnr then + local buf_state = state.buffers[bufnr] or {} + changetracking._reset_timer(buf_state) + if buf_state.pending_change then + buf_state.pending_change() + end + else + for _, buf_state in pairs(state.buffers) do + changetracking._reset_timer(buf_state) + if buf_state.pending_change then + buf_state.pending_change() + end end end end @@ -645,8 +706,8 @@ end ---@param on_error Callback with parameters (code, ...), invoked --- when the client operation throws an error. `code` is a number describing --- the error. Other arguments may be passed depending on the error kind. See ---- |vim.lsp.client_errors| for possible errors. ---- Use `vim.lsp.client_errors[code]` to get human-friendly name. +--- |vim.lsp.rpc.client_errors| for possible errors. +--- Use `vim.lsp.rpc.client_errors[code]` to get human-friendly name. --- ---@param before_init Callback with parameters (initialize_params, config) --- invoked before the LSP "initialize" phase, where `params` contains the @@ -757,8 +818,8 @@ function lsp.start_client(config) --- ---@param code (number) Error code ---@param err (...) Other arguments may be passed depending on the error kind - ---@see |vim.lsp.client_errors| for possible errors. Use - ---`vim.lsp.client_errors[code]` to get a human-friendly name. + ---@see |vim.lsp.rpc.client_errors| for possible errors. Use + ---`vim.lsp.rpc.client_errors[code]` to get a human-friendly name. function dispatch.on_error(code, err) local _ = log.error() and log.error(log_prefix, "on_error", { code = lsp.client_errors[code], err = err }) err_message(log_prefix, ': Error ', lsp.client_errors[code], ': ', vim.inspect(err)) @@ -897,7 +958,7 @@ function lsp.start_client(config) client.initialized = true uninitialized_clients[client_id] = nil client.workspace_folders = workspace_folders - -- TODO(mjlbach): Backwards compatbility, to be removed in 0.7 + -- TODO(mjlbach): Backwards compatibility, to be removed in 0.7 client.workspaceFolders = client.workspace_folders client.server_capabilities = assert(result.capabilities, "initialize result doesn't contain capabilities") -- These are the cleaned up capabilities we use for dynamically deciding @@ -957,7 +1018,7 @@ function lsp.start_client(config) or error(string.format("not found: %q request handler for client %q.", method, client.name)) end -- Ensure pending didChange notifications are sent so that the server doesn't operate on a stale state - changetracking.flush(client) + changetracking.flush(client, bufnr) bufnr = resolve_bufnr(bufnr) local _ = log.debug() and log.debug(log_prefix, "client.request", client_id, method, params, handler, bufnr) local success, request_id = rpc.request(method, params, function(err, result) @@ -1014,14 +1075,16 @@ function lsp.start_client(config) ---@private --- Sends a notification to an LSP server. --- - ---@param method (string) LSP method name. - ---@param params (optional, table) LSP request params. - ---@param bufnr (number) Buffer handle, or 0 for current. + ---@param method string LSP method name. + ---@param params table|nil LSP request params. ---@returns {status} (bool) true if the notification was successful. ---If it is false, then it will always be false ---(the client has shutdown). - function client.notify(...) - return rpc.notify(...) + function client.notify(method, params) + if method ~= 'textDocument/didChange' then + changetracking.flush(client) + end + return rpc.notify(method, params) end ---@private @@ -1131,7 +1194,7 @@ function lsp._text_document_did_save_handler(bufnr) if client.resolved_capabilities.text_document_save then local included_text if client.resolved_capabilities.text_document_save_include_text then - included_text = text() + included_text = text(bufnr) end client.notify('textDocument/didSave', { textDocument = { @@ -1535,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 @@ -1708,14 +1771,14 @@ end -- -- Can be used to lookup the number from the name or the -- name from the number. --- Levels by name: "trace", "debug", "info", "warn", "error" --- Level numbers begin with "trace" at 0 +-- Levels by name: "TRACE", "DEBUG", "INFO", "WARN", "ERROR" +-- Level numbers begin with "TRACE" at 0 lsp.log_levels = log.levels --- Sets the global log level for LSP logging. --- ---- Levels by name: "trace", "debug", "info", "warn", "error" ---- Level numbers begin with "trace" at 0 +--- Levels by name: "TRACE", "DEBUG", "INFO", "WARN", "ERROR" +--- Level numbers begin with "TRACE" at 0 --- --- Use `lsp.log_levels` for reverse lookup. --- diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua index 8e3ed9b002..eb7ec579f1 100644 --- a/runtime/lua/vim/lsp/buf.lua +++ b/runtime/lua/vim/lsp/buf.lua @@ -184,7 +184,7 @@ function M.formatting_sync(options, timeout_ms) local result, err = client.request_sync('textDocument/formatting', params, timeout_ms, bufnr) if result and result.result then - util.apply_text_edits(result.result, bufnr) + util.apply_text_edits(result.result, bufnr, client.offset_encoding) elseif err then vim.notify('vim.lsp.buf.formatting_sync: ' .. err, vim.log.levels.WARN) end @@ -228,7 +228,7 @@ function M.formatting_seq_sync(options, timeout_ms, order) local params = util.make_formatting_params(options) local result, err = client.request_sync("textDocument/formatting", params, timeout_ms, vim.api.nvim_get_current_buf()) if result and result.result then - util.apply_text_edits(result.result, bufnr) + util.apply_text_edits(result.result, bufnr, client.offset_encoding) elseif err then vim.notify(string.format("vim.lsp.buf.formatting_seq_sync: (%s) %s", client.name, err), vim.log.levels.WARN) end @@ -447,13 +447,16 @@ end ---@param query (string, optional) function M.workspace_symbol(query) query = query or npcall(vfn.input, "Query: ") + if query == nil then + return + end local params = {query = query} request('workspace/symbol', params) end --- Send request to the server to resolve document highlights for the current --- text document position. This request can be triggered by a key mapping or ---- by events such as `CursorHold`, eg: +--- by events such as `CursorHold`, e.g.: --- --- <pre> --- autocmd CursorHold <buffer> lua vim.lsp.buf.document_highlight() @@ -503,7 +506,7 @@ local function on_code_action_results(results, ctx) ---@private local function apply_action(action, client) if action.edit then - util.apply_workspace_edit(action.edit) + util.apply_workspace_edit(action.edit, client.offset_encoding) end if action.command then local command = type(action.command) == 'table' and action.command or action @@ -627,14 +630,19 @@ end --- Executes an LSP server command. --- ----@param command A valid `ExecuteCommandParams` object +---@param command_params table A valid `ExecuteCommandParams` object ---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_executeCommand -function M.execute_command(command) +function M.execute_command(command_params) validate { - command = { command.command, 's' }, - arguments = { command.arguments, 't', true } + command = { command_params.command, 's' }, + arguments = { command_params.arguments, 't', true } + } + command_params = { + command=command_params.command, + arguments=command_params.arguments, + workDoneToken=command_params.workDoneToken, } - request('workspace/executeCommand', command) + request('workspace/executeCommand', command_params ) end return M diff --git a/runtime/lua/vim/lsp/diagnostic.lua b/runtime/lua/vim/lsp/diagnostic.lua index 8850d25233..68942ae11a 100644 --- a/runtime/lua/vim/lsp/diagnostic.lua +++ b/runtime/lua/vim/lsp/diagnostic.lua @@ -168,8 +168,8 @@ end --- }, --- -- 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, @@ -243,7 +243,7 @@ end ---@param client_id number ---@private function M.save(diagnostics, bufnr, client_id) - vim.api.nvim_echo({{'vim.lsp.diagnostic.save is deprecated. See :h deprecated', 'WarningMsg'}}, true, {}) + vim.notify_once('vim.lsp.diagnostic.save is deprecated. See :h deprecated', vim.log.levels.WARN) local namespace = M.get_namespace(client_id) vim.diagnostic.set(namespace, bufnr, diagnostic_lsp_to_vim(diagnostics, bufnr, client_id)) end @@ -257,7 +257,7 @@ end --- If nil, diagnostics of all clients are included. ---@return table with diagnostics grouped by bufnr (bufnr: Diagnostic[]) function M.get_all(client_id) - vim.api.nvim_echo({{'vim.lsp.diagnostic.get_all is deprecated. See :h deprecated', 'WarningMsg'}}, true, {}) + vim.notify_once('vim.lsp.diagnostic.get_all is deprecated. See :h deprecated', vim.log.levels.WARN) local result = {} local namespace if client_id then @@ -279,7 +279,7 @@ end --- Else, return just the diagnostics associated with the client_id. ---@param predicate function|nil Optional function for filtering diagnostics function M.get(bufnr, client_id, predicate) - vim.api.nvim_echo({{'vim.lsp.diagnostic.get is deprecated. See :h deprecated', 'WarningMsg'}}, true, {}) + vim.notify_once('vim.lsp.diagnostic.get is deprecated. See :h deprecated', vim.log.levels.WARN) predicate = predicate or function() return true end if client_id == nil then local all_diagnostics = {} @@ -341,7 +341,7 @@ end ---@param severity DiagnosticSeverity ---@param client_id number the client id function M.get_count(bufnr, severity, client_id) - vim.api.nvim_echo({{'vim.lsp.diagnostic.get_count is deprecated. See :h deprecated', 'WarningMsg'}}, true, {}) + vim.notify_once('vim.lsp.diagnostic.get_count is deprecated. See :h deprecated', vim.log.levels.WARN) severity = severity_lsp_to_vim(severity) local opts = { severity = severity } if client_id ~= nil then @@ -358,7 +358,7 @@ end ---@param opts table See |vim.lsp.diagnostic.goto_next()| ---@return table Previous diagnostic function M.get_prev(opts) - vim.api.nvim_echo({{'vim.lsp.diagnostic.get_prev is deprecated. See :h deprecated', 'WarningMsg'}}, true, {}) + vim.notify_once('vim.lsp.diagnostic.get_prev is deprecated. See :h deprecated', vim.log.levels.WARN) if opts then if opts.severity then opts.severity = severity_lsp_to_vim(opts.severity) @@ -376,7 +376,7 @@ end ---@param opts table See |vim.lsp.diagnostic.goto_next()| ---@return table Previous diagnostic position function M.get_prev_pos(opts) - vim.api.nvim_echo({{'vim.lsp.diagnostic.get_prev_pos is deprecated. See :h deprecated', 'WarningMsg'}}, true, {}) + vim.notify_once('vim.lsp.diagnostic.get_prev_pos is deprecated. See :h deprecated', vim.log.levels.WARN) if opts then if opts.severity then opts.severity = severity_lsp_to_vim(opts.severity) @@ -393,7 +393,7 @@ end --- ---@param opts table See |vim.lsp.diagnostic.goto_next()| function M.goto_prev(opts) - vim.api.nvim_echo({{'vim.lsp.diagnostic.goto_prev is deprecated. See :h deprecated', 'WarningMsg'}}, true, {}) + vim.notify_once('vim.lsp.diagnostic.goto_prev is deprecated. See :h deprecated', vim.log.levels.WARN) if opts then if opts.severity then opts.severity = severity_lsp_to_vim(opts.severity) @@ -411,7 +411,7 @@ end ---@param opts table See |vim.lsp.diagnostic.goto_next()| ---@return table Next diagnostic function M.get_next(opts) - vim.api.nvim_echo({{'vim.lsp.diagnostic.get_next is deprecated. See :h deprecated', 'WarningMsg'}}, true, {}) + vim.notify_once('vim.lsp.diagnostic.get_next is deprecated. See :h deprecated', vim.log.levels.WARN) if opts then if opts.severity then opts.severity = severity_lsp_to_vim(opts.severity) @@ -429,7 +429,7 @@ end ---@param opts table See |vim.lsp.diagnostic.goto_next()| ---@return table Next diagnostic position function M.get_next_pos(opts) - vim.api.nvim_echo({{'vim.lsp.diagnostic.get_next_pos is deprecated. See :h deprecated', 'WarningMsg'}}, true, {}) + vim.notify_once('vim.lsp.diagnostic.get_next_pos is deprecated. See :h deprecated', vim.log.levels.WARN) if opts then if opts.severity then opts.severity = severity_lsp_to_vim(opts.severity) @@ -444,7 +444,7 @@ end --- ---@deprecated Prefer |vim.diagnostic.goto_next()| function M.goto_next(opts) - vim.api.nvim_echo({{'vim.lsp.diagnostic.goto_next is deprecated. See :h deprecated', 'WarningMsg'}}, true, {}) + vim.notify_once('vim.lsp.diagnostic.goto_next is deprecated. See :h deprecated', vim.log.levels.WARN) if opts then if opts.severity then opts.severity = severity_lsp_to_vim(opts.severity) @@ -468,7 +468,7 @@ end --- - severity_limit (DiagnosticSeverity): --- - Limit severity of diagnostics found. E.g. "Warning" means { "Error", "Warning" } will be valid. function M.set_signs(diagnostics, bufnr, client_id, _, opts) - vim.api.nvim_echo({{'vim.lsp.diagnostic.set_signs is deprecated. See :h deprecated', 'WarningMsg'}}, true, {}) + vim.notify_once('vim.lsp.diagnostic.set_signs is deprecated. See :h deprecated', vim.log.levels.WARN) local namespace = M.get_namespace(client_id) if opts and not opts.severity and opts.severity_limit then opts.severity = {min=severity_lsp_to_vim(opts.severity_limit)} @@ -489,7 +489,7 @@ end --- - severity_limit (DiagnosticSeverity): --- - Limit severity of diagnostics found. E.g. "Warning" means { "Error", "Warning" } will be valid. function M.set_underline(diagnostics, bufnr, client_id, _, opts) - vim.api.nvim_echo({{'vim.lsp.diagnostic.set_underline is deprecated. See :h deprecated', 'WarningMsg'}}, true, {}) + vim.notify_once('vim.lsp.diagnostic.set_underline is deprecated. See :h deprecated', vim.log.levels.WARN) local namespace = M.get_namespace(client_id) if opts and not opts.severity and opts.severity_limit then opts.severity = {min=severity_lsp_to_vim(opts.severity_limit)} @@ -511,7 +511,7 @@ end --- - severity_limit (DiagnosticSeverity): --- - Limit severity of diagnostics found. E.g. "Warning" means { "Error", "Warning" } will be valid. function M.set_virtual_text(diagnostics, bufnr, client_id, _, opts) - vim.api.nvim_echo({{'vim.lsp.diagnostic.set_virtual_text is deprecated. See :h deprecated', 'WarningMsg'}}, true, {}) + vim.notify_once('vim.lsp.diagnostic.set_virtual_text is deprecated. See :h deprecated', vim.log.levels.WARN) local namespace = M.get_namespace(client_id) if opts and not opts.severity and opts.severity_limit then opts.severity = {min=severity_lsp_to_vim(opts.severity_limit)} @@ -530,7 +530,7 @@ end ---@return an array of [text, hl_group] arrays. This can be passed directly to --- the {virt_text} option of |nvim_buf_set_extmark()|. function M.get_virtual_text_chunks_for_line(bufnr, _, line_diags, opts) - vim.api.nvim_echo({{'vim.lsp.diagnostic.get_virtual_text_chunks_for_line is deprecated. See :h deprecated', 'WarningMsg'}}, true, {}) + vim.notify_once('vim.lsp.diagnostic.get_virtual_text_chunks_for_line is deprecated. See :h deprecated', vim.log.levels.WARN) return vim.diagnostic._get_virt_text_chunks(diagnostic_lsp_to_vim(line_diags, bufnr), opts) end @@ -548,7 +548,7 @@ end ---@param position table|nil The (0,0)-indexed position ---@return table {popup_bufnr, win_id} function M.show_position_diagnostics(opts, buf_nr, position) - vim.api.nvim_echo({{'vim.lsp.diagnostic.show_position_diagnostics is deprecated. See :h deprecated', 'WarningMsg'}}, true, {}) + vim.notify_once('vim.lsp.diagnostic.show_position_diagnostics is deprecated. See :h deprecated', vim.log.levels.WARN) opts = opts or {} opts.scope = "cursor" opts.pos = position @@ -572,7 +572,7 @@ end ---@param client_id number|nil the client id ---@return table {popup_bufnr, win_id} function M.show_line_diagnostics(opts, buf_nr, line_nr, client_id) - vim.api.nvim_echo({{'vim.lsp.diagnostic.show_line_diagnostics is deprecated. See :h deprecated', 'WarningMsg'}}, true, {}) + vim.notify_once('vim.lsp.diagnostic.show_line_diagnostics is deprecated. See :h deprecated', vim.log.levels.WARN) opts = opts or {} opts.scope = "line" opts.pos = line_nr @@ -596,7 +596,7 @@ end --- client. The default is to redraw diagnostics for all attached --- clients. function M.redraw(bufnr, client_id) - vim.api.nvim_echo({{'vim.lsp.diagnostic.redraw is deprecated. See :h deprecated', 'WarningMsg'}}, true, {}) + vim.notify_once('vim.lsp.diagnostic.redraw is deprecated. See :h deprecated', vim.log.levels.WARN) bufnr = get_bufnr(bufnr) if not client_id then return vim.lsp.for_each_buffer_client(bufnr, function(client) @@ -624,7 +624,7 @@ end --- - {workspace}: (boolean, default true) --- - Set the list with workspace diagnostics function M.set_qflist(opts) - vim.api.nvim_echo({{'vim.lsp.diagnostic.set_qflist is deprecated. See :h deprecated', 'WarningMsg'}}, true, {}) + vim.notify_once('vim.lsp.diagnostic.set_qflist is deprecated. See :h deprecated', vim.log.levels.WARN) opts = opts or {} if opts.severity then opts.severity = severity_lsp_to_vim(opts.severity) @@ -656,7 +656,7 @@ end --- - {workspace}: (boolean, default false) --- - Set the list with workspace diagnostics function M.set_loclist(opts) - vim.api.nvim_echo({{'vim.lsp.diagnostic.set_loclist is deprecated. See :h deprecated', 'WarningMsg'}}, true, {}) + vim.notify_once('vim.lsp.diagnostic.set_loclist is deprecated. See :h deprecated', vim.log.levels.WARN) opts = opts or {} if opts.severity then opts.severity = severity_lsp_to_vim(opts.severity) @@ -684,7 +684,7 @@ end -- send diagnostic information and the client will still process it. The -- diagnostics are simply not displayed to the user. function M.disable(bufnr, client_id) - vim.api.nvim_echo({{'vim.lsp.diagnostic.disable is deprecated. See :h deprecated', 'WarningMsg'}}, true, {}) + vim.notify_once('vim.lsp.diagnostic.disable is deprecated. See :h deprecated', vim.log.levels.WARN) if not client_id then return vim.lsp.for_each_buffer_client(bufnr, function(client) M.disable(bufnr, client.id) @@ -705,7 +705,7 @@ end --- client. The default is to enable diagnostics for all attached --- clients. function M.enable(bufnr, client_id) - vim.api.nvim_echo({{'vim.lsp.diagnostic.enable is deprecated. See :h deprecated', 'WarningMsg'}}, true, {}) + vim.notify_once('vim.lsp.diagnostic.enable is deprecated. See :h deprecated', vim.log.levels.WARN) if not client_id then return vim.lsp.for_each_buffer_client(bufnr, function(client) M.enable(bufnr, client.id) diff --git a/runtime/lua/vim/lsp/handlers.lua b/runtime/lua/vim/lsp/handlers.lua index c974d1a6b4..f5aefd4402 100644 --- a/runtime/lua/vim/lsp/handlers.lua +++ b/runtime/lua/vim/lsp/handlers.lua @@ -111,13 +111,15 @@ M['client/registerCapability'] = function(_, _, ctx) end --see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_applyEdit -M['workspace/applyEdit'] = function(_, workspace_edit) +M['workspace/applyEdit'] = function(_, workspace_edit, ctx) if not workspace_edit then return end -- TODO(ashkan) Do something more with label? + local client_id = ctx.client_id + local client = vim.lsp.get_client_by_id(client_id) if workspace_edit.label then print("Workspace edit", workspace_edit.label) end - local status, result = pcall(util.apply_workspace_edit, workspace_edit.edit) + local status, result = pcall(util.apply_workspace_edit, workspace_edit.edit, client.offset_encoding) return { applied = status; failureReason = result; @@ -150,6 +152,17 @@ M['workspace/configuration'] = function(_, result, ctx) return response end +--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_workspaceFolders +M['workspace/workspaceFolders'] = function(_, _, ctx) + local client_id = ctx.client_id + local client = vim.lsp.get_client_by_id(client_id) + if not client then + err_message("LSP[id=", client_id, "] client has shut down after sending the message") + return + end + return client.workspace_folders or vim.NIL +end + M['textDocument/publishDiagnostics'] = function(...) return require('vim.lsp.diagnostic').on_publish_diagnostics(...) end @@ -159,6 +172,31 @@ M['textDocument/codeLens'] = function(...) end +--see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_references +M['textDocument/references'] = function(_, result, ctx, config) + if not result or vim.tbl_isempty(result) then + vim.notify('No references found') + else + local client = vim.lsp.get_client_by_id(ctx.client_id) + config = config or {} + if config.loclist then + vim.fn.setloclist(0, {}, ' ', { + title = 'References'; + items = util.locations_to_items(result, client.offset_encoding); + context = ctx; + }) + api.nvim_command("lopen") + else + vim.fn.setqflist({}, ' ', { + title = 'References'; + items = util.locations_to_items(result, client.offset_encoding); + context = ctx; + }) + api.nvim_command("botright copen") + end + end +end + ---@private --- Return a function that converts LSP responses to list items and opens the list @@ -169,23 +207,26 @@ end --- loclist: (boolean) use the location list (default is to use the quickfix list) --- ---@param map_result function `((resp, bufnr) -> list)` to convert the response ----@param entity name of the resource used in a `not found` error message -local function response_to_list(map_result, entity) - return function(_,result, ctx, config) +---@param entity string name of the resource used in a `not found` error message +---@param title_fn function Function to call to generate list title +local function response_to_list(map_result, entity, title_fn) + return function(_, result, ctx, config) if not result or vim.tbl_isempty(result) then vim.notify('No ' .. entity .. ' found') else config = config or {} if config.loclist then vim.fn.setloclist(0, {}, ' ', { - title = 'Language Server'; + title = title_fn(ctx); items = map_result(result, ctx.bufnr); + context = ctx; }) api.nvim_command("lopen") else vim.fn.setqflist({}, ' ', { - title = 'Language Server'; + title = title_fn(ctx); items = map_result(result, ctx.bufnr); + context = ctx; }) api.nvim_command("botright copen") end @@ -194,31 +235,36 @@ local function response_to_list(map_result, entity) end ---see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_references -M['textDocument/references'] = response_to_list(util.locations_to_items, 'references') - --see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_documentSymbol -M['textDocument/documentSymbol'] = response_to_list(util.symbols_to_items, 'document symbols') +M['textDocument/documentSymbol'] = response_to_list(util.symbols_to_items, 'document symbols', function(ctx) + local fname = vim.fn.fnamemodify(vim.uri_to_fname(ctx.params.textDocument.uri), ":.") + return string.format('Symbols in %s', fname) +end) --see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_symbol -M['workspace/symbol'] = response_to_list(util.symbols_to_items, 'symbols') +M['workspace/symbol'] = response_to_list(util.symbols_to_items, 'symbols', function(ctx) + return string.format("Symbols matching '%s'", ctx.params.query) +end) --see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_rename -M['textDocument/rename'] = function(_, result, _) +M['textDocument/rename'] = function(_, result, ctx, _) if not result then return end - util.apply_workspace_edit(result) + local client = vim.lsp.get_client_by_id(ctx.client_id) + util.apply_workspace_edit(result, client.offset_encoding) end --see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_rangeFormatting M['textDocument/rangeFormatting'] = function(_, result, ctx, _) if not result then return end - util.apply_text_edits(result, ctx.bufnr) + local client = vim.lsp.get_client_by_id(ctx.client_id) + util.apply_text_edits(result, ctx.bufnr, client.offset_encoding) end --see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_formatting M['textDocument/formatting'] = function(_, result, ctx, _) if not result then return end - util.apply_text_edits(result, ctx.bufnr) + local client = vim.lsp.get_client_by_id(ctx.client_id) + util.apply_text_edits(result, ctx.bufnr, client.offset_encoding) end --see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_completion @@ -246,7 +292,7 @@ end ---@param config table Configuration table. --- - border: (default=nil) --- - Add borders to the floating window ---- - See |vim.api.nvim_open_win()| +--- - See |nvim_open_win()| function M.hover(_, result, ctx, config) config = config or {} config.focus_id = ctx.method @@ -277,19 +323,23 @@ local function location_handler(_, result, ctx, _) local _ = log.info() and log.info(ctx.method, 'No location found') return nil end + local client = vim.lsp.get_client_by_id(ctx.client_id) -- textDocument/definition can return Location or Location[] -- https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_definition if vim.tbl_islist(result) then - util.jump_to_location(result[1]) + util.jump_to_location(result[1], client.offset_encoding) if #result > 1 then - vim.fn.setqflist({}, ' ', {title = 'LSP locations', items = util.locations_to_items(result)}) - api.nvim_command("copen") + vim.fn.setqflist({}, ' ', { + title = 'LSP locations', + items = util.locations_to_items(result, client.offset_encoding) + }) + api.nvim_command("botright copen") end else - util.jump_to_location(result) + util.jump_to_location(result, client.offset_encoding) end end @@ -380,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 @@ -439,14 +489,20 @@ for k, fn in pairs(M) do }) if err then - local client = vim.lsp.get_client_by_id(ctx.client_id) - local client_name = client and client.name or string.format("client_id=%d", ctx.client_id) -- LSP spec: -- interface ResponseError: -- code: integer; -- message: string; -- data?: string | number | boolean | array | object | null; - return err_message(client_name .. ': ' .. tostring(err.code) .. ': ' .. err.message) + + -- Per LSP, don't show ContentModified error to the user. + if err.code ~= protocol.ErrorCodes.ContentModified then + local client = vim.lsp.get_client_by_id(ctx.client_id) + local client_name = client and client.name or string.format("client_id=%d", ctx.client_id) + + err_message(client_name .. ': ' .. tostring(err.code) .. ': ' .. err.message) + end + return end return fn(err, result, ctx, config) diff --git a/runtime/lua/vim/lsp/log.lua b/runtime/lua/vim/lsp/log.lua index dbc473b52c..e0b5653587 100644 --- a/runtime/lua/vim/lsp/log.lua +++ b/runtime/lua/vim/lsp/log.lua @@ -8,8 +8,8 @@ local log = {} -- Log level dictionary with reverse lookup as well. -- -- Can be used to lookup the number from the name or the name from the number. --- Levels by name: 'trace', 'debug', 'info', 'warn', 'error' --- Level numbers begin with 'trace' at 0 +-- Levels by name: "TRACE", "DEBUG", "INFO", "WARN", "ERROR" +-- Level numbers begin with "TRACE" at 0 log.levels = vim.deepcopy(vim.log.levels) -- Default log level is warn. diff --git a/runtime/lua/vim/lsp/rpc.lua b/runtime/lua/vim/lsp/rpc.lua index 1fb75ddeb7..1ecac50df4 100644 --- a/runtime/lua/vim/lsp/rpc.lua +++ b/runtime/lua/vim/lsp/rpc.lua @@ -133,7 +133,8 @@ local function request_parser_loop() end end -local client_errors = vim.tbl_add_reverse_lookup { +--- Mapping of error codes used by the client +local client_errors = { INVALID_SERVER_MESSAGE = 1; INVALID_SERVER_JSON = 2; NO_RESULT_CALLBACK_FOUND = 3; @@ -143,6 +144,8 @@ local client_errors = vim.tbl_add_reverse_lookup { SERVER_RESULT_CALLBACK_ERROR = 7; } +client_errors = vim.tbl_add_reverse_lookup(client_errors) + --- Constructs an error message from an LSP error object. --- ---@param err (table) The error object diff --git a/runtime/lua/vim/lsp/sync.lua b/runtime/lua/vim/lsp/sync.lua index d01f45ad8f..0f4e5b572b 100644 --- a/runtime/lua/vim/lsp/sync.lua +++ b/runtime/lua/vim/lsp/sync.lua @@ -298,7 +298,7 @@ end ---@private -- rangelength depends on the offset encoding --- bytes for utf-8 (clangd with extenion) +-- bytes for utf-8 (clangd with extension) -- codepoints for utf-16 -- codeunits for utf-32 -- Line endings count here as 2 chars for \r\n (dos), 1 char for \n (unix), and 1 char for \r (mac) diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index 5921eb5bf0..655c3a4679 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -10,14 +10,6 @@ local uv = vim.loop local npcall = vim.F.npcall local split = vim.split -local _warned = {} -local warn_once = function(message) - if not _warned[message] then - vim.api.nvim_err_writeln(message) - _warned[message] = true - end -end - local M = {} local default_border = { @@ -201,6 +193,11 @@ end local function get_lines(bufnr, rows) rows = type(rows) == "table" and rows or { rows } + -- This is needed for bufload and bufloaded + if bufnr == 0 then + bufnr = vim.api.nvim_get_current_buf() + end + ---@private local function buf_lines() local lines = {} @@ -280,7 +277,7 @@ end ---@private --- Position is a https://microsoft.github.io/language-server-protocol/specifications/specification-current/#position --- Returns a zero-indexed column, since set_lines() does the conversion to ----@param offset_encoding string utf-8|utf-16|utf-32|nil defaults to utf-16 +---@param offset_encoding string utf-8|utf-16|utf-32 --- 1-indexed local function get_line_byte_from_position(bufnr, position, offset_encoding) -- LSP's line and characters are 0-indexed @@ -289,7 +286,7 @@ local function get_line_byte_from_position(bufnr, position, offset_encoding) -- When on the first character, we can ignore the difference between byte and -- character if col > 0 then - local line = get_line(bufnr, position.line) + local line = get_line(bufnr, position.line) or '' local ok, result ok, result = pcall(_str_byteindex_enc, line, col, offset_encoding) if ok then @@ -363,15 +360,14 @@ end --- Applies a list of text edits to a buffer. ---@param text_edits table list of `TextEdit` objects ---@param bufnr number Buffer id ----@param offset_encoding string utf-8|utf-16|utf-32|nil defaults to encoding of first client of `bufnr` +---@param offset_encoding string utf-8|utf-16|utf-32 defaults to encoding of first client of `bufnr` ---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textEdit function M.apply_text_edits(text_edits, bufnr, offset_encoding) validate { text_edits = { text_edits, 't', false }; bufnr = { bufnr, 'number', false }; - offset_encoding = { offset_encoding, 'string', true }; + offset_encoding = { offset_encoding, 'string', false }; } - offset_encoding = offset_encoding or M._get_offset_encoding(bufnr) if not next(text_edits) then return end if not api.nvim_buf_is_loaded(bufnr) then vim.fn.bufload(bufnr) @@ -405,25 +401,6 @@ function M.apply_text_edits(text_edits, bufnr, offset_encoding) end end) - -- Some LSP servers may return +1 range of the buffer content but nvim_buf_set_text can't accept it so we should fix it here. - local has_eol_text_edit = false - local max = vim.api.nvim_buf_line_count(bufnr) - local len = _str_utfindex_enc(vim.api.nvim_buf_get_lines(bufnr, -2, -1, false)[1] or '', nil, offset_encoding) - text_edits = vim.tbl_map(function(text_edit) - if max <= text_edit.range.start.line then - text_edit.range.start.line = max - 1 - text_edit.range.start.character = len - text_edit.newText = '\n' .. text_edit.newText - has_eol_text_edit = true - end - if max <= text_edit.range['end'].line then - text_edit.range['end'].line = max - 1 - text_edit.range['end'].character = len - has_eol_text_edit = true - end - return text_edit - end, text_edits) - -- Some LSP servers are depending on the VSCode behavior. -- The VSCode will re-locate the cursor position after applying TextEdit so we also do it. local is_current_buf = vim.api.nvim_get_current_buf() == bufnr @@ -443,16 +420,38 @@ function M.apply_text_edits(text_edits, bufnr, offset_encoding) -- Apply text edits. local is_cursor_fixed = false + local has_eol_text_edit = false for _, text_edit in ipairs(text_edits) do + -- Normalize line ending + text_edit.newText, _ = string.gsub(text_edit.newText, '\r\n?', '\n') + + -- Convert from LSP style ranges to Neovim style ranges. local e = { start_row = text_edit.range.start.line, - start_col = get_line_byte_from_position(bufnr, text_edit.range.start), + start_col = get_line_byte_from_position(bufnr, text_edit.range.start, offset_encoding), end_row = text_edit.range['end'].line, - end_col = get_line_byte_from_position(bufnr, text_edit.range['end']), + end_col = get_line_byte_from_position(bufnr, text_edit.range['end'], offset_encoding), text = vim.split(text_edit.newText, '\n', true), } + + -- Some LSP servers may return +1 range of the buffer content but nvim_buf_set_text can't accept it so we should fix it here. + local max = vim.api.nvim_buf_line_count(bufnr) + if max <= e.start_row or max <= e.end_row then + local len = #(get_line(bufnr, max - 1) or '') + if max <= e.start_row then + e.start_row = max - 1 + e.start_col = len + table.insert(e.text, 1, '') + end + if max <= e.end_row then + e.end_row = max - 1 + e.end_col = len + end + has_eol_text_edit = true + end vim.api.nvim_buf_set_text(bufnr, e.start_row, e.start_col, e.end_row, e.end_col, e.text) + -- Fix cursor position. local row_count = (e.end_row - e.start_row) + 1 if e.end_row < cursor.row then cursor.row = cursor.row + (#e.text - row_count) @@ -467,10 +466,13 @@ function M.apply_text_edits(text_edits, bufnr, offset_encoding) end end + local max = vim.api.nvim_buf_line_count(bufnr) + + -- Apply fixed cursor position. if is_cursor_fixed then local is_valid_cursor = true - is_valid_cursor = is_valid_cursor and cursor.row < vim.api.nvim_buf_line_count(bufnr) - is_valid_cursor = is_valid_cursor and cursor.col <= #(vim.api.nvim_buf_get_lines(bufnr, cursor.row, cursor.row + 1, false)[1] or '') + is_valid_cursor = is_valid_cursor and cursor.row < max + is_valid_cursor = is_valid_cursor and cursor.col <= #(get_line(bufnr, max - 1) or '') if is_valid_cursor then vim.api.nvim_win_set_cursor(0, { cursor.row + 1, cursor.col }) end @@ -479,7 +481,7 @@ function M.apply_text_edits(text_edits, bufnr, offset_encoding) -- Remove final line if needed local fix_eol = has_eol_text_edit fix_eol = fix_eol and api.nvim_buf_get_option(bufnr, 'fixeol') - fix_eol = fix_eol and (vim.api.nvim_buf_get_lines(bufnr, -2, -1, false)[1] or '') == '' + fix_eol = fix_eol and get_line(bufnr, max - 1) == '' if fix_eol then vim.api.nvim_buf_set_lines(bufnr, -2, -1, false, {}) end @@ -517,9 +519,12 @@ end ---@param text_document_edit table: a `TextDocumentEdit` object ---@param index number: Optional index of the edit, if from a list of edits (or nil, if not from a list) ---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocumentEdit -function M.apply_text_document_edit(text_document_edit, index) +function M.apply_text_document_edit(text_document_edit, index, offset_encoding) local text_document = text_document_edit.textDocument local bufnr = vim.uri_to_bufnr(text_document.uri) + if offset_encoding == nil then + vim.notify_once("apply_text_document_edit must be called with valid offset encoding", vim.log.levels.WARN) + end -- For lists of text document edits, -- do not check the version after the first edit. @@ -538,7 +543,7 @@ function M.apply_text_document_edit(text_document_edit, index) return end - M.apply_text_edits(text_document_edit.edits, bufnr) + M.apply_text_edits(text_document_edit.edits, bufnr, offset_encoding) end --- Parses snippets in a completion entry. @@ -735,9 +740,13 @@ end --- Applies a `WorkspaceEdit`. --- ----@param workspace_edit (table) `WorkspaceEdit` +---@param workspace_edit table `WorkspaceEdit` +---@param offset_encoding string utf-8|utf-16|utf-32 (required) --see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_applyEdit -function M.apply_workspace_edit(workspace_edit) +function M.apply_workspace_edit(workspace_edit, offset_encoding) + if offset_encoding == nil then + vim.notify_once("apply_workspace_edit must be called with valid offset encoding", vim.log.levels.WARN) + end if workspace_edit.documentChanges then for idx, change in ipairs(workspace_edit.documentChanges) do if change.kind == "rename" then @@ -753,7 +762,7 @@ function M.apply_workspace_edit(workspace_edit) elseif change.kind then error(string.format("Unsupported change: %q", vim.inspect(change))) else - M.apply_text_document_edit(change, idx) + M.apply_text_document_edit(change, idx, offset_encoding) end end return @@ -766,7 +775,7 @@ function M.apply_workspace_edit(workspace_edit) for uri, changes in pairs(all_changes) do local bufnr = vim.uri_to_bufnr(uri) - M.apply_text_edits(changes, bufnr) + M.apply_text_edits(changes, bufnr, offset_encoding) end end @@ -842,7 +851,8 @@ function M.convert_signature_help_to_markdown_lines(signature_help, ft, triggers local active_hl local active_signature = signature_help.activeSignature or 0 -- If the activeSignature is not inside the valid range, then clip it. - if active_signature >= #signature_help.signatures then + -- In 3.15 of the protocol, activeSignature was allowed to be negative + if active_signature >= #signature_help.signatures or active_signature < 0 then active_signature = 0 end local signature = signature_help.signatures[active_signature + 1] @@ -983,12 +993,16 @@ end --- Jumps to a location. --- ----@param location (`Location`|`LocationLink`) +---@param location table (`Location`|`LocationLink`) +---@param offset_encoding string utf-8|utf-16|utf-32 (required) ---@returns `true` if the jump succeeded -function M.jump_to_location(location) +function M.jump_to_location(location, offset_encoding) -- location may be Location or LocationLink local uri = location.uri or location.targetUri if uri == nil then return end + if offset_encoding == nil then + vim.notify_once("jump_to_location must be called with valid offset encoding", vim.log.levels.WARN) + end local bufnr = vim.uri_to_bufnr(uri) -- Save position in jumplist vim.cmd "normal! m'" @@ -1000,10 +1014,10 @@ function M.jump_to_location(location) --- Jump to new location (adjusting for UTF-16 encoding of characters) api.nvim_set_current_buf(bufnr) - api.nvim_buf_set_option(0, 'buflisted', true) + api.nvim_buf_set_option(bufnr, 'buflisted', true) local range = location.range or location.targetSelectionRange local row = range.start.line - local col = get_line_byte_from_position(0, range.start) + local col = get_line_byte_from_position(bufnr, range.start, offset_encoding) api.nvim_win_set_cursor(0, {row + 1, col}) -- Open folds under the cursor vim.cmd("normal! zv") @@ -1505,18 +1519,20 @@ do --[[ References ]] ---@param bufnr number Buffer id function M.buf_clear_references(bufnr) validate { bufnr = {bufnr, 'n', true} } - api.nvim_buf_clear_namespace(bufnr, reference_ns, 0, -1) + api.nvim_buf_clear_namespace(bufnr or 0, reference_ns, 0, -1) end --- Shows a list of document highlights for a certain buffer. --- ---@param bufnr number Buffer id ---@param references table List of `DocumentHighlight` objects to highlight - ---@param offset_encoding string One of "utf-8", "utf-16", "utf-32", or nil. Defaults to `offset_encoding` of first client of `bufnr` + ---@param offset_encoding string One of "utf-8", "utf-16", "utf-32". ---@see https://microsoft.github.io/language-server-protocol/specifications/specification-3-17/#documentHighlight function M.buf_highlight_references(bufnr, references, offset_encoding) - validate { bufnr = {bufnr, 'n', true} } - offset_encoding = offset_encoding or M._get_offset_encoding(bufnr) + validate { + bufnr = {bufnr, 'n', true}, + offset_encoding = { offset_encoding, 'string', false }; + } for _, reference in ipairs(references) do local start_line, start_char = reference["range"]["start"]["line"], reference["range"]["start"]["character"] local end_line, end_char = reference["range"]["end"]["line"], reference["range"]["end"]["character"] @@ -1534,7 +1550,8 @@ do --[[ References ]] reference_ns, document_highlight_kind[kind], { start_line, start_idx }, - { end_line, end_idx }) + { end_line, end_idx }, + { priority = vim.highlight.priorities.user }) end end end @@ -1549,9 +1566,14 @@ end) --- The result can be passed to the {list} argument of |setqflist()| or --- |setloclist()|. --- ----@param locations (table) list of `Location`s or `LocationLink`s +---@param locations table list of `Location`s or `LocationLink`s +---@param offset_encoding string offset_encoding for locations utf-8|utf-16|utf-32 ---@returns (table) list of items -function M.locations_to_items(locations) +function M.locations_to_items(locations, offset_encoding) + if offset_encoding == nil then + vim.notify_once("locations_to_items must be called with valid offset encoding", vim.log.levels.WARN) + end + local items = {} local grouped = setmetatable({}, { __index = function(t, k) @@ -1591,7 +1613,7 @@ function M.locations_to_items(locations) local pos = temp.start local row = pos.line local line = lines[row] or "" - local col = pos.character + local col = M._str_byteindex_enc(line, pos.character, offset_encoding) table.insert(items, { filename = filename, lnum = row + 1, @@ -1677,7 +1699,7 @@ function M.symbols_to_items(symbols, bufnr) end return _items end - return _symbols_to_items(symbols, {}, bufnr) + return _symbols_to_items(symbols, {}, bufnr or 0) end --- Removes empty lines from the beginning and end. @@ -1775,7 +1797,13 @@ function M._get_offset_encoding(bufnr) local offset_encoding for _, client in pairs(vim.lsp.buf_get_clients(bufnr)) do - local this_offset_encoding = client.offset_encoding or "utf-16" + if client.offset_encoding == nil then + vim.notify_once( + string.format("Client (id: %s) offset_encoding is nil. Do not unset offset_encoding.", client.id), + vim.log.levels.ERROR + ) + end + local this_offset_encoding = client.offset_encoding if not offset_encoding then offset_encoding = this_offset_encoding elseif offset_encoding ~= this_offset_encoding then @@ -1796,7 +1824,7 @@ end ---@returns { textDocument = { uri = `current_file_uri` }, range = { start = ---`current_position`, end = `current_position` } } function M.make_range_params(window, offset_encoding) - local buf = vim.api.nvim_win_get_buf(window) + local buf = vim.api.nvim_win_get_buf(window or 0) offset_encoding = offset_encoding or M._get_offset_encoding(buf) local position = make_position_param(window, offset_encoding) return { @@ -1822,7 +1850,7 @@ function M.make_given_range_params(start_pos, end_pos, bufnr, offset_encoding) end_pos = {end_pos, 't', true}; offset_encoding = {offset_encoding, 's', true}; } - bufnr = bufnr or 0 + bufnr = bufnr or vim.api.nvim_get_current_buf() offset_encoding = offset_encoding or M._get_offset_encoding(bufnr) local A = list_extend({}, start_pos or api.nvim_buf_get_mark(bufnr, '<')) local B = list_extend({}, end_pos or api.nvim_buf_get_mark(bufnr, '>')) @@ -1904,7 +1932,9 @@ end ---@returns (number, number) `offset_encoding` index of the character in line {row} column {col} in buffer {buf} function M.character_offset(buf, row, col, offset_encoding) local line = get_line(buf, row) - offset_encoding = offset_encoding or M._get_offset_encoding(buf) + if offset_encoding == nil then + vim.notify_once("character_offset must be called with valid offset encoding", vim.log.levels.WARN) + end -- If the col is past the EOL, use the line length. if col > #line then return _str_utfindex_enc(line, nil, offset_encoding) @@ -1928,7 +1958,6 @@ function M.lookup_section(settings, section) end M._get_line_byte_from_position = get_line_byte_from_position -M._warn_once = warn_once M.buf_versions = {} diff --git a/runtime/lua/vim/shared.lua b/runtime/lua/vim/shared.lua index 1cf618725d..e170befa4c 100644 --- a/runtime/lua/vim/shared.lua +++ b/runtime/lua/vim/shared.lua @@ -526,13 +526,23 @@ end --- => error('arg1: expected even number, got 3') --- </pre> --- ----@param opt Map of parameter names to validations. Each key is a parameter +--- If multiple types are valid they can be given as a list. +--- <pre> +--- vim.validate{arg1={{'foo'}, {'table', 'string'}}, arg2={'foo', {'table', 'string'}}} +--- => NOP (success) +--- +--- vim.validate{arg1={1, {'string', table'}}} +--- => error('arg1: expected string|table, got number') +--- +--- </pre> +--- +---@param opt table of parameter names to validations. Each key is a parameter --- name; each value is a tuple in one of these forms: --- 1. (arg_value, type_name, optional) --- - arg_value: argument value ---- - type_name: string type name, one of: ("table", "t", "string", +--- - type_name: string|table type name, one of: ("table", "t", "string", --- "s", "number", "n", "boolean", "b", "function", "f", "nil", ---- "thread", "userdata") +--- "thread", "userdata") or list of them. --- - optional: (optional) boolean, if true, `nil` is valid --- 2. (arg_value, fn, msg) --- - arg_value: argument value @@ -571,31 +581,43 @@ do end local val = spec[1] -- Argument value. - local t = spec[2] -- Type name, or callable. + local types = spec[2] -- Type name, or callable. local optional = (true == spec[3]) - if type(t) == 'string' then - local t_name = type_names[t] - if not t_name then - return false, string.format('invalid type name: %s', t) - end + if type(types) == 'string' then + types = {types} + end - if (not optional or val ~= nil) and not _is_type(val, t_name) then - return false, string.format("%s: expected %s, got %s", param_name, t_name, type(val)) - end - elseif vim.is_callable(t) then + if vim.is_callable(types) then -- Check user-provided validation function. - local valid, optional_message = t(val) + local valid, optional_message = types(val) if not valid then - local error_message = string.format("%s: expected %s, got %s", param_name, (spec[3] or '?'), val) + local error_message = string.format("%s: expected %s, got %s", param_name, (spec[3] or '?'), tostring(val)) if optional_message ~= nil then error_message = error_message .. string.format(". Info: %s", optional_message) end return false, error_message end + elseif type(types) == 'table' then + local success = false + for i, t in ipairs(types) do + local t_name = type_names[t] + if not t_name then + return false, string.format('invalid type name: %s', t) + end + types[i] = t_name + + if (optional and val == nil) or _is_type(val, t_name) then + success = true + break + end + end + if not success then + return false, string.format("%s: expected %s, got %s", param_name, table.concat(types, '|'), type(val)) + end else - return false, string.format("invalid type name: %s", tostring(t)) + return false, string.format("invalid type name: %s", tostring(types)) end end diff --git a/runtime/lua/vim/treesitter.lua b/runtime/lua/vim/treesitter.lua index 07f6418c0c..f9d539f028 100644 --- a/runtime/lua/vim/treesitter.lua +++ b/runtime/lua/vim/treesitter.lua @@ -11,6 +11,7 @@ local parsers = {} local M = vim.tbl_extend("error", query, language) M.language_version = vim._ts_get_language_version() +M.minimum_language_version = vim._ts_get_minimum_language_version() setmetatable(M, { __index = function (t, k) 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/language.lua b/runtime/lua/vim/treesitter/language.lua index 6f347ff25f..8b106108df 100644 --- a/runtime/lua/vim/treesitter/language.lua +++ b/runtime/lua/vim/treesitter/language.lua @@ -14,7 +14,7 @@ function M.require_language(lang, path, silent) return true end if path == nil then - local fname = 'parser/' .. lang .. '.*' + local fname = 'parser/' .. vim.fn.fnameescape(lang) .. '.*' local paths = a.nvim_get_runtime_file(fname, false) if #paths == 0 then if silent then diff --git a/runtime/lua/vim/treesitter/languagetree.lua b/runtime/lua/vim/treesitter/languagetree.lua index 594765761d..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 an 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 ebed502c92..8383551b5f 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -138,30 +138,43 @@ function M.get_query(lang, query_name) end end +local query_cache = setmetatable({}, { + __index = function(tbl, key) + rawset(tbl, key, {}) + return rawget(tbl, key) + end +}) + --- 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`. --- - `info.patterns` contains information about predicates. --- ----@param lang The language ----@param query A string containing the query (s-expr syntax) +---@param lang string The language +---@param query string A string containing the query (s-expr syntax) --- ---@returns The query function M.parse_query(lang, query) language.require_language(lang) - local self = setmetatable({}, Query) - self.query = vim._ts_parse_query(lang, query) - self.info = self.query:inspect() - self.captures = self.info.captures - return self + local cached = query_cache[lang][query] + if cached then + return cached + else + local self = setmetatable({}, Query) + self.query = vim._ts_parse_query(lang, query) + self.info = self.query:inspect() + self.captures = self.info.captures + query_cache[lang][query] = self + return self + end end --- Gets the text corresponding to a given node @@ -186,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 9568b60fd0..9d4b38f08a 100644 --- a/runtime/lua/vim/ui.lua +++ b/runtime/lua/vim/ui.lua @@ -18,6 +18,24 @@ local M = {} --- Called once the user made a choice. --- `idx` is the 1-based index of `item` within `item`. --- `nil` if the user aborted the dialog. +--- +--- +--- Example: +--- <pre> +--- 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) +--- </pre> + function M.select(items, opts, on_choice) vim.validate { items = { items, 'table', false }, @@ -57,6 +75,13 @@ end --- Called once the user confirms or abort the input. --- `input` is what the user typed. --- `nil` if the user aborted the dialog. +--- +--- Example: +--- <pre> +--- vim.ui.input({ prompt = 'Enter value for shiftwidth: ' }, function(input) +--- vim.o.shiftwidth = tonumber(input) +--- end) +--- </pre> function M.input(opts, on_confirm) vim.validate { on_confirm = { on_confirm, 'function', false }, diff --git a/runtime/nvim.appdata.xml b/runtime/nvim.appdata.xml index 225dd79878..4ad656f1a3 100644 --- a/runtime/nvim.appdata.xml +++ b/runtime/nvim.appdata.xml @@ -26,7 +26,9 @@ </screenshots> <releases> + <release date="2021-12-31" version="0.6.1"/> <release date="2021-11-30" version="0.6.0"/> + <release date="2021-09-26" version="0.5.1"/> <release date="2021-07-02" version="0.5.0"/> <release date="2020-08-04" version="0.4.4"/> <release date="2019-11-06" version="0.4.3"/> diff --git a/runtime/pack/dist/opt/matchit/autoload/matchit.vim b/runtime/pack/dist/opt/matchit/autoload/matchit.vim index e8689980ae..eafb7c0551 100644 --- a/runtime/pack/dist/opt/matchit/autoload/matchit.vim +++ b/runtime/pack/dist/opt/matchit/autoload/matchit.vim @@ -763,9 +763,9 @@ fun! s:ParseSkip(str) let skip = "synIDattr(synID(line('.'),col('.'),1),'name') !~? '" .. \ strpart(skip,2) .. "'" elseif skip[0] == "r" - let skip = "strpart(getline('.'),0,col('.'))=~'" . strpart(skip,2). "'" + let skip = "strpart(getline('.'),0,col('.'))=~'" .. strpart(skip,2) .. "'" elseif skip[0] == "R" - let skip = "strpart(getline('.'),0,col('.'))!~'" . strpart(skip,2). "'" + let skip = "strpart(getline('.'),0,col('.'))!~'" .. strpart(skip,2) .. "'" endif endif return skip diff --git a/runtime/pack/dist/opt/termdebug/plugin/termdebug.vim b/runtime/pack/dist/opt/termdebug/plugin/termdebug.vim index f9978a6b00..49d9389773 100644 --- a/runtime/pack/dist/opt/termdebug/plugin/termdebug.vim +++ b/runtime/pack/dist/opt/termdebug/plugin/termdebug.vim @@ -2,7 +2,7 @@ " " Author: Bram Moolenaar " Copyright: Vim license applies, see ":help license" -" Last Change: 2021 Dec 16 +" Last Change: 2022 Jan 17 " " WORK IN PROGRESS - Only the basics work " Note: On MS-Windows you need a recent version of gdb. The one included with @@ -153,7 +153,7 @@ func s:StartDebug_internal(dict) if &columns < g:termdebug_wide let s:save_columns = &columns let &columns = g:termdebug_wide - " If we make the Vim window wider, use the whole left halve for the debug + " If we make the Vim window wider, use the whole left half for the debug " windows. let s:allleft = 1 endif @@ -1051,10 +1051,10 @@ func s:GetEvaluationExpression(range, arg) return expr endfunc -" clean up expression that may got in because of range +" clean up expression that may get in because of range " (newlines and surrounding whitespace) " As it can also be specified via ex-command for assignments this function -" may not change the "content" parts (like replacing contained spaces +" may not change the "content" parts (like replacing contained spaces) func s:CleanupExpr(expr) " replace all embedded newlines/tabs/... let expr = substitute(a:expr, '\_s', ' ', 'g') @@ -1084,7 +1084,7 @@ func s:HandleEvaluate(msg) \ ->substitute('.*value="\(.*\)"', '\1', '') \ ->substitute('\\"', '"', 'g') \ ->substitute('\\\\', '\\', 'g') - "\ multi-byte characters arrive in octal form, replace everthing but NULL values + "\ multi-byte characters arrive in octal form, replace everything but NULL values \ ->substitute('\\000', s:NullRepl, 'g') \ ->substitute('\\\o\o\o', {-> eval('"' .. submatch(0) .. '"')}, 'g') "\ Note: GDB docs also mention hex encodings - the translations below work @@ -1344,6 +1344,16 @@ func s:HandleCursor(msg) if lnum =~ '^[0-9]*$' call s:GotoSourcewinOrCreateIt() if expand('%:p') != fnamemodify(fname, ':p') + echomsg 'different fname: "' .. expand('%:p') .. '" vs "' .. fnamemodify(fname, ':p') .. '"' + augroup Termdebug + " Always open a file read-only instead of showing the ATTENTION + " prompt, since we are unlikely to want to edit the file. + " The file may be changed but not saved, warn for that. + au SwapExists * echohl WarningMsg + \ | echo 'Warning: file is being edited elsewhere' + \ | echohl None + \ | let v:swapchoice = 'o' + augroup END if &modified " TODO: find existing window exe 'split ' . fnameescape(fname) @@ -1352,6 +1362,9 @@ func s:HandleCursor(msg) else exe 'edit ' . fnameescape(fname) endif + augroup Termdebug + au! SwapExists + augroup END endif exe lnum normal! zv diff --git a/runtime/scripts.vim b/runtime/scripts.vim index 3790b1c10f..dd47f65ba0 100644 --- a/runtime/scripts.vim +++ b/runtime/scripts.vim @@ -384,7 +384,7 @@ else set ft=scheme " Git output - elseif s:line1 =~# '^\(commit\|tree\|object\) \x\{40\}\>\|^tag \S\+$' + elseif s:line1 =~# '^\(commit\|tree\|object\) \x\{40,\}\>\|^tag \S\+$' set ft=git " Gprof (gnu profiler) @@ -406,6 +406,12 @@ else elseif s:line1 =~# '^#.*by RouterOS.*$' set ft=routeros + " Sed scripts + " #ncomment is allowed but most likely a false positive so require a space + " before any trailing comment text + elseif s:line1 =~# '^#n\%($\|\s\)' + set ft=sed + " CVS diff else let s:lnum = 1 diff --git a/runtime/syntax/basic.vim b/runtime/syntax/basic.vim index ad9450b3b8..7fe411a869 100644 --- a/runtime/syntax/basic.vim +++ b/runtime/syntax/basic.vim @@ -1,14 +1,15 @@ " Vim syntax file -" Language: BASIC +" Language: BASIC (QuickBASIC 4.5) " Maintainer: Doug Kearns <dougkearns@gmail.com> " Previous Maintainer: Allan Kelly <allan@fruitloaf.co.uk> " Contributors: Thilo Six -" Last Change: 2015 Jan 10 +" Last Change: 2021 Aug 08 " First version based on Micro$soft QBASIC circa 1989, as documented in " 'Learn BASIC Now' by Halvorson&Rygmyr. Microsoft Press 1989. -" This syntax file not a complete implementation yet. Send suggestions to the -" maintainer. +" +" Second version attempts to match Microsoft QuickBASIC 4.5 while keeping FreeBASIC +" (-lang qb) and QB64 (excluding extensions) in mind. -- DJK " Prelude {{{1 if exists("b:current_syntax") @@ -18,154 +19,357 @@ endif let s:cpo_save = &cpo set cpo&vim +syn iskeyword @,48-57,.,!,#,%,&,$ +syn case ignore + +" Whitespace Errors {{{1 +if exists("basic_space_errors") + if !exists("basic_no_trail_space_error") + syn match basicSpaceError display excludenl "\s\+$" + endif + if !exists("basic_no_tab_space_error") + syn match basicSpaceError display " \+\t"me=e-1 + endif +endif + +" Comment Errors {{{1 +if !exists("basic_no_comment_errors") + syn match basicCommentError "\<REM\>.*" +endif + +" Not Top Cluster {{{1 +syn cluster basicNotTop contains=@basicLineIdentifier,basicDataString,basicDataSeparator,basicTodo + +" Statements {{{1 + +syn cluster basicStatements contains=basicStatement,basicDataStatement,basicMetaRemStatement,basicPutStatement,basicRemStatement + +let s:statements =<< trim EOL " {{{2 + beep + bload + bsave + call + calls + case + chain + chdir + circle + clear + close + cls + color + com + common + const + declare + def + def\s\+seg + defdbl + defint + deflng + defsng + defstr + dim + do + draw + elseif + end + end\s\+\%(def\|function\|if\|select\|sub\|type\) + environ + erase + error + exit\s\+\%(def\|do\|for\|function\|sub\) + field + files + for + function + get + gosub + goto + if + input + ioctl + key + kill + let + line + line\s\+input + locate + lock + loop + lprint + lset + mkdir + name + next + on + on\s\+error + on\s\+uevent + open + open\s\+com + option + out + paint + palette + palette\s\+using + pcopy + pen + pmap + poke + preset + print + pset + randomize + read + redim + reset + restore + resume + return + rmdir + rset + run + select\s\+case + shared + shell + sleep + sound + static + stop + strig + sub + swap + system + troff + tron + type + uevent + unlock + using + view + view\s\+print + wait + wend + while + width + window + write +EOL +" }}} + +for s in s:statements + exe 'syn match basicStatement "\<' .. s .. '\>" contained' +endfor + +syn match basicStatement "\<\%(then\|else\)\>" nextgroup=@basicStatements skipwhite + +" DATA Statement +syn match basicDataSeparator "," contained +syn region basicDataStatement matchgroup=basicStatement start="\<data\>" matchgroup=basicStatementSeparator end=":\|$" contained contains=basicDataSeparator,basicDataString,basicNumber,basicFloat,basicString + +if !exists("basic_no_data_fold") + syn region basicMultilineData start="^\s*\<data\>.*\n\%(^\s*\<data\>\)\@=" end="^\s*\<data\>.*\n\%(^\s*\<data\>\)\@!" contains=basicDataStatement transparent fold keepend +endif + +" PUT File I/O and Graphics statements - needs special handling for graphics +" action verbs +syn match basicPutAction "\<\%(pset\|preset\|and\|or\|xor\)\>" contained +syn region basicPutStatement matchgroup=basicStatement start="\<put\>" matchgroup=basicStatementSeparator end=":\|$" contained contains=basicKeyword,basicPutAction,basicFilenumber + " Keywords {{{1 -syn keyword basicStatement BEEP beep Beep BLOAD bload Bload BSAVE bsave Bsave -syn keyword basicStatement CALL call Call ABSOLUTE absolute Absolute -syn keyword basicStatement CHAIN chain Chain CHDIR chdir Chdir -syn keyword basicStatement CIRCLE circle Circle CLEAR clear Clear -syn keyword basicStatement CLOSE close Close CLS cls Cls COLOR color Color -syn keyword basicStatement COM com Com COMMON common Common -syn keyword basicStatement CONST const Const DATA data Data -syn keyword basicStatement DECLARE declare Declare DEF def Def -syn keyword basicStatement DEFDBL defdbl Defdbl DEFINT defint Defint -syn keyword basicStatement DEFLNG deflng Deflng DEFSNG defsng Defsng -syn keyword basicStatement DEFSTR defstr Defstr DIM dim Dim -syn keyword basicStatement DO do Do LOOP loop Loop -syn keyword basicStatement DRAW draw Draw END end End -syn keyword basicStatement ENVIRON environ Environ ERASE erase Erase -syn keyword basicStatement ERROR error Error EXIT exit Exit -syn keyword basicStatement FIELD field Field FILES files Files -syn keyword basicStatement FOR for For NEXT next Next -syn keyword basicStatement FUNCTION function Function GET get Get -syn keyword basicStatement GOSUB gosub Gosub GOTO goto Goto -syn keyword basicStatement IF if If THEN then Then ELSE else Else -syn keyword basicStatement INPUT input Input INPUT# input# Input# -syn keyword basicStatement IOCTL ioctl Ioctl KEY key Key -syn keyword basicStatement KILL kill Kill LET let Let -syn keyword basicStatement LINE line Line LOCATE locate Locate -syn keyword basicStatement LOCK lock Lock UNLOCK unlock Unlock -syn keyword basicStatement LPRINT lprint Lprint USING using Using -syn keyword basicStatement LSET lset Lset MKDIR mkdir Mkdir -syn keyword basicStatement NAME name Name ON on On -syn keyword basicStatement ERROR error Error OPEN open Open -syn keyword basicStatement OPTION option Option BASE base Base -syn keyword basicStatement OUT out Out PAINT paint Paint -syn keyword basicStatement PALETTE palette Palette PCOPY pcopy Pcopy -syn keyword basicStatement PEN pen Pen PLAY play Play -syn keyword basicStatement PMAP pmap Pmap POKE poke Poke -syn keyword basicStatement PRESET preset Preset PRINT print Print -syn keyword basicStatement PRINT# print# Print# USING using Using -syn keyword basicStatement PSET pset Pset PUT put Put -syn keyword basicStatement RANDOMIZE randomize Randomize READ read Read -syn keyword basicStatement REDIM redim Redim RESET reset Reset -syn keyword basicStatement RESTORE restore Restore RESUME resume Resume -syn keyword basicStatement RETURN return Return RMDIR rmdir Rmdir -syn keyword basicStatement RSET rset Rset RUN run Run -syn keyword basicStatement SEEK seek Seek SELECT select Select -syn keyword basicStatement CASE case Case SHARED shared Shared -syn keyword basicStatement SHELL shell Shell SLEEP sleep Sleep -syn keyword basicStatement SOUND sound Sound STATIC static Static -syn keyword basicStatement STOP stop Stop STRIG strig Strig -syn keyword basicStatement SUB sub Sub SWAP swap Swap -syn keyword basicStatement SYSTEM system System TIMER timer Timer -syn keyword basicStatement TROFF troff Troff TRON tron Tron -syn keyword basicStatement TYPE type Type UNLOCK unlock Unlock -syn keyword basicStatement VIEW view View WAIT wait Wait -syn keyword basicStatement WHILE while While WEND wend Wend -syn keyword basicStatement WIDTH width Width WINDOW window Window -syn keyword basicStatement WRITE write Write DATE$ date$ Date$ -syn keyword basicStatement MID$ mid$ Mid$ TIME$ time$ Time$ - -syn keyword basicFunction ABS abs Abs ASC asc Asc -syn keyword basicFunction ATN atn Atn CDBL cdbl Cdbl -syn keyword basicFunction CINT cint Cint CLNG clng Clng -syn keyword basicFunction COS cos Cos CSNG csng Csng -syn keyword basicFunction CSRLIN csrlin Csrlin CVD cvd Cvd -syn keyword basicFunction CVDMBF cvdmbf Cvdmbf CVI cvi Cvi -syn keyword basicFunction CVL cvl Cvl CVS cvs Cvs -syn keyword basicFunction CVSMBF cvsmbf Cvsmbf EOF eof Eof -syn keyword basicFunction ERDEV erdev Erdev ERL erl Erl -syn keyword basicFunction ERR err Err EXP exp Exp -syn keyword basicFunction FILEATTR fileattr Fileattr FIX fix Fix -syn keyword basicFunction FRE fre Fre FREEFILE freefile Freefile -syn keyword basicFunction INP inp Inp INSTR instr Instr -syn keyword basicFunction INT int Int LBOUND lbound Lbound -syn keyword basicFunction LEN len Len LOC loc Loc -syn keyword basicFunction LOF lof Lof LOG log Log -syn keyword basicFunction LPOS lpos Lpos PEEK peek Peek -syn keyword basicFunction PEN pen Pen POINT point Point -syn keyword basicFunction POS pos Pos RND rnd Rnd -syn keyword basicFunction SADD sadd Sadd SCREEN screen Screen -syn keyword basicFunction SEEK seek Seek SETMEM setmem Setmem -syn keyword basicFunction SGN sgn Sgn SIN sin Sin -syn keyword basicFunction SPC spc Spc SQR sqr Sqr -syn keyword basicFunction STICK stick Stick STRIG strig Strig -syn keyword basicFunction TAB tab Tab TAN tan Tan -syn keyword basicFunction UBOUND ubound Ubound VAL val Val -syn keyword basicFunction VALPTR valptr Valptr VALSEG valseg Valseg -syn keyword basicFunction VARPTR varptr Varptr VARSEG varseg Varseg -syn keyword basicFunction CHR$ Chr$ chr$ COMMAND$ command$ Command$ -syn keyword basicFunction DATE$ date$ Date$ ENVIRON$ environ$ Environ$ -syn keyword basicFunction ERDEV$ erdev$ Erdev$ HEX$ hex$ Hex$ -syn keyword basicFunction INKEY$ inkey$ Inkey$ INPUT$ input$ Input$ -syn keyword basicFunction IOCTL$ ioctl$ Ioctl$ LCASES$ lcases$ Lcases$ -syn keyword basicFunction LAFT$ laft$ Laft$ LTRIM$ ltrim$ Ltrim$ -syn keyword basicFunction MID$ mid$ Mid$ MKDMBF$ mkdmbf$ Mkdmbf$ -syn keyword basicFunction MKD$ mkd$ Mkd$ MKI$ mki$ Mki$ -syn keyword basicFunction MKL$ mkl$ Mkl$ MKSMBF$ mksmbf$ Mksmbf$ -syn keyword basicFunction MKS$ mks$ Mks$ OCT$ oct$ Oct$ -syn keyword basicFunction RIGHT$ right$ Right$ RTRIM$ rtrim$ Rtrim$ -syn keyword basicFunction SPACE$ space$ Space$ STR$ str$ Str$ -syn keyword basicFunction STRING$ string$ String$ TIME$ time$ Time$ -syn keyword basicFunction UCASE$ ucase$ Ucase$ VARPTR$ varptr$ Varptr$ +let s:keywords =<< trim EOL " {{{2 + absolute + access + alias + append + as + base + binary + byval + cdecl + com + def + do + for + function + gosub + goto + input + int86old + int86xold + interrupt + interruptx + is + key + len + list + local + lock + lprint + next + off + on + output + pen + play + random + read + resume + screen + seg + shared + signal + static + step + stop + strig + sub + timer + to + until + using + while + write +EOL +" }}} + +for k in s:keywords + exe 'syn match basicKeyword "\<' .. k .. '\>"' +endfor + +" Functions {{{1 +syn keyword basicFunction abs asc atn cdbl chr$ cint clng command$ cos csng +syn keyword basicFunction csrlin cvd cvdmbf cvi cvl cvs cvsmbf environ$ eof +syn keyword basicFunction erdev erdev$ erl err exp fileattr fix fre freefile +syn keyword basicFunction hex$ inkey$ inp input$ instr int ioctl$ left$ lbound +syn keyword basicFunction lcase$ len loc lof log lpos ltrim$ mkd$ mkdmbf$ mki$ +syn keyword basicFunction mkl$ mks$ mksmbf$ oct$ peek pen point pos right$ rnd +syn keyword basicFunction rtrim$ sadd setmem sgn sin space$ spc sqr stick str$ +syn keyword basicFunction strig string$ tab tan ubound ucase$ val valptr +syn keyword basicFunction valseg varptr varptr$ varseg + +" Functions and statements (same name) {{{1 +syn match basicStatement "\<\%(date\$\|mid\$\|play\|screen\|seek\|time\$\|timer\)\>" contained +syn match basicFunction "\<\%(date\$\|mid\$\|play\|screen\|seek\|time\$\|timer\)\>" + +" Types {{{1 +syn keyword basicType integer long single double string any + +" Strings {{{1 + +" Unquoted DATA strings - anything except [:,] and leading or trailing whitespace +" Needs lower priority than numbers +syn match basicDataString "[^[:space:],:]\+\%(\s\+[^[:space:],:]\+\)*" contained + +syn region basicString start=+"+ end=+"+ oneline + +" Booleans {{{1 +if exists("basic_booleans") + syn keyword basicBoolean true false +endif " Numbers {{{1 -" Integer number, or floating point number without a dot. -syn match basicNumber "\<\d\+\>" -" Floating point number, with dot -syn match basicNumber "\<\d\+\.\d*\>" -" Floating point number, starting with a dot -syn match basicNumber "\.\d\+\>" -" String and Character constants {{{1 -syn match basicSpecial "\\\d\d\d\|\\." contained -syn region basicString start=+"+ skip=+\\\\\|\\"+ end=+"+ contains=basicSpecial +" Integers +syn match basicNumber "-\=&o\=\o\+[%&]\=\>" +syn match basicNumber "-\=&h\x\+[%&]\=\>" +syn match basicNumber "-\=\<\d\+[%&]\=\>" + +" Floats +syn match basicFloat "-\=\<\d\+\.\=\d*\%(\%([ed][+-]\=\d*\)\|[!#]\)\=\>" +syn match basicFloat "-\=\<\.\d\+\%(\%([ed][+-]\=\d*\)\|[!#]\)\=\>" -" Line numbers {{{1 -syn region basicLineNumber start="^\d" end="\s" +" Statement anchors {{{1 +syn match basicLineStart "^" nextgroup=@basicStatements,@basicLineIdentifier skipwhite +syn match basicStatementSeparator ":" nextgroup=@basicStatements skipwhite -" Data-type suffixes {{{1 -syn match basicTypeSpecifier "[a-zA-Z0-9][$%&!#]"ms=s+1 -" Used with OPEN statement -syn match basicFilenumber "#\d\+" +" Line numbers and labels {{{1 + +" QuickBASIC limits these to 65,529 and 40 chars respectively +syn match basicLineNumber "\d\+" nextgroup=@basicStatements skipwhite contained +syn match basicLineLabel "\a[[:alnum:]]*\ze\s*:" nextgroup=@basicStatements skipwhite contained + +syn cluster basicLineIdentifier contains=basicLineNumber,basicLineLabel + +" Line Continuation {{{1 +syn match basicLineContinuation "\s*\zs_\ze\s*$" + +" Type suffixes {{{1 +if exists("basic_type_suffixes") + syn match basicTypeSuffix "\a[[:alnum:].]*\zs[$%&!#]" +endif -" Mathematical operators {{{1 -" syn match basicMathsOperator "[<>+\*^/\\=-]" -syn match basicMathsOperator "-\|=\|[:<>+\*^/\\]\|AND\|OR" +" File numbers {{{1 +syn match basicFilenumber "#\d\+" +syn match basicFilenumber "#\a[[:alnum:].]*[%&!#]\=" + +" Operators {{{1 +if exists("basic_operators") + syn match basicArithmeticOperator "[-+*/\\^]" + syn match basicRelationalOperator "<>\|<=\|>=\|[><=]" +endif +syn match basicLogicalOperator "\<\%(not\|and\|or\|xor\|eqv\|imp\)\>" +syn match basicArithmeticOperator "\<mod\>" + +" Metacommands {{{1 +" Note: No trailing word boundaries. Text may be freely mixed however there +" must be only leading whitespace prior to the first metacommand +syn match basicMetacommand "$INCLUDE\s*:\s*'[^']\+'" contained containedin=@basicMetaComments +syn match basicMetacommand "$\%(DYNAMIC\|STATIC\)" contained containedin=@basicMetaComments " Comments {{{1 -syn keyword basicTodo TODO FIXME XXX NOTE contained -syn region basicComment start="^\s*\zsREM\>" start="\%(:\s*\)\@<=REM\>" end="$" contains=basicTodo -syn region basicComment start="'" end="$" contains=basicTodo +syn keyword basicTodo TODO FIXME XXX NOTE contained + +syn region basicRemStatement matchgroup=basicStatement start="REM\>" end="$" contains=basicTodo,@Spell contained +syn region basicComment start="'" end="$" contains=basicTodo,@Spell + +if !exists("basic_no_comment_fold") + syn region basicMultilineComment start="^\s*'.*\n\%(\s*'\)\@=" end="^\s*'.*\n\%(\s*'\)\@!" contains=@basicComments transparent fold keepend +endif + +" Metacommands +syn region basicMetaRemStatement matchgroup=basicStatement start="REM\>\s*\$\@=" end="$" contains=basicTodo contained +syn region basicMetaComment start="'\s*\$\@=" end="$" contains=basicTodo + +syn cluster basicMetaComments contains=basicMetaComment,basicMetaRemStatement +syn cluster basicComments contains=basicComment,basicMetaComment "syn sync ccomment basicComment " Default Highlighting {{{1 -hi def link basicLabel Label -hi def link basicConditional Conditional -hi def link basicRepeat Repeat -hi def link basicLineNumber Comment -hi def link basicNumber Number -hi def link basicError Error -hi def link basicStatement Statement -hi def link basicString String -hi def link basicComment Comment -hi def link basicSpecial Special -hi def link basicTodo Todo -hi def link basicFunction Identifier -hi def link basicTypeSpecifier Type -hi def link basicFilenumber basicTypeSpecifier -"hi basicMathsOperator term=bold cterm=bold gui=bold +hi def link basicArithmeticOperator basicOperator +hi def link basicBoolean Boolean +hi def link basicComment Comment +hi def link basicCommentError Error +hi def link basicDataString basicString +hi def link basicFilenumber basicTypeSuffix " TODO: better group +hi def link basicFloat Float +hi def link basicFunction Identifier +hi def link basicKeyword Keyword +hi def link basicLineIdentifier LineNr +hi def link basicLineContinuation Special +hi def link basicLineLabel basicLineIdentifier +hi def link basicLineNumber basicLineIdentifier +hi def link basicLogicalOperator basicOperator +hi def link basicMetacommand SpecialComment +hi def link basicMetaComment Comment +hi def link basicMetaRemStatement Comment +hi def link basicNumber Number +hi def link basicOperator Operator +hi def link basicPutAction Keyword +hi def link basicRelationalOperator basicOperator +hi def link basicRemStatement Comment +hi def link basicSpaceError Error +hi def link basicStatementSeparator Special +hi def link basicStatement Statement +hi def link basicString String +hi def link basicTodo Todo +hi def link basicType Type +hi def link basicTypeSuffix Special +if exists("basic_legacy_syntax_groups") + hi def link basicTypeSpecifier Type + hi def link basicTypeSuffix basicTypeSpecifier +endif " Postscript {{{1 let b:current_syntax = "basic" diff --git a/runtime/syntax/checkhealth.vim b/runtime/syntax/checkhealth.vim index dff880a0bc..37f1822740 100644 --- a/runtime/syntax/checkhealth.vim +++ b/runtime/syntax/checkhealth.vim @@ -12,7 +12,9 @@ unlet! b:current_syntax syn case match " We do not care about markdown syntax errors -syn clear markdownError +if hlexists('markdownError') + syn clear markdownError +endif syn keyword healthError ERROR[:] containedin=markdownCodeBlock,mkdListItemLine syn keyword healthWarning WARNING[:] containedin=markdownCodeBlock,mkdListItemLine diff --git a/runtime/syntax/git.vim b/runtime/syntax/git.vim index a8467edd43..bf013ce195 100644 --- a/runtime/syntax/git.vim +++ b/runtime/syntax/git.vim @@ -1,7 +1,7 @@ " Vim syntax file " Language: generic git output " Maintainer: Tim Pope <vimNOSPAM@tpope.org> -" Last Change: 2019 Dec 05 +" Last Change: 2022 Jan 05 if exists("b:current_syntax") finish @@ -12,12 +12,28 @@ syn sync minlines=50 syn include @gitDiff syntax/diff.vim -syn region gitHead start=/\%^/ end=/^$/ -syn region gitHead start=/\%(^commit\%( \x\{40\}\)\{1,\}\%(\s*(.*)\)\=$\)\@=/ end=/^$/ - -" For git reflog and git show ...^{tree}, avoid sync issues -syn match gitHead /^\d\{6\} \%(\w\{4} \)\=\x\{40\}\%( [0-3]\)\=\t.*/ -syn match gitHead /^\x\{40\} \x\{40}\t.*/ +syn region gitHead start=/\%^\%(tag \|tree \|object \)\@=/ end=/^$/ contains=@NoSpell +syn region gitHead start=/\%(^commit\%( \x\{4,\}\)\{1,\}\%(\s*(.*)\)\=$\)\@=/ end=/^$/ contains=@NoSpell +" git log --oneline +" minimize false positives by verifying contents of buffer +if getline(1) =~# '^\x\{7,\} ' && getline('$') =~# '^\x\{7,\} ' + syn match gitHashAbbrev /^\x\{7,\} \@=/ contains=@NoSpell +elseif getline(1) =~# '^[|\/\\_ ]\{-\}\*[|\/\\_ ]\{-\} \x\{7,\} ' + syn match gitHashAbbrev /^[|\/\\_ ]\{-\}\*[|\/\\_ ]\{-\} \zs\x\{7,\} \@=/ contains=@NoSpell +endif +" git log --graph +syn region gitGraph start=/\%(^[|\/\\_ ]*\*[|\/\\_ ]\{-\} commit\%( \x\{4,\}\)\{1,\}\%(\s*(.*)\)\=$\)\@=/ end=/^\%([|\/\\_ ]*$\)\@=/ contains=@NoSpell +" git blame --porcelain +syn region gitHead start=/\%(^\x\{40,\} \d\+ \d\+\%( \d\+\)\=$\)\@=/ end=/^\t\@=/ contains=@NoSpell +" git ls-tree +syn match gitMode /^\d\{6\}\%( \%(blob\|tree\) \x\{4,\}\t\)\@=/ nextgroup=gitType skipwhite contains=@NoSpell +" git ls-files --stage +syn match gitMode /^\d\{6\}\%( \x\{4,\} [0-3]\t\)\@=/ nextgroup=gitHashStage skipwhite contains=@NoSpell +" .git/HEAD, .git/refs/ +syn match gitKeyword /\%^ref: \@=/ nextgroup=gitReference skipwhite contains=@NoSpell +syn match gitHash /\%^\x\{40,}\%$/ skipwhite contains=@NoSpell +" .git/logs/ +syn match gitReflog /^\x\{40,\} \x\{40,\} .\{-\}\d\+\s-\d\{4\}\t.*/ skipwhite contains=@NoSpell,gitReflogOld syn region gitDiff start=/^\%(diff --git \)\@=/ end=/^\%(diff --\|$\)\@=/ contains=@gitDiff fold syn region gitDiff start=/^\%(@@ -\)\@=/ end=/^\%(diff --\%(git\|cc\|combined\) \|$\)\@=/ contains=@gitDiff @@ -25,35 +41,47 @@ syn region gitDiff start=/^\%(@@ -\)\@=/ end=/^\%(diff --\%(git\|cc\|combined\) syn region gitDiffMerge start=/^\%(diff --\%(cc\|combined\) \)\@=/ end=/^\%(diff --\|$\)\@=/ contains=@gitDiff syn region gitDiffMerge start=/^\%(@@@@* -\)\@=/ end=/^\%(diff --\|$\)\@=/ contains=@gitDiff syn match gitDiffAdded "^ \++.*" contained containedin=gitDiffMerge -syn match gitDiffAdded "{+.*+}" contained containedin=gitDiff +syn match gitDiffAdded "{+[^}]*+}" contained containedin=gitDiff syn match gitDiffRemoved "^ \+-.*" contained containedin=gitDiffMerge -syn match gitDiffRemoved "\[-.*-\]" contained containedin=gitDiff +syn match gitDiffRemoved "\[-[^]]*-\]" contained containedin=gitDiff + +syn match gitKeyword /^commit \@=/ contained containedin=gitHead nextgroup=gitHashAbbrev skipwhite contains=@NoSpell +syn match gitKeyword /^\%(object\|tree\|parent\|encoding\|gpgsig\%(-\w\+\)\=\|previous\) \@=/ contained containedin=gitHead nextgroup=gitHash skipwhite contains=@NoSpell +syn match gitKeyword /^Merge:/ contained containedin=gitHead nextgroup=gitHashAbbrev skipwhite contains=@NoSpell +syn match gitIdentityKeyword /^\%(author\|committer\|tagger\) \@=/ contained containedin=gitHead nextgroup=gitIdentity skipwhite contains=@NoSpell +syn match gitIdentityHeader /^\%(Author\|Commit\|Tagger\):/ contained containedin=gitHead nextgroup=gitIdentity skipwhite contains=@NoSpell +syn match gitDateHeader /^\%(AuthorDate\|CommitDate\|Date\):/ contained containedin=gitHead nextgroup=gitDate skipwhite contains=@NoSpell -syn match gitKeyword /^\%(object\|type\|tag\|commit\|tree\|parent\|encoding\)\>/ contained containedin=gitHead nextgroup=gitHash,gitType skipwhite -syn match gitKeyword /^\%(tag\>\|ref:\)/ contained containedin=gitHead nextgroup=gitReference skipwhite -syn match gitKeyword /^Merge:/ contained containedin=gitHead nextgroup=gitHashAbbrev skipwhite -syn match gitMode /^\d\{6\}\>/ contained containedin=gitHead nextgroup=gitType,gitHash skipwhite -syn match gitIdentityKeyword /^\%(author\|committer\|tagger\)\>/ contained containedin=gitHead nextgroup=gitIdentity skipwhite -syn match gitIdentityHeader /^\%(Author\|Commit\|Tagger\):/ contained containedin=gitHead nextgroup=gitIdentity skipwhite -syn match gitDateHeader /^\%(AuthorDate\|CommitDate\|Date\):/ contained containedin=gitHead nextgroup=gitDate skipwhite +syn match gitKeyword /^[*|\/\\_ ]\+\zscommit \@=/ contained containedin=gitGraph nextgroup=gitHashAbbrev skipwhite contains=@NoSpell +syn match gitKeyword /^[|\/\\_ ]\+\zs\%(object\|tree\|parent\|encoding\|gpgsig\%(-\w\+\)\=\|previous\) \@=/ contained containedin=gitGraph nextgroup=gitHash skipwhite contains=@NoSpell +syn match gitKeyword /^[|\/\\_ ]\+\zsMerge:/ contained containedin=gitGraph nextgroup=gitHashAbbrev skipwhite contains=@NoSpell +syn match gitIdentityKeyword /^[|\/\\_ ]\+\zs\%(author\|committer\|tagger\) \@=/ contained containedin=gitGraph nextgroup=gitIdentity skipwhite contains=@NoSpell +syn match gitIdentityHeader /^[|\/\\_ ]\+\zs\%(Author\|Commit\|Tagger\):/ contained containedin=gitGraph nextgroup=gitIdentity skipwhite contains=@NoSpell +syn match gitDateHeader /^[|\/\\_ ]\+\zs\%(AuthorDate\|CommitDate\|Date\):/ contained containedin=gitGraph nextgroup=gitDate skipwhite contains=@NoSpell -syn match gitReflogHeader /^Reflog:/ contained containedin=gitHead nextgroup=gitReflogMiddle skipwhite -syn match gitReflogHeader /^Reflog message:/ contained containedin=gitHead skipwhite -syn match gitReflogMiddle /\S\+@{\d\+} (/he=e-2 nextgroup=gitIdentity +syn match gitKeyword /^type \@=/ contained containedin=gitHead nextgroup=gitType skipwhite contains=@NoSpell +syn match gitKeyword /^\%(summary\|boundary\|filename\|\%(author\|committer\)-\%(time\|tz\)\) \@=/ contained containedin=gitHead skipwhite contains=@NoSpell +syn match gitKeyword /^tag \@=/ contained containedin=gitHead nextgroup=gitReference skipwhite contains=@NoSpell +syn match gitIdentityKeyword /^\%(author\|committer\)-mail \@=/ contained containedin=gitHead nextgroup=gitEmail skipwhite contains=@NoSpell +syn match gitReflogHeader /^Reflog:/ contained containedin=gitHead nextgroup=gitReflogMiddle skipwhite contains=@NoSpell +syn match gitReflogHeader /^Reflog message:/ contained containedin=gitHead skipwhite contains=@NoSpell +syn match gitReflogMiddle /\S\+@{\d\+} (/he=e-2 nextgroup=gitIdentity contains=@NoSpell -syn match gitDate /\<\u\l\l \u\l\l \d\=\d \d\d:\d\d:\d\d \d\d\d\d [+-]\d\d\d\d/ contained -syn match gitDate /-\=\d\+ [+-]\d\d\d\d\>/ contained -syn match gitDate /\<\d\+ \l\+ ago\>/ contained -syn match gitType /\<\%(tag\|commit\|tree\|blob\)\>/ contained nextgroup=gitHash skipwhite -syn match gitStage /\<\d\t\@=/ contained -syn match gitReference /\S\+\S\@!/ contained -syn match gitHash /\<\x\{40\}\>/ contained nextgroup=gitIdentity,gitStage,gitHash skipwhite -syn match gitHash /^\<\x\{40\}\>/ containedin=gitHead contained nextgroup=gitHash skipwhite -syn match gitHashAbbrev /\<\x\{4,40\}\>/ contained nextgroup=gitHashAbbrev skipwhite -syn match gitHashAbbrev /\<\x\{4,39\}\.\.\./he=e-3 contained nextgroup=gitHashAbbrev skipwhite +syn match gitIdentity /\S.\{-\} <[^>]*>/ contained nextgroup=gitDate skipwhite contains=@NoSpell +syn region gitEmail matchgroup=gitEmailDelimiter start=/</ end=/>/ keepend oneline contained containedin=gitIdentity contains=@NoSpell +syn match gitDate /\<\u\l\l \u\l\l \d\=\d \d\d:\d\d:\d\d \d\d\d\d [+-]\d\d\d\d/ contained contains=@NoSpell +syn match gitDate /-\=\d\+ [+-]\d\d\d\d\>/ contained contains=@NoSpell +syn match gitDate /\<\d\+ \l\+ ago\>/ contained contains=@NoSpell +syn match gitType /\<\%(tag\|commit\|tree\|blob\)\>/ contained nextgroup=gitHashAbbrev skipwhite contains=@NoSpell +syn match gitReference /\S\+\S\@!/ contained contains=@NoSpell +syn match gitHash /\<\x\{40,\}\>/ contained nextgroup=gitIdentity,gitHash skipwhite contains=@NoSpell +syn match gitReflogOld /^\x\{40,\} \@=/ contained nextgroup=gitReflogNew skipwhite contains=@NoSpell +syn match gitReflogNew /\<\x\{40,\} \@=/ contained nextgroup=gitIdentity skipwhite contains=@NoSpell +syn match gitHashAbbrev /\<\x\{4,\}\>/ contained nextgroup=gitHashAbbrev skipwhite contains=@NoSpell +syn match gitHashAbbrev /\<\x\{4,39\}\.\.\./he=e-3 contained nextgroup=gitHashAbbrev skipwhite contains=@NoSpell +syn match gitHashStage /\<\x\{4,\}\>/ contained nextgroup=gitStage skipwhite contains=@NoSpell +syn match gitStage /\<\d\t\@=/ contained contains=@NoSpell -syn match gitIdentity /\S.\{-\} <[^>]*>/ contained nextgroup=gitDate skipwhite -syn region gitEmail matchgroup=gitEmailDelimiter start=/</ end=/>/ keepend oneline contained containedin=gitIdentity syn match gitNotesHeader /^Notes:\ze\n / @@ -68,7 +96,10 @@ hi def link gitEmailDelimiter Delimiter hi def link gitEmail Special hi def link gitDate Number hi def link gitMode Number +hi def link gitHashStage gitHash hi def link gitHashAbbrev gitHash +hi def link gitReflogOld gitHash +hi def link gitReflogNew gitHash hi def link gitHash Identifier hi def link gitReflogMiddle gitReference hi def link gitReference Function diff --git a/runtime/syntax/gitcommit.vim b/runtime/syntax/gitcommit.vim index 63b1ce920f..42c8d4414f 100644 --- a/runtime/syntax/gitcommit.vim +++ b/runtime/syntax/gitcommit.vim @@ -2,71 +2,87 @@ " Language: git commit file " Maintainer: Tim Pope <vimNOSPAM@tpope.org> " Filenames: *.git/COMMIT_EDITMSG -" Last Change: 2019 Dec 05 +" Last Change: 2022 Jan 05 if exists("b:current_syntax") finish endif +scriptencoding utf-8 + syn case match syn sync minlines=50 +syn sync linebreaks=1 if has("spell") syn spell toplevel endif syn include @gitcommitDiff syntax/diff.vim -syn region gitcommitDiff start=/\%(^diff --\%(git\|cc\|combined\) \)\@=/ end=/^\%(diff --\|$\|#\)\@=/ fold contains=@gitcommitDiff +syn region gitcommitDiff start=/\%(^diff --\%(git\|cc\|combined\) \)\@=/ end=/^\%(diff --\|$\|@@\@!\|[^[:alnum:]\ +-]\S\@!\)\@=/ fold contains=@gitcommitDiff syn match gitcommitSummary "^.*\%<51v." contained containedin=gitcommitFirstLine nextgroup=gitcommitOverflow contains=@Spell syn match gitcommitOverflow ".*" contained contains=@Spell -syn match gitcommitBlank "^[^#].*" contained contains=@Spell +syn match gitcommitBlank "^.\+" contained contains=@Spell +syn match gitcommitFirstLine "\%^.*" nextgroup=gitcommitBlank,gitcommitComment skipnl + +let s:scissors = 0 +let s:l = search('^[#;@!$%^&|:] -\{24,\} >8 -\{24,\}$', 'cnW', '', 100) +if s:l == 0 + let s:l = line('$') +elseif getline(s:l)[0] !=# getline(s:l - 1)[0] + let s:scissors = 1 +endif +let s:comment = escape((matchstr(getline(s:l), '^[#;@!$%^&|:]\S\@!') . '#')[0], '^$.*[]~\"/') -if get(g:, "gitcommit_cleanup") is# "scissors" - syn match gitcommitFirstLine "\%^.*" nextgroup=gitcommitBlank skipnl - syn region gitcommitComment start=/^# -\+ >8 -\+$/ end=/\%$/ contains=gitcommitDiff +if s:scissors + let s:comment .= ' -\{24,\} >8 -\{24,\}$' + exe 'syn region gitcommitComment start="^' . s:comment . '" end="\%$" contains=gitcommitDiff' else - syn match gitcommitFirstLine "\%^[^#].*" nextgroup=gitcommitBlank skipnl - syn match gitcommitComment "^#.*" + exe 'syn match gitcommitComment "^' . s:comment . '.*"' endif +exe 'syn match gitcommitTrailers "\n\@<=\n\%([[:alnum:]-]\+\s*:.*\|(cherry picked from commit .*\)\%(\n\s.*\|\n[[:alnum:]-]\+\s*:.*\|\n(cherry picked from commit .*\)*\%(\n\n*\%(' . s:comment . '\)\|\n*\%$\)\@="' -syn match gitcommitHead "^\%(# .*\n\)\+#$" contained transparent -syn match gitcommitOnBranch "\%(^# \)\@<=On branch" contained containedin=gitcommitComment nextgroup=gitcommitBranch skipwhite -syn match gitcommitOnBranch "\%(^# \)\@<=Your branch .\{-\} '" contained containedin=gitcommitComment nextgroup=gitcommitBranch skipwhite +unlet s:l s:comment s:scissors + +syn match gitcommitTrailerToken "^[[:alnum:]-]\+\s*:" contained containedin=gitcommitTrailers + +syn match gitcommitHash "\<\x\{40,}\>" contains=@NoSpell display +syn match gitcommitOnBranch "\%(^. \)\@<=On branch" contained containedin=gitcommitComment nextgroup=gitcommitBranch skipwhite +syn match gitcommitOnBranch "\%(^. \)\@<=Your branch .\{-\} '" contained containedin=gitcommitComment nextgroup=gitcommitBranch skipwhite syn match gitcommitBranch "[^ ']\+" contained -syn match gitcommitNoBranch "\%(^# \)\@<=Not currently on any branch." contained containedin=gitcommitComment -syn match gitcommitHeader "\%(^# \)\@<=.*:$" contained containedin=gitcommitComment -syn region gitcommitAuthor matchgroup=gitCommitHeader start=/\%(^# \)\@<=\%(Author\|Committer\):/ end=/$/ keepend oneline contained containedin=gitcommitComment transparent -syn match gitcommitNoChanges "\%(^# \)\@<=No changes$" contained containedin=gitcommitComment +syn match gitcommitNoBranch "\%(^. \)\@<=Not currently on any branch." contained containedin=gitcommitComment +syn match gitcommitHeader "\%(^. \)\@<=\S.*[::]\%(\n^$\)\@!$" contained containedin=gitcommitComment +syn region gitcommitAuthor matchgroup=gitCommitHeader start=/\%(^. \)\@<=\%(Author\|Committer\|Date\):/ end=/$/ keepend oneline contained containedin=gitcommitComment transparent +syn match gitcommitHeader "\%(^. \)\@<=commit\%( \x\{40,\}$\)\@=" contained containedin=gitcommitComment nextgroup=gitcommitHash skipwhite +syn match gitcommitNoChanges "\%(^. \)\@<=No changes$" contained containedin=gitcommitComment -syn region gitcommitUntracked start=/^# Untracked files:/ end=/^#$\|^#\@!/ contains=gitcommitHeader,gitcommitHead,gitcommitUntrackedFile fold -syn match gitcommitUntrackedFile "\t\@<=.*" contained +syn match gitcommitType "\%(^.\t\)\@<=[^[:punct:][:space:]][^/::]*[^[:punct:][:space:]][::]\ze "he=e-1 contained containedin=gitcommitComment nextgroup=gitcommitFile skipwhite +syn match gitcommitFile ".\{-\}\%($\| -> \)\@=" contained nextgroup=gitcommitArrow +syn match gitcommitArrow " -> " contained nextgroup=gitcommitFile +syn match gitcommitUntrackedFile "\%(^.\t\)\@<=[^::/]*\%(/.*\)\=$" contained containedin=gitcommitComment -syn region gitcommitDiscarded start=/^# Change\%(s not staged for commit\|d but not updated\):/ end=/^#$\|^#\@!/ contains=gitcommitHeader,gitcommitHead,gitcommitDiscardedType fold -syn region gitcommitSelected start=/^# Changes to be committed:/ end=/^#$\|^#\@!/ contains=gitcommitHeader,gitcommitHead,gitcommitSelectedType fold -syn region gitcommitUnmerged start=/^# Unmerged paths:/ end=/^#$\|^#\@!/ contains=gitcommitHeader,gitcommitHead,gitcommitUnmergedType fold +syn region gitcommitUntracked start=/^\z(.\) Untracked files:$/ end=/^\z1\=$\|^\z1\@!/ contains=gitcommitHeader containedin=gitcommitComment containedin=gitcommitComment contained transparent fold +syn region gitcommitDiscarded start=/^\z(.\) Change\%(s not staged for commit\|d but not updated\):$/ end=/^\z1\=$\|^\z1\@!/ contains=gitcommitHeader,gitcommitDiscardedType containedin=gitcommitComment containedin=gitcommitComment contained transparent fold +syn region gitcommitSelected start=/^\z(.\) Changes to be committed:$/ end=/^\z1$\|^\z1\@!/ contains=gitcommitHeader,gitcommitSelectedType containedin=gitcommitComment containedin=gitcommitComment contained transparent fold +syn region gitcommitUnmerged start=/^\z(.\) Unmerged paths:$/ end=/^\z1\=$\|^\z1\@!/ contains=gitcommitHeader,gitcommitUnmergedType containedin=gitcommitComment containedin=gitcommitComment contained transparent fold +syn match gitcommitUntrackedFile "\%(^.\t\)\@<=.*" contained containedin=gitcommitUntracked -syn match gitcommitDiscardedType "\t\@<=[[:lower:]][^:]*[[:lower:]]: "he=e-2 contained containedin=gitcommitComment nextgroup=gitcommitDiscardedFile skipwhite -syn match gitcommitSelectedType "\t\@<=[[:lower:]][^:]*[[:lower:]]: "he=e-2 contained containedin=gitcommitComment nextgroup=gitcommitSelectedFile skipwhite -syn match gitcommitUnmergedType "\t\@<=[[:lower:]][^:]*[[:lower:]]: "he=e-2 contained containedin=gitcommitComment nextgroup=gitcommitUnmergedFile skipwhite -syn match gitcommitDiscardedFile ".\{-\}\%($\| -> \)\@=" contained nextgroup=gitcommitDiscardedArrow -syn match gitcommitSelectedFile ".\{-\}\%($\| -> \)\@=" contained nextgroup=gitcommitSelectedArrow -syn match gitcommitUnmergedFile ".\{-\}\%($\| -> \)\@=" contained nextgroup=gitcommitSelectedArrow +syn match gitcommitDiscardedType "\%(^.\t\)\@<=[^[:punct:][:space:]][^/::]*[^[:punct:][:space:]][::]\ze "he=e-1 contained nextgroup=gitcommitDiscardedFile skipwhite +syn match gitcommitSelectedType "\%(^.\t\)\@<=[^[:punct:][:space:]][^/::]*[^[:punct:][:space:]][::]\ze "he=e-1 contained nextgroup=gitcommitSelectedFile skipwhite +syn match gitcommitUnmergedType "\%(^.\t\)\@<=[^[:punct:][:space:]][^/::]*[^[:punct:][:space:]][::]\ze "he=e-1 contained nextgroup=gitcommitUnmergedFile skipwhite +syn match gitcommitDiscardedFile "\S.\{-\}\%($\| -> \)\@=" contained nextgroup=gitcommitDiscardedArrow +syn match gitcommitSelectedFile "\S.\{-\}\%($\| -> \)\@=" contained nextgroup=gitcommitSelectedArrow +syn match gitcommitUnmergedFile "\S.\{-\}\%($\| -> \)\@=" contained nextgroup=gitcommitUnmergedArrow syn match gitcommitDiscardedArrow " -> " contained nextgroup=gitcommitDiscardedFile syn match gitcommitSelectedArrow " -> " contained nextgroup=gitcommitSelectedFile -syn match gitcommitUnmergedArrow " -> " contained nextgroup=gitcommitSelectedFile - -syn match gitcommitWarning "\%^[^#].*: needs merge$" nextgroup=gitcommitWarning skipnl -syn match gitcommitWarning "^[^#].*: needs merge$" nextgroup=gitcommitWarning skipnl contained -syn match gitcommitWarning "^\%(no changes added to commit\|nothing \%(added \)\=to commit\)\>.*\%$" +syn match gitcommitUnmergedArrow " -> " contained nextgroup=gitcommitUnmergedFile hi def link gitcommitSummary Keyword +hi def link gitcommitTrailerToken Label hi def link gitcommitComment Comment -hi def link gitcommitUntracked gitcommitComment -hi def link gitcommitDiscarded gitcommitComment -hi def link gitcommitSelected gitcommitComment -hi def link gitcommitUnmerged gitcommitComment +hi def link gitcommitHash Identifier hi def link gitcommitOnBranch Comment hi def link gitcommitBranch Special hi def link gitcommitNoBranch gitCommitBranch diff --git a/runtime/syntax/gitrebase.vim b/runtime/syntax/gitrebase.vim index bc6f34d1a7..13f157b005 100644 --- a/runtime/syntax/gitrebase.vim +++ b/runtime/syntax/gitrebase.vim @@ -2,7 +2,7 @@ " Language: git rebase --interactive " Maintainer: Tim Pope <vimNOSPAM@tpope.org> " Filenames: git-rebase-todo -" Last Change: 2019 Dec 06 +" Last Change: 2022 Jan 05 if exists("b:current_syntax") finish @@ -10,8 +10,10 @@ endif syn case match -syn match gitrebaseHash "\v<\x{7,}>" contained -syn match gitrebaseCommit "\v<\x{7,}>" nextgroup=gitrebaseSummary skipwhite +let s:c = escape((matchstr(getline('$'), '^[#;@!$%^&|:]\S\@!') . '#')[0], '^$.*[]~\"/') + +syn match gitrebaseHash "\v<\x{7,}>" contained contains=@NoSpell +syn match gitrebaseCommit "\v<\x{7,}>" nextgroup=gitrebaseSummary skipwhite contains=@NoSpell syn match gitrebasePick "\v^p%(ick)=>" nextgroup=gitrebaseCommit skipwhite syn match gitrebaseReword "\v^r%(eword)=>" nextgroup=gitrebaseCommit skipwhite syn match gitrebaseEdit "\v^e%(dit)=>" nextgroup=gitrebaseCommit skipwhite @@ -26,12 +28,15 @@ syn match gitrebaseLabel "\v^l(abel)=>" nextgroup=gitrebaseName skipwhite syn match gitrebaseReset "\v^(t|reset)=>" nextgroup=gitrebaseName skipwhite syn match gitrebaseSummary ".*" contains=gitrebaseHash contained syn match gitrebaseCommand ".*" contained -syn match gitrebaseComment "^\s*#.*" contains=gitrebaseHash +exe 'syn match gitrebaseComment " \@<=' . s:c . ' empty$" containedin=gitrebaseSummary contained' +exe 'syn match gitrebaseComment "^\s*' . s:c . '.*" contains=gitrebaseHash' syn match gitrebaseSquashError "\v%^%(s%(quash)=>|f%(ixup)=>)" nextgroup=gitrebaseCommit skipwhite syn match gitrebaseMergeOption "\v-[Cc]>" nextgroup=gitrebaseMergeCommit skipwhite contained syn match gitrebaseMergeCommit "\v<\x{7,}>" nextgroup=gitrebaseName skipwhite contained syn match gitrebaseName "\v[^[:space:].*?i:^~/-]\S+" nextgroup=gitrebaseMergeComment skipwhite contained -syn match gitrebaseMergeComment "#" nextgroup=gitrebaseSummary skipwhite contained +exe 'syn match gitrebaseMergeComment "' . s:c . '" nextgroup=gitrebaseSummary skipwhite contained' + +unlet s:c hi def link gitrebaseCommit gitrebaseHash hi def link gitrebaseHash Identifier diff --git a/runtime/syntax/i3config.vim b/runtime/syntax/i3config.vim index a8b6637140..a2f50e50b8 100644 --- a/runtime/syntax/i3config.vim +++ b/runtime/syntax/i3config.vim @@ -1,8 +1,9 @@ " Vim syntax file " Language: i3 config file -" Maintainer: Mohamed Boughaba <mohamed dot bgb at gmail dot com> +" Original Author: Mohamed Boughaba <mohamed dot bgb at gmail dot com> +" Maintainer: Quentin Hibon (github user hiqua) " Version: 0.4 -" Last Change: 2021 Dec 14 +" Last Change: 2022 Jan 15 " References: " http://i3wm.org/docs/userguide.html#configuring @@ -174,7 +175,7 @@ syn keyword i3ConfigDrawingMarksKeyword show_marks contained syn match i3ConfigDrawingMarks /^\s*show_marks\s\+\(yes\|no\)\s\?$/ contains=i3ConfigFocusWrappingType,i3ConfigDrawingMarksKeyword " Group mode/bar -syn keyword i3ConfigBlockKeyword mode bar colors i3bar_command status_command position exec mode hidden_state modifier id position output background statusline tray_output tray_padding separator separator_symbol workspace_buttons strip_workspace_numbers binding_mode_indicator focused_workspace active_workspace inactive_workspace urgent_workspace binding_mode contained +syn keyword i3ConfigBlockKeyword mode bar colors i3bar_command status_command position exec mode hidden_state modifier id position output background statusline tray_output tray_padding separator separator_symbol workspace_min_width workspace_buttons strip_workspace_numbers binding_mode_indicator focused_workspace active_workspace inactive_workspace urgent_workspace binding_mode contained syn region i3ConfigBlock start=+.*s\?{$+ end=+^}$+ contains=i3ConfigBlockKeyword,i3ConfigString,i3ConfigBind,i3ConfigComment,i3ConfigFont,i3ConfigFocusWrappingType,i3ConfigColor,i3ConfigVariable transparent keepend extend " Line continuation diff --git a/runtime/syntax/qb64.vim b/runtime/syntax/qb64.vim new file mode 100644 index 0000000000..a777e14481 --- /dev/null +++ b/runtime/syntax/qb64.vim @@ -0,0 +1,409 @@ +" Vim syntax file +" Language: QB64 +" Maintainer: Doug Kearns <dougkearns@gmail.com> +" Last Change: 2022 Jan 21 + +" Prelude {{{1 +if exists("b:current_syntax") + finish +endif + +let s:cpo_save = &cpo +set cpo&vim + +" syn iskeyword set after sourcing of basic.vim + +syn case ignore + +let s:prefix = search('\c^\s*$NOPREFIX\>', 'n') ? '_\=' : '_' + +" Statements {{{1 + +let s:statements =<< trim EOL " {{{2 + acceptfiledrop + allowfullscreen + assert + console + consolecursor + consolefont + consoletitle + continue + copypalette + define + delay + depthbuffer + displayorder + dontblend + echo + exit\s\+\%(select\|case\) + finishdrop + freefont + freeimage + icon + keyclear + limit + maptriangle + memcopy + memfill + memfree + memput + mousehide + mousemove + mouseshow + printimage + printstring + putimage + screenclick + screenhide + screenmove + screenprint + screenshow + setalpha + sndbal + sndclose + sndlimit + sndloop + sndpause + sndplay + sndplaycopy + sndplayfile + sndraw + sndrawdone + sndsetpos + sndstop + sndvol + title +EOL +" }}} + +for s in s:statements + exe 'syn match qb64Statement "\<' .. s:prefix .. s .. '\>" contained contains=qb64Underscore' +endfor + +" Functions {{{1 + +let s:functions =<< trim EOL " {{{2 + acos + acosh + alpha + alpha32 + arccot + arccsc + arcsec + asin + asinh + atan2 + atanh + axis + backgroundcolor + blue + blue32 + button + buttonchange + ceil + cinp + commandcount + connected + connectionaddress + connectionaddress$ + consoleinput + copyimage + cot + coth + cosh + csc + csch + cv + cwd$ + d2g + d2r + defaultcolor + deflate$ + desktopheight + desktopwidth + device$ + deviceinput + devices + dir$ + direxists + droppedfile + droppedfile$ + errorline + errormessage$ + exit + fileexists + fontheight + fontwidth + freetimer + g2d + g2r + green + green32 + height + hypot + inclerrorfile$ + inclerrorline + inflate$ + instrrev + keyhit + keydown + lastaxis + lastbutton + lastwheel + loadfont + loadimage + mem + memelement + memexists + memimage + memnew + memsound + mk$ + mousebutton + mouseinput + mousemovementx + mousemovementy + mousepipeopen + mousewheel + mousex + mousey + newimage + offset + openclient + os$ + pi + pixelsize + printwidth + r2d + r2g + red + red32 + readbit + resetbit + resizeheight + resizewidth + rgb + rgb32 + rgba + rgba32 + round + sec + sech + screenexists + screenimage + screenx + screeny + setbit + shellhide + shl + shr + sinh + sndcopy + sndgetpos + sndlen + sndopen + sndopenraw + sndpaused + sndplaying + sndrate + sndrawlen + startdir$ + strcmp + stricmp + tanh + title$ + togglebit + totaldroppedfiles + trim$ + wheel + width + windowhandle + windowhasfocus +EOL +" }}} + +for f in s:functions + exe 'syn match qb64Function "\<' .. s:prefix .. f .. '\>" contains=qb64Underscore' +endfor + +" Functions and statements (same name) {{{1 + +let s:common =<< trim EOL " {{{2 + autodisplay + blend + blink + capslock + clearcolor + clipboard$ + clipboardimage + controlchr + dest + display + font + fullscreen + mapunicode + memget + numlock + palettecolor + printmode + resize + screenicon + scrolllock + source +EOL +" }}} + +for c in s:common + exe 'syn match qb64Statement "\<' .. s:prefix .. c .. '\>" contains=qb64Underscore contained' + exe 'syn match qb64Function "\<' .. s:prefix .. c .. '\>" contains=qb64Underscore' +endfor + +" Keywords {{{1 + +" Non-prefixed keywords {{{2 +" TIMER FREE +" _DEPTH_BUFFER LOCK +syn keyword qb64Keyword free lock + +let s:keywords =<< trim EOL " {{{2 + all + anticlockwise + behind + clear + clip + console + dontwait + explicit + explicitarray + fillbackground + hardware + hardware1 + hide + keepbackground + middle + none + off + only + onlybackground + ontop + openconnection + openhost + preserve + seamless + smooth + smoothshrunk + smoothstretched + software + squarepixels + stretch + toggle +EOL +" }}} + +for k in s:keywords + exe 'syn match qb64Keyword "\<' .. s:prefix .. k .. '\>" contains=qb64Underscore' +endfor + +syn match qb64Underscore "\<_" contained conceal transparent + +" Source QuickBASIC syntax {{{1 +runtime! syntax/basic.vim + +" add after the BASIC syntax file is sourced so cluster already exists +syn cluster basicStatements add=qb64Statement,qb64Metacommand,qb64IfMetacommand +syn cluster basicLineIdentifier add=qb64LineLabel +syn cluster qb64NotTop contains=@basicNotTop,qb64Metavariable + +syn iskeyword @,48-57,.,_,!,#,$,%,&,` + +" Unsupported QuickBASIC features {{{1 +" TODO: add linux only missing features +syn keyword qb64Unsupported alias any byval calls cdecl erdev erdev$ fileattr +syn keyword qb64Unsupported fre ioctl ioctl$ pen play setmem signal uevent +syn keyword qb64Unsupported tron troff +syn match qb64Unsupported "\<declare\%(\s\+\%(sub\|function\)\>\)\@=" +syn match qb64Unsupported "\<\%(date\|time\)$\ze\s*=" " statements only +syn match qb64Unsupported "\<def\zs\s\+FN" +syn match qb64Unsupported "\<\%(exit\|end\)\s\+def\>" +syn match qb64Unsupported "\<width\s\+lprint\>" + +" Types {{{1 +syn keyword qb64Type _BIT _BYTE _FLOAT _INTEGER64 _MEM _OFFSET _UNSIGNED + +" Type suffixes {{{1 +if exists("basic_type_suffixes") + " TODO: handle leading word boundary and __+ prefix + syn match qb64TypeSuffix "\%(\a[[:alnum:]._]*\)\@<=\~\=`\%(\d\+\)\=" + syn match qb64TypeSuffix "\%(\a[[:alnum:]._]*\)\@<=\~\=\%(%\|%%\|&\|&&\|%&\)" + syn match qb64TypeSuffix "\%(\a[[:alnum:]._]*\)\@<=\%(!\|##\|#\)" + syn match qb64TypeSuffix "\%(\a[[:alnum:]._]*\)\@<=$\%(\d\+\)\=" +endif + +" Numbers {{{1 + +" Integers +syn match qb64Number "-\=&b[01]\+&\>\=" + +syn match qb64Number "-\=\<[01]\~\=`\>" +syn match qb64Number "-\=\<\d\+`\d\+\>" + +syn match qb64Number "-\=\<\d\+\%(%%\|&&\|%&\)\>" +syn match qb64Number "\<\d\+\~\%(%%\|&&\|%&\)\>" + +syn match qb64Number "-\=\<&b[01]\+\%(%%\|&&\|%&\)\>" +syn match qb64Number "\<&b[01]\+\~\%(%%\|&&\|%&\)\>" + +syn match qb64Number "-\=\<&o\=\o\+\%(%%\|&&\|%&\)\>" +syn match qb64Number "\<&o\=\o\+\~\%(%%\|&&\|%&\)\>" + +syn match qb64Number "-\=\<&h\x\+\%(%%\|&&\|%&\)\>" +syn match qb64Number "\<&h\x\+\~\%(%%\|&&\|%&\)\>" + +" Floats +syn match qb64Float "-\=\<\d\+\.\=\d*##\>" +syn match qb64Float "-\=\<\.\d\+##\>" + +" Line numbers and labels {{{1 +syn match qb64LineLabel "\%(_\{2,}\)\=\a[[:alnum:]._]*[[:alnum:]]\ze\s*:" nextgroup=@basicStatements skipwhite contained + +" Metacommands {{{1 +syn match qb64Metacommand contained "$NOPREFIX\>" +syn match qb64Metacommand contained "$ASSERTS\%(:CONSOLE\)\=\>" +syn match qb64Metacommand contained "$CHECKING:\%(ON\|OFF\)\>" +syn match qb64Metacommand contained "$COLOR:\%(0\|32\)\>" +syn match qb64Metacommand contained "$CONSOLE\%(:ONLY\)\=\>" +syn match qb64Metacommand contained "$EXEICON\s*:\s*'[^']\+'" +syn match qb64Metacommand contained "$ERROR\>" +syn match qb64Metacommand contained "$LET\>" +syn match qb64Metacommand contained "$RESIZE:\%(ON\|OFF\|STRETCH\|SMOOTH\)\>" +syn match qb64Metacommand contained "$SCREEN\%(HIDE\|SHOW\)\>" +syn match qb64Metacommand contained "$VERSIONINFO\s*:.*" +syn match qb64Metacommand contained "$VIRTUALKEYBOARD:\%(ON\|OFF\)\>" + +syn region qb64IfMetacommand contained matchgroup=qb64Metacommand start="$\%(IF\|ELSEIF\)\>" end="\<THEN\>" oneline transparent contains=qb64Metavariable +syn match qb64Metacommand contained "$\%(ELSE\|END\s*IF\)\>" + +syn keyword qb64Metavariable contained defined undefined +syn keyword qb64Metavariable contained windows win linux mac maxosx +syn keyword qb64Metavariable contained 32bit 64bit version + +" Default Highlighting {{{1 +hi def link qb64Float basicFloat +hi def link qb64Function Function +hi def link qb64Keyword Keyword +hi def link qb64LineLabel basicLineLabel +hi def link qb64Metacommand PreProc +hi def link qb64Metavariable Identifier +hi def link qb64Number basicNumber +hi def link qb64Statement Statement +hi def link qb64TypeSuffix basicTypeSuffix +hi def link qb64Type Type +hi def link qb64Unsupported Error + +" Postscript {{{1 +let b:current_syntax = "qb64" + +let &cpo = s:cpo_save +unlet s:cpo_save + +" vim: nowrap sw=2 sts=2 ts=8 noet fdm=marker: diff --git a/runtime/syntax/rc.vim b/runtime/syntax/rc.vim index 4c6856bc83..d69edd00fd 100644 --- a/runtime/syntax/rc.vim +++ b/runtime/syntax/rc.vim @@ -1,7 +1,7 @@ " Vim syntax file " Language: M$ Resource files (*.rc) " Maintainer: Christian Brabandt -" Last Change: 2015-05-29 +" Last Change: 20220116 " Repository: https://github.com/chrisbra/vim-rc-syntax " License: Vim (see :h license) " Previous Maintainer: Heiko Erhardt <Heiko.Erhardt@munich.netsurf.de> @@ -173,16 +173,17 @@ hi def link rcAttribute rcCommonAttribute hi def link rcStdId rcStatement hi def link rcStatement Statement -" Default color overrides -hi def rcLanguage term=reverse ctermbg=Red ctermfg=Yellow guibg=Red guifg=Yellow -hi def rcMainObject term=underline ctermfg=Blue guifg=Blue -hi def rcSubObject ctermfg=Green guifg=Green -hi def rcCaptionParam term=underline ctermfg=DarkGreen guifg=Green -hi def rcParam ctermfg=DarkGreen guifg=DarkGreen -hi def rcStatement ctermfg=DarkGreen guifg=DarkGreen -hi def rcCommonAttribute ctermfg=Brown guifg=Brown +hi def link rcLanguage Constant +hi def link rcCaptionParam Constant +hi def link rcCommonAttribute Constant + +hi def link rcMainObject Identifier +hi def link rcSubObject Define +hi def link rcParam Constant +hi def link rcStatement Statement +" +"hi def link rcIdentifier Identifier -"hi def link rcIdentifier Identifier let b:current_syntax = "rc" diff --git a/runtime/syntax/scala.vim b/runtime/syntax/scala.vim index 16e114778d..c08e60e55a 100644 --- a/runtime/syntax/scala.vim +++ b/runtime/syntax/scala.vim @@ -3,7 +3,7 @@ " Maintainer: Derek Wyatt " URL: https://github.com/derekwyatt/vim-scala " License: Same as Vim -" Last Change: 23 August 2021 +" Last Change: 23 January 2022 " ---------------------------------------------------------------------------- if !exists('main_syntax') @@ -43,55 +43,55 @@ syn keyword scalaKeyword class trait object extends with nextgroup=scalaInstance syn keyword scalaKeyword case nextgroup=scalaKeyword,scalaCaseFollowing skipwhite syn keyword scalaKeyword val nextgroup=scalaNameDefinition,scalaQuasiQuotes skipwhite syn keyword scalaKeyword def var nextgroup=scalaNameDefinition skipwhite -hi link scalaKeyword Keyword +hi def link scalaKeyword Keyword exe 'syn region scalaBlock start=/{/ end=/}/ contains=' . s:ContainedGroup() . ' fold' syn keyword scalaAkkaSpecialWord when goto using startWith initialize onTransition stay become unbecome -hi link scalaAkkaSpecialWord PreProc +hi def link scalaAkkaSpecialWord PreProc syn keyword scalatestSpecialWord shouldBe syn match scalatestShouldDSLA /^\s\+\zsit should/ syn match scalatestShouldDSLB /\<should\>/ -hi link scalatestSpecialWord PreProc -hi link scalatestShouldDSLA PreProc -hi link scalatestShouldDSLB PreProc +hi def link scalatestSpecialWord PreProc +hi def link scalatestShouldDSLA PreProc +hi def link scalatestShouldDSLB PreProc syn match scalaSymbol /'[_A-Za-z0-9$]\+/ -hi link scalaSymbol Number +hi def link scalaSymbol Number syn match scalaChar /'.'/ syn match scalaChar /'\\[\\"'ntbrf]'/ contains=scalaEscapedChar syn match scalaChar /'\\u[A-Fa-f0-9]\{4}'/ contains=scalaUnicodeChar syn match scalaEscapedChar /\\[\\"'ntbrf]/ syn match scalaUnicodeChar /\\u[A-Fa-f0-9]\{4}/ -hi link scalaChar Character -hi link scalaEscapedChar Special -hi link scalaUnicodeChar Special +hi def link scalaChar Character +hi def link scalaEscapedChar Special +hi def link scalaUnicodeChar Special syn match scalaOperator "||" syn match scalaOperator "&&" syn match scalaOperator "|" syn match scalaOperator "&" -hi link scalaOperator Special +hi def link scalaOperator Special syn match scalaNameDefinition /\<[_A-Za-z0-9$]\+\>/ contained nextgroup=scalaPostNameDefinition,scalaVariableDeclarationList syn match scalaNameDefinition /`[^`]\+`/ contained nextgroup=scalaPostNameDefinition syn match scalaVariableDeclarationList /\s*,\s*/ contained nextgroup=scalaNameDefinition syn match scalaPostNameDefinition /\_s*:\_s*/ contained nextgroup=scalaTypeDeclaration -hi link scalaNameDefinition Function +hi def link scalaNameDefinition Function syn match scalaInstanceDeclaration /\<[_\.A-Za-z0-9$]\+\>/ contained nextgroup=scalaInstanceHash syn match scalaInstanceDeclaration /`[^`]\+`/ contained syn match scalaInstanceHash /#/ contained nextgroup=scalaInstanceDeclaration -hi link scalaInstanceDeclaration Special -hi link scalaInstanceHash Type +hi def link scalaInstanceDeclaration Special +hi def link scalaInstanceHash Type syn match scalaUnimplemented /???/ -hi link scalaUnimplemented ERROR +hi def link scalaUnimplemented ERROR syn match scalaCapitalWord /\<[A-Z][A-Za-z0-9$]*\>/ -hi link scalaCapitalWord Special +hi def link scalaCapitalWord Special " Handle type declarations specially syn region scalaTypeStatement matchgroup=Keyword start=/\<type\_s\+\ze/ end=/$/ contains=scalaTypeTypeDeclaration,scalaSquareBrackets,scalaTypeTypeEquals,scalaTypeStatement @@ -105,18 +105,18 @@ syn match scalaTypeTypeEquals /=\ze[^>]/ contained nextgroup=scalaTypeTypePostDe syn match scalaTypeTypeExtension /)\?\_s*\zs\%(⇒\|=>\|<:\|:>\|=:=\|::\|#\)/ contained contains=scalaTypeOperator nextgroup=scalaTypeTypeDeclaration skipwhite syn match scalaTypeTypePostDeclaration /\<[_\.A-Za-z0-9$]\+\>/ contained nextgroup=scalaTypeTypePostExtension skipwhite syn match scalaTypeTypePostExtension /\%(⇒\|=>\|<:\|:>\|=:=\|::\)/ contained contains=scalaTypeOperator nextgroup=scalaTypeTypePostDeclaration skipwhite -hi link scalaTypeTypeDeclaration Type -hi link scalaTypeTypeExtension Keyword -hi link scalaTypeTypePostDeclaration Special -hi link scalaTypeTypePostExtension Keyword +hi def link scalaTypeTypeDeclaration Type +hi def link scalaTypeTypeExtension Keyword +hi def link scalaTypeTypePostDeclaration Special +hi def link scalaTypeTypePostExtension Keyword syn match scalaTypeDeclaration /(/ contained nextgroup=scalaTypeExtension contains=scalaRoundBrackets skipwhite syn match scalaTypeDeclaration /\%(⇒\|=>\)\ze/ contained nextgroup=scalaTypeDeclaration contains=scalaTypeExtension skipwhite syn match scalaTypeDeclaration /\<[_\.A-Za-z0-9$]\+\>/ contained nextgroup=scalaTypeExtension skipwhite syn match scalaTypeExtension /)\?\_s*\zs\%(⇒\|=>\|<:\|:>\|=:=\|::\|#\)/ contained contains=scalaTypeOperator nextgroup=scalaTypeDeclaration skipwhite -hi link scalaTypeDeclaration Type -hi link scalaTypeExtension Keyword -hi link scalaTypePostExtension Keyword +hi def link scalaTypeDeclaration Type +hi def link scalaTypeExtension Keyword +hi def link scalaTypePostExtension Keyword syn match scalaTypeAnnotation /\%([_a-zA-Z0-9$\s]:\_s*\)\ze[_=(\.A-Za-z0-9$]\+/ skipwhite nextgroup=scalaTypeDeclaration contains=scalaRoundBrackets syn match scalaTypeAnnotation /)\_s*:\_s*\ze[_=(\.A-Za-z0-9$]\+/ skipwhite nextgroup=scalaTypeDeclaration @@ -124,51 +124,51 @@ hi clear scalaTypeAnnotation syn match scalaCaseFollowing /\<[_\.A-Za-z0-9$]\+\>/ contained contains=scalaCapitalWord syn match scalaCaseFollowing /`[^`]\+`/ contained contains=scalaCapitalWord -hi link scalaCaseFollowing Special +hi def link scalaCaseFollowing Special syn keyword scalaKeywordModifier abstract override final lazy implicit private protected sealed null super syn keyword scalaSpecialFunction implicitly require -hi link scalaKeywordModifier Function -hi link scalaSpecialFunction Function +hi def link scalaKeywordModifier Function +hi def link scalaSpecialFunction Function syn keyword scalaSpecial this true false ne eq syn keyword scalaSpecial new nextgroup=scalaInstanceDeclaration skipwhite syn match scalaSpecial "\%(=>\|⇒\|<-\|←\|->\|→\)" syn match scalaSpecial /`[^`]\+`/ " Backtick literals -hi link scalaSpecial PreProc +hi def link scalaSpecial PreProc syn keyword scalaExternal package import -hi link scalaExternal Include +hi def link scalaExternal Include syn match scalaStringEmbeddedQuote /\\"/ contained syn region scalaString start=/"/ end=/"/ contains=scalaStringEmbeddedQuote,scalaEscapedChar,scalaUnicodeChar -hi link scalaString String -hi link scalaStringEmbeddedQuote String +hi def link scalaString String +hi def link scalaStringEmbeddedQuote String syn region scalaIString matchgroup=scalaInterpolationBrackets start=/\<[a-zA-Z][a-zA-Z0-9_]*"/ skip=/\\"/ end=/"/ contains=scalaInterpolation,scalaInterpolationB,scalaEscapedChar,scalaUnicodeChar syn region scalaTripleIString matchgroup=scalaInterpolationBrackets start=/\<[a-zA-Z][a-zA-Z0-9_]*"""/ end=/"""\ze\%([^"]\|$\)/ contains=scalaInterpolation,scalaInterpolationB,scalaEscapedChar,scalaUnicodeChar -hi link scalaIString String -hi link scalaTripleIString String +hi def link scalaIString String +hi def link scalaTripleIString String syn match scalaInterpolation /\$[a-zA-Z0-9_$]\+/ contained exe 'syn region scalaInterpolationB matchgroup=scalaInterpolationBoundary start=/\${/ end=/}/ contained contains=' . s:ContainedGroup() -hi link scalaInterpolation Function +hi def link scalaInterpolation Function hi clear scalaInterpolationB syn region scalaFString matchgroup=scalaInterpolationBrackets start=/f"/ skip=/\\"/ end=/"/ contains=scalaFInterpolation,scalaFInterpolationB,scalaEscapedChar,scalaUnicodeChar syn match scalaFInterpolation /\$[a-zA-Z0-9_$]\+\(%[-A-Za-z0-9\.]\+\)\?/ contained exe 'syn region scalaFInterpolationB matchgroup=scalaInterpolationBoundary start=/${/ end=/}\(%[-A-Za-z0-9\.]\+\)\?/ contained contains=' . s:ContainedGroup() -hi link scalaFString String -hi link scalaFInterpolation Function +hi def link scalaFString String +hi def link scalaFInterpolation Function hi clear scalaFInterpolationB syn region scalaTripleString start=/"""/ end=/"""\%([^"]\|$\)/ contains=scalaEscapedChar,scalaUnicodeChar syn region scalaTripleFString matchgroup=scalaInterpolationBrackets start=/f"""/ end=/"""\%([^"]\|$\)/ contains=scalaFInterpolation,scalaFInterpolationB,scalaEscapedChar,scalaUnicodeChar -hi link scalaTripleString String -hi link scalaTripleFString String +hi def link scalaTripleString String +hi def link scalaTripleFString String -hi link scalaInterpolationBrackets Special -hi link scalaInterpolationBoundary Function +hi def link scalaInterpolationBrackets Special +hi def link scalaInterpolationBoundary Function syn match scalaNumber /\<0[dDfFlL]\?\>/ " Just a bare 0 syn match scalaNumber /\<[1-9]\d*[dDfFlL]\?\>/ " A multi-digit number - octal numbers with leading 0's are deprecated in Scala @@ -176,16 +176,16 @@ syn match scalaNumber /\<0[xX][0-9a-fA-F]\+[dDfFlL]\?\>/ " Hex number syn match scalaNumber /\%(\<\d\+\.\d*\|\.\d\+\)\%([eE][-+]\=\d\+\)\=[fFdD]\=/ " exponential notation 1 syn match scalaNumber /\<\d\+[eE][-+]\=\d\+[fFdD]\=\>/ " exponential notation 2 syn match scalaNumber /\<\d\+\%([eE][-+]\=\d\+\)\=[fFdD]\>/ " exponential notation 3 -hi link scalaNumber Number +hi def link scalaNumber Number syn region scalaRoundBrackets start="(" end=")" skipwhite contained contains=scalaTypeDeclaration,scalaSquareBrackets,scalaRoundBrackets syn region scalaSquareBrackets matchgroup=scalaSquareBracketsBrackets start="\[" end="\]" skipwhite nextgroup=scalaTypeExtension contains=scalaTypeDeclaration,scalaSquareBrackets,scalaTypeOperator,scalaTypeAnnotationParameter syn match scalaTypeOperator /[-+=:<>]\+/ contained syn match scalaTypeAnnotationParameter /@\<[`_A-Za-z0-9$]\+\>/ contained -hi link scalaSquareBracketsBrackets Type -hi link scalaTypeOperator Keyword -hi link scalaTypeAnnotationParameter Function +hi def link scalaSquareBracketsBrackets Type +hi def link scalaTypeOperator Keyword +hi def link scalaTypeAnnotationParameter Function syn match scalaShebang "\%^#!.*" display syn region scalaMultilineComment start="/\*" end="\*/" contains=scalaMultilineComment,scalaDocLinks,scalaParameterAnnotation,scalaCommentAnnotation,scalaTodo,scalaCommentCodeBlock,@Spell keepend fold @@ -195,20 +195,20 @@ syn match scalaParamAnnotationValue /[.`_A-Za-z0-9$]\+/ contained syn region scalaDocLinks start="\[\[" end="\]\]" contained syn region scalaCommentCodeBlock matchgroup=Keyword start="{{{" end="}}}" contained syn match scalaTodo "\vTODO|FIXME|XXX" contained -hi link scalaShebang Comment -hi link scalaMultilineComment Comment -hi link scalaDocLinks Function -hi link scalaParameterAnnotation Function -hi link scalaParamAnnotationValue Keyword -hi link scalaCommentAnnotation Function -hi link scalaCommentCodeBlock String -hi link scalaTodo Todo +hi def link scalaShebang Comment +hi def link scalaMultilineComment Comment +hi def link scalaDocLinks Function +hi def link scalaParameterAnnotation Function +hi def link scalaParamAnnotationValue Keyword +hi def link scalaCommentAnnotation Function +hi def link scalaCommentCodeBlock String +hi def link scalaTodo Todo syn match scalaAnnotation /@\<[`_A-Za-z0-9$]\+\>/ -hi link scalaAnnotation PreProc +hi def link scalaAnnotation PreProc syn match scalaTrailingComment "//.*$" contains=scalaTodo,@Spell -hi link scalaTrailingComment Comment +hi def link scalaTrailingComment Comment syn match scalaAkkaFSM /goto([^)]*)\_s\+\<using\>/ contains=scalaAkkaFSMGotoUsing syn match scalaAkkaFSM /stay\_s\+using/ @@ -221,8 +221,8 @@ syn match scalaAkkaFSM /onTermination/ syn match scalaAkkaFSM /whenUnhandled/ syn match scalaAkkaFSMGotoUsing /\<using\>/ syn match scalaAkkaFSMGotoUsing /\<goto\>/ -hi link scalaAkkaFSM PreProc -hi link scalaAkkaFSMGotoUsing PreProc +hi def link scalaAkkaFSM PreProc +hi def link scalaAkkaFSMGotoUsing PreProc let b:current_syntax = 'scala' diff --git a/runtime/syntax/strace.vim b/runtime/syntax/strace.vim index 206c58919e..20516a1853 100644 --- a/runtime/syntax/strace.vim +++ b/runtime/syntax/strace.vim @@ -1,8 +1,7 @@ " Vim syntax file -" This is a GENERATED FILE. Please always refer to source file at the URI below. " Language: strace output " Maintainer: David Necas (Yeti) <yeti@physics.muni.cz> -" Last Change: 2015-01-16 +" Last Change: 2022 Jan 29 " Setup " quit when a syntax file was already loaded 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/runtime/tutor/en/vim-01-beginner.tutor b/runtime/tutor/en/vim-01-beginner.tutor index 7c0c357e80..e256711e70 100644 --- a/runtime/tutor/en/vim-01-beginner.tutor +++ b/runtime/tutor/en/vim-01-beginner.tutor @@ -1,15 +1,14 @@ -# Welcome to the VIM Tutor +# Welcome to the Neovim Tutorial -Vim is a very powerful editor that has many commands, too many to explain in -a tutor such as this. This tutor is designed to describe enough of the -commands that you will be able to easily use Vim as an all-purpose editor. -It is IMPORTANT to remember that this tutor is set up to teach by use. That +Neovim is a very powerful editor that has many commands, too many to explain in +a tutorial such as this. This tutorial is designed to describe enough of the +commands that you will be able to easily use Neovim as an all-purpose editor. +It is IMPORTANT to remember that this tutorial is set up to teach by use. That means that you need to do the exercises to learn them properly. If you only read the text, you will soon forget what is most important! -For now, make sure that your Shift-Lock key is NOT depressed and press the -`j`{normal} key enough times to move the cursor so that Lesson 0 completely -fills the screen. +For now, make sure that your Caps-Lock is off and press the `j`{normal} key enough +times to move the cursor so that Lesson 0 completely fills the screen. # Lesson 0 @@ -20,12 +19,13 @@ pressing [<Esc>](<Esc>) and then [u](u) will undo the latest change. This tutorial is interactive, and there are a few things you should know. - Type [<Enter>](<Enter>) on links [like this](holy-grail ) to open the linked help section. - Or simply type [K](K) on any word to find its documentation! +- You can close this help window with `:q`{vim} - Sometimes you will be required to modify text like this here Once you have done the changes correctly, the ✗ sign at the left will change -to ✓. I imagine you can already see how neat Vim can be. ;) +to ✓. I imagine you can already see how neat Neovim can be. Other times, you'll be prompted to run a command (I'll explain this later): ~~~ cmd :help <Enter> @@ -43,9 +43,9 @@ Now, move to the next lesson (use the `j`{normal} key to scroll down). ** To move the cursor, press the `h`, `j`, `k`, `l` keys as indicated. ** - ↑ - k Hint: The `h`{normal} key is at the left and moves left. - ← h l → The `l`{normal} key is at the right and moves right. + ↑ + k Hint: The `h`{normal} key is at the left and moves left. + ← h l → The `l`{normal} key is at the right and moves right. j The `j`{normal} key looks like a down arrow. ↓ @@ -60,11 +60,11 @@ NOTE: If you are ever unsure about something you typed, press <Esc> to place you in Normal mode. Then retype the command you wanted. NOTE: The cursor keys should also work. But using hjkl you will be able to - move around much faster, once you get used to it. Really! + move around much faster, once you get used to it. -# Lesson 1.2: EXITING VIM +# Lesson 1.2: EXITING NEOVIM -!! NOTE: Before executing any of the steps below, read this entire lesson !! +!! NOTE: Before executing any of the steps below, read the entire lesson !! 1. Press the <Esc> key (to make sure you are in Normal mode). @@ -72,10 +72,10 @@ NOTE: The cursor keys should also work. But using hjkl you will be able to `:q!`{vim} `<Enter>`{normal}. - This exits the editor, DISCARDING any changes you have made. + This quits the editor, DISCARDING any changes you have made. - 3. Open vim and get back here by executing the command that got you into - this tutor. That might be: + 3. Open Neovim and get back here by executing the command that got you into + this tutorial. That might be: :Tutor <Enter> @@ -104,9 +104,9 @@ The ccow jumpedd ovverr thhe mooon. 5. Now that the line is correct, go on to Lesson 1.4. -NOTE: As you go through this tutor, do not try to memorize everything, - your Vim vocabulary will expand with usage. Consider returning to - this tutor periodically for a refresher. +NOTE: As you go through this tutorial, do not try to memorize everything, + your Neovim vocabulary will expand with usage. Consider returning to + this tutorial periodically for a refresher. # Lesson 1.4: TEXT EDITING: INSERTION @@ -151,11 +151,11 @@ There is also some text missing here. # Lesson 1.6: EDITING A FILE -** Use `:wq`{vim} to save a file and exit. ** +** Use `:wq`{vim} to write a file and quit. ** -!! NOTE: Before executing any of the steps below, read this entire lesson !! +!! NOTE: Before executing any of the steps below, read the entire lesson !! - 1. Exit this tutor as you did in Lesson 1.2: `:q!`{vim} + 1. Exit this tutorial as you did in Lesson 1.2: `:q!`{vim} Or, if you have access to another terminal, do the following there. 2. At the shell prompt type this command: @@ -167,28 +167,28 @@ There is also some text missing here. 3. Insert and delete text as you learned in the previous lessons. - 4. Save the file with changes and exit Vim with: + 4. Save the file with changes and exit Neovim with: ~~~ cmd :wq ~~~ Note you'll need to press `<Enter>` to execute the command. - 5. If you have quit vimtutor in step 1 restart the vimtutor and move down + 5. If you have quit this tutorial in step 1, restart and move down to the following summary. - 6. After reading the above steps and understanding them: do it. + 6. After reading and understanding the above steps: do them. # Lesson 1 SUMMARY 1. The cursor is moved using either the arrow keys or the hjkl keys. h (left) j (down) k (up) l (right) - 2. To start Vim from the shell prompt type: + 2. To start Neovim from the shell prompt type: ~~~ sh $ nvim FILENAME ~~~ - 3. To exit Vim type: `<Esc>`{normal} `:q!`{vim} `<Enter>`{normal} to trash all changes. - OR type: `<Esc>`{normal} `:wq`{vim} `<Enter>`{normal} to save the changes. + 3. To exit Neovim type: `<Esc>`{normal} `:q!`{vim} `<Enter>`{normal} to trash all changes. + OR type: `<Esc>`{normal} `:wq`{vim} `<Enter>`{normal} to save the changes. 4. To delete the character at the cursor type: `x`{normal} @@ -297,8 +297,11 @@ Due to the frequency of whole line deletion, the designers of Vi decided it would be easier to simply type two d's to delete a line. 1. Move the cursor to the second line in the phrase below. + 2. Type [dd](dd) to delete the line. + 3. Now move to the fourth line. + 4. Type `2dd`{normal} to delete two lines. 1) Roses are red, @@ -314,11 +317,17 @@ it would be easier to simply type two d's to delete a line. ** Press `u`{normal} to undo the last commands, `U`{normal} to fix a whole line. ** 1. Move the cursor to the line below marked ✗ and place it on the first error. + 2. Type `x`{normal} to delete the first unwanted character. + 3. Now type `u`{normal} to undo the last command executed. + 4. This time fix all the errors on the line using the `x`{normal} command. + 5. Now type a capital `U`{normal} to return the line to its original state. + 6. Now type `u`{normal} a few times to undo the `U`{normal} and preceding commands. + 7. Now type `<C-r>`{normal} (Control + R) a few times to redo the commands. Fiix the errors oon thhis line and reeplace them witth undo. @@ -328,8 +337,11 @@ Fiix the errors oon thhis line and reeplace them witth undo. # Lesson 2 SUMMARY 1. To delete from the cursor up to the next word type: `dw`{normal} + 2. To delete from the cursor to the end of a line type: `d$`{normal} + 3. To delete a whole line type: `dd`{normal} + 4. To repeat a motion prepend it with a number: `2w`{normal} 5. The format for a change command is: @@ -356,7 +368,7 @@ Fiix the errors oon thhis line and reeplace them witth undo. 1. Move the cursor to the first ✓ line below. - 2. Type `dd`{normal} to delete the line and store it in a Vim register. + 2. Type `dd`{normal} to delete the line and store it in a Neovim register. 3. Move the cursor to the c) line, ABOVE where the deleted line should go. @@ -369,6 +381,8 @@ b) Violets are blue, c) Intelligence is learned, a) Roses are red, +NOTE: You can also put the text before the cursor with `P`{normal} (capital P) + # Lesson 3.2: THE REPLACE COMMAND ** Type `rx`{normal} to replace the character at the cursor with x. ** @@ -386,7 +400,7 @@ When this line was typed in, someone pressed some wrong keys! 5. Now move on to Lesson 3.3. -NOTE: Remember that you should be learning by doing, not memorization. +NOTE: Remember that you should be learning by doing, not memorizing. # Lesson 3.3: THE CHANGE OPERATOR @@ -439,7 +453,7 @@ NOTE: You can use the Backspace key to correct mistakes while typing. 3. The [change operator](c) allows you to change from the cursor to where the motion takes you. Type `ce`{normal} to change from the cursor to the - end of the word, `c$`{normal} to change to the end of a line. + end of the word, `c$`{normal} to change to the end of a line, etc. 4. The format for change is: @@ -452,7 +466,7 @@ Now go on to the next lesson. ** Type `<C-g>`{normal} to show your location in a file and the file status. Type `G`{normal} to move to a line in the file. ** -NOTE: Read this entire lesson before executing any of the steps!! +NOTE: Read the entire lesson before executing any of these steps!! 1. Hold down the `<Ctrl>`{normal} key and press `g`{normal}. We call this `<C-g>`{normal}. A message will appear at the bottom of the page with the filename and @@ -535,8 +549,8 @@ Usually thee best time to see thee flowers is in thee spring. ~~~ cmd :#,#s/old/new/g ~~~ - where #,# are the line numbers of the range of lines where the - substitution is to be done. + where # are the line numbers of the range of lines where the + substitution is to be done (i.e., `1,3` means from line 1 to line 3, inclusive). Type ~~~ cmd @@ -551,6 +565,9 @@ Usually thee best time to see thee flowers is in thee spring. to find every occurrence in the whole file, with a prompt whether to substitute or not. +NOTE: You can also select the lines you want to substitute first using visual-mode. + This will be explained more in a future lesson. + # Lesson 4 SUMMARY 1. `<C-g>`{normal} displays your location and the file status. @@ -603,11 +620,10 @@ Usually thee best time to see thee flowers is in thee spring. This will show you a listing of your directory, just as if you were at the shell prompt. -NOTE: It is possible to execute any external command this way, also with - arguments. +NOTE: It is possible to execute any external command this way, and you + can include arguments. -NOTE: All `:`{vim} commands must be finished by hitting `<Enter>`{normal}. - From here on we will not always mention it. +NOTE: All `:`{vim} commands are executed when you press `<Enter>`{normal}. # Lesson 5.2: MORE ON WRITING FILES @@ -624,11 +640,11 @@ NOTE: All `:`{vim} commands must be finished by hitting `<Enter>`{normal}. ~~~ (where TEST is the filename you chose.) - 4. This saves the whole file (the Vim Tutor) under the name TEST. + 4. This saves the current file under the name TEST. To verify this, type `:!ls`{vim} again to see your directory. -NOTE: If you were to exit Vim and start it again with `nvim TEST`, the file - would be an exact copy of the tutor when you saved it. +NOTE: If you were to exit Neovim and start it again with `nvim TEST`, the file + would be an exact copy of the tutorial when you saved it. 5. Now remove the file by typing: ~~~ cmd @@ -659,7 +675,7 @@ NOTE: If you were to exit Vim and start it again with `nvim TEST`, the file before you press `<Enter>`{normal}. - 5. Vim will write the selected lines to the file TEST. Use `:!ls`{vim} to see it. + 5. Neovim will write the selected lines to the file TEST. Use `:!ls`{vim} to see it. Do not remove it yet! We will use it in the next lesson. NOTE: Pressing [v](v) starts [Visual selection](visual-mode). You can move the cursor around to @@ -668,7 +684,7 @@ NOTE: Pressing [v](v) starts [Visual selection](visual-mode). You can move the c # Lesson 5.4: RETRIEVING AND MERGING FILES -** To insert the contents of a file, type `:r FILENAME`{vim}. ** +** To retrieve the contents of a file, type `:r FILENAME`{vim}. ** 1. Place the cursor just above this line. @@ -683,7 +699,7 @@ NOTE: After executing Step 2 you will see text from Lesson 5.3. Then move The file you retrieve is placed below the cursor line. 3. To verify that a file was retrieved, cursor back and notice that there - are now two copies of Lesson 5.3, the original and the file version. + are now two copies of Lesson 5.3, the original and the retrieved version. NOTE: You can also read the output of an external command. For example, @@ -699,7 +715,7 @@ NOTE: You can also read the output of an external command. For example, `:!ls`{vim} - shows a directory listing `:!rm FILENAME`{vim} - removes file FILENAME - 2. [:w](:w) FILENAME writes the current Vim file to disk with + 2. [:w](:w) FILENAME writes the current Neovim file to disk with name FILENAME. 3. [v](v) motion :w FILENAME saves the Visually selected lines in file @@ -768,11 +784,11 @@ Adding 123 to xxx gives you xxx. Adding 123 to 456 gives you 579. NOTE: Replace mode is like Insert mode, but every typed character - deletes an existing character. + replaces an existing character. # Lesson 6.4: COPY AND PASTE TEXT -** Use the `y`{normal} operator to copy text and `p`{normal} to paste it. ** +** Use the `y`{normal} operator to copy text and `p`{normal} to put it. ** 1. Go to the line marked with ✓ below and place the cursor after "a)". @@ -796,9 +812,13 @@ b) NOTE: you can use `y`{normal} as an operator: `yw`{normal} yanks one word. +NOTE: you can use `P`{normal} to put before the cursor, rather than after. + # Lesson 6.5: SET OPTION -** Set an option so a search or substitute ignores case. ** +** Set an option so search and substitute commands ignore case. ** + +There are many settings in Neovim that you can configure to suit your needs. 1. Search for 'ignore' by entering: `/ignore` Repeat several times by pressing `n`{normal}. @@ -820,7 +840,7 @@ NOTE: you can use `y`{normal} as an operator: `yw`{normal} yanks one word. ~~~ cmd :set noic ~~~ - 7. To toggle the value of a setting, prepend it with "inv": + 7. To invert the value of a setting, prepend it with "inv": ~~~ cmd :set invic ~~~ @@ -858,19 +878,18 @@ NOTE: If you want to ignore case for just one search command, use [\c](/\c) ~~~ cmd :set noic ~~~ - 8. Prepend "inv" to toggle an option: + 8. Prepend "inv" to invert an option: ~~~ cmd :set invic ~~~ # Lesson 7.1: GETTING HELP -** Use the on-line help system. ** +** Use the online help system. ** -Vim has a comprehensive on-line help system. +Neovim has a comprehensive online help system. To get started, try one of these three: - - press the `<HELP>`{normal} key (if you have one) - press the `<F1>`{normal} key (if you have one) - type `:help`{vim} @@ -879,7 +898,7 @@ Type `<C-w><C-w>`{normal} to jump from one window to another. Type `:q`{vim} to close the help window. You can find help on just about any subject, by giving an argument to the -":help" command. Try these (don't forget pressing <Enter>): +":help" command. Try these (don't forget to press <Enter>): ~~~ cmd :help w :help c_CTRL-D @@ -888,12 +907,12 @@ You can find help on just about any subject, by giving an argument to the ~~~ # Lesson 7.2: CREATE A STARTUP SCRIPT -** Enable Vim features. ** +** Enable Neovim features. ** -Vim has many more features than Vi, but most of them are disabled by -default. To start using more features you have to create a "vimrc" file. +Neovim is a very configurable editor. You can customise it any way you like. +To start using more features create an "init.vim" file. - 1. Start editing the "vimrc" file. + 1. Start editing the "init.vim" file. `:call mkdir(stdpath('config'),'p')`{vim} `:exe 'edit' stdpath('config').'/init.vim'`{vim} @@ -902,24 +921,24 @@ default. To start using more features you have to create a "vimrc" file. `:w`{vim} - You can add all your preferred settings to this "vimrc" file. - For more information type `:help vimrc-intro`{vim}. + You can add all your preferred settings to this "init.vim" file. + For more information type `:help init.vim`{vim}. # Lesson 7.3: COMPLETION ** Command line completion with `<C-d>`{normal} and `<Tab>`{normal}. ** - 1. Look what files exist in the directory: `:!ls`{vim} + 1. List the contents of the current directory: `:!ls`{vim} 2. Type the start of a command: `:e`{vim} - 3. Press `<C-d>`{normal} and Vim will show a list of commands beginning with "e". + 3. Press `<C-d>`{normal} and Neovim will show a list of commands beginning with "e". - 4. Press `<Tab>`{normal} and Vim will complete the command name to ":edit". + 4. Press `<Tab>`{normal} and Neovim will complete the command name to ":edit". 5. Now add a space and the start of an existing file name: `:edit FIL`{vim} - 6. Press `<Tab>`{normal}. Vim will complete the name ("FIL" -> "FILE", if it is unique). + 6. Press `<Tab>`{normal}. Neovim will complete the name ("FIL" -> "FILE", if it is unique). NOTE: Completion works for many commands. It is especially useful for `:help`{vim}. @@ -934,19 +953,18 @@ NOTE: Completion works for many commands. It is especially useful for `:help`{vi 4. Type `:q`{vim} to close the help window - 5. Create a vimrc startup script to keep your preferred settings. + 5. Create an init.vim startup script to keep your preferred settings. 6. While in command mode, press `<C-d>`{normal} to see possible completions. Press `<Tab>`{normal} to use one completion. # CONCLUSION -This was intended to give a brief overview of the Vim editor, just enough to -allow you to use the editor fairly easily. It is far from complete as Vim has +This was intended to give a brief overview of the Neovim editor, just enough to +allow you to use it fairly easily. It is far from complete as Neovim has many many more commands. Consult the help often. - -There are many resources online to learn more about vim. Here's a bunch of -them: +There are also countless great tutorials and videos to be found online. +Here's a bunch of them: - *Learn Vim Progressively*: http://yannesposito.com/Scratch/en/blog/Learn-Vim-Progressively/ @@ -964,7 +982,7 @@ them: https://github.com/mhinz/vim-galore If you prefer a book, *Practical Vim* by Drew Neil is recommended often -(the sequel, *Modern Vim*, includes material specific to nvim). +(the sequel, *Modern Vim*, includes material specific to Neovim). This tutorial was written by Michael C. Pierce and Robert K. Ware, Colorado School of Mines using ideas supplied by Charles Smith, Colorado State @@ -972,5 +990,6 @@ University. E-mail: bware@mines.colorado.edu. Modified for Vim by Bram Moolenaar. Modified for vim-tutor-mode by Felipe Morales. +Modified for Neovim by Rory Nesbitt. // vim: nowrap diff --git a/runtime/tutor/en/vim-01-beginner.tutor.json b/runtime/tutor/en/vim-01-beginner.tutor.json index e71ead976d..5dccb824e0 100644 --- a/runtime/tutor/en/vim-01-beginner.tutor.json +++ b/runtime/tutor/en/vim-01-beginner.tutor.json @@ -12,34 +12,34 @@ "232": "Somebody typed the end of this line twice.", "271": -1, "290": "This line of words is cleaned up.", - "304": -1, - "305": -1, - "306": -1, "307": -1, "308": -1, "309": -1, "310": -1, - "324": "Fix the errors on this line and replace them with undo.", - "367": -1, - "368": -1, - "369": -1, - "370": -1, - "384": "When this line was typed in, someone pressed some wrong keys!", - "385": "When this line was typed in, someone pressed some wrong keys!", - "405": "This line has a few words that need changing using the change operator.", - "406": "This line has a few words that need changing using the change operator.", - "426": "The end of this line needs to be corrected using the `c$` command.", - "427": "The end of this line needs to be corrected using the `c$` command.", - "490": -1, - "509": -1, - "532": "Usually the best time to see the flowers is in the spring.", - "725": -1, - "730": -1, - "746": "This line will allow you to practice appending text to a line.", - "747": "This line will allow you to practice appending text to a line.", - "767": "Adding 123 to 456 gives you 579.", - "768": "Adding 123 to 456 gives you 579.", - "794": "a) This is the first item.", - "795": "b) This is the second item." + "311": -1, + "312": -1, + "313": -1, + "333": "Fix the errors on this line and replace them with undo.", + "379": -1, + "380": -1, + "381": -1, + "382": -1, + "398": "When this line was typed in, someone pressed some wrong keys!", + "399": "When this line was typed in, someone pressed some wrong keys!", + "419": "This line has a few words that need changing using the change operator.", + "420": "This line has a few words that need changing using the change operator.", + "440": "The end of this line needs to be corrected using the `c$` command.", + "441": "The end of this line needs to be corrected using the `c$` command.", + "504": -1, + "523": -1, + "546": "Usually the best time to see the flowers is in the spring.", + "741": -1, + "746": -1, + "762": "This line will allow you to practice appending text to a line.", + "763": "This line will allow you to practice appending text to a line.", + "783": "Adding 123 to 456 gives you 579.", + "784": "Adding 123 to 456 gives you 579.", + "810": "a) This is the first item.", + "811": "b) This is the second item." } } diff --git a/scripts/gen_filetype.lua b/scripts/gen_filetype.lua new file mode 100644 index 0000000000..42478a1082 --- /dev/null +++ b/scripts/gen_filetype.lua @@ -0,0 +1,201 @@ +local do_not_run = true +if do_not_run then + print([[ + This script was used to bootstrap the filetype patterns in runtime/lua/vim/filetype.lua. It + should no longer be used except for testing purposes. New filetypes, or changes to existing + filetypes, should be ported manually as part of the vim-patch process. + ]]) + return +end + +local filetype_vim = "runtime/filetype.vim" +local filetype_lua = "runtime/lua/vim/filetype.lua" + +local keywords = { + ["for"] = true, + ["or"] = true, + ["and"] = true, + ["end"] = true, + ["do"] = true, + ["if"] = true, + ["while"] = true, + ["repeat"] = true, +} + +local sections = { + extension = { str = {}, func = {} }, + filename = { str = {}, func = {} }, + pattern = { str = {}, func = {} }, +} + +local specialchars = "%*%?\\%$%[%]%{%}" + +local function add_pattern(pat, ft) + local ok = true + + -- Patterns that start or end with { or } confuse splitting on commas and make parsing harder, so just skip those + if not string.find(pat, "^%{") and not string.find(pat, "%}$") then + for part in string.gmatch(pat, "[^,]+") do + if not string.find(part, "[" .. specialchars .. "]") then + if type(ft) == "string" then + sections.filename.str[part] = ft + else + sections.filename.func[part] = ft + end + elseif string.match(part, "^%*%.[^%./" .. specialchars .. "]+$") then + if type(ft) == "string" then + sections.extension.str[part:sub(3)] = ft + else + sections.extension.func[part:sub(3)] = ft + end + else + if string.match(part, "^%*/[^" .. specialchars .. "]+$") then + -- For patterns matching */some/pattern we want to easily match files + -- with path /some/pattern, so include those in filename detection + if type(ft) == "string" then + sections.filename.str[part:sub(2)] = ft + else + sections.filename.func[part:sub(2)] = ft + end + end + + if string.find(part, "^[%w-_.*?%[%]/]+$") then + local p = part:gsub("%.", "%%."):gsub("%*", ".*"):gsub("%?", ".") + -- Insert into array to maintain order rather than setting + -- key-value directly + if type(ft) == "string" then + sections.pattern.str[p] = ft + else + sections.pattern.func[p] = ft + end + else + ok = false + end + end + end + end + + return ok +end + +local function parse_line(line) + local pat, ft + pat, ft = line:match("^%s*au%a* Buf[%a,]+%s+(%S+)%s+setf%s+(%S+)") + if pat then + return add_pattern(pat, ft) + else + local func + pat, func = line:match("^%s*au%a* Buf[%a,]+%s+(%S+)%s+call%s+(%S+)") + if pat then + return add_pattern(pat, function() return func end) + end + end +end + +local unparsed = {} +local full_line +for line in io.lines(filetype_vim) do + local cont = string.match(line, "^%s*\\%s*(.*)$") + if cont then + full_line = full_line .. " " .. cont + else + if full_line then + if not parse_line(full_line) and string.find(full_line, "^%s*au%a* Buf") then + table.insert(unparsed, full_line) + end + end + full_line = line + end +end + +if #unparsed > 0 then + print("Failed to parse the following patterns:") + for _, v in ipairs(unparsed) do + print(v) + end +end + +local function add_item(indent, key, ft) + if type(ft) == "string" then + if string.find(key, "%A") or keywords[key] then + key = string.format("[\"%s\"]", key) + end + return string.format([[%s%s = "%s",]], indent, key, ft) + elseif type(ft) == "function" then + local func = ft() + if string.find(key, "%A") or keywords[key] then + key = string.format("[\"%s\"]", key) + end + -- Right now only a single argument is supported, which covers + -- everything in filetype.vim as of this writing + local arg = string.match(func, "%((.*)%)$") + func = string.gsub(func, "%(.*$", "") + if arg == "" then + -- Function with no arguments, call the function directly + return string.format([[%s%s = function() vim.fn["%s"]() end,]], indent, key, func) + elseif string.match(arg, [[^(["']).*%1$]]) then + -- String argument + if func == "s:StarSetf" then + return string.format([[%s%s = starsetf(%s),]], indent, key, arg) + else + return string.format([[%s%s = function() vim.fn["%s"](%s) end,]], indent, key, func, arg) + end + elseif string.find(arg, "%(") then + -- Function argument + return string.format([[%s%s = function() vim.fn["%s"](vim.fn.%s) end,]], indent, key, func, arg) + else + assert(false, arg) + end + end +end + +do + local lines = {} + local start = false + for line in io.lines(filetype_lua) do + if line:match("^%s+-- END [A-Z]+$") then + start = false + end + + if not start then + table.insert(lines, line) + end + + local indent, section = line:match("^(%s+)-- BEGIN ([A-Z]+)$") + if section then + start = true + local t = sections[string.lower(section)] + + local sorted = {} + for k, v in pairs(t.str) do + table.insert(sorted, {[k] = v}) + end + + table.sort(sorted, function(a, b) + return a[next(a)] < b[next(b)] + end) + + for _, v in ipairs(sorted) do + local k = next(v) + table.insert(lines, add_item(indent, k, v[k])) + end + + sorted = {} + for k, v in pairs(t.func) do + table.insert(sorted, {[k] = v}) + end + + table.sort(sorted, function(a, b) + return next(a) < next(b) + end) + + for _, v in ipairs(sorted) do + local k = next(v) + table.insert(lines, add_item(indent, k, v[k])) + end + end + end + local f = io.open(filetype_lua, "w") + f:write(table.concat(lines, "\n") .. "\n") + f:close() +end diff --git a/scripts/gen_vimdoc.py b/scripts/gen_vimdoc.py index 89fc14121e..7b6d974181 100755 --- a/scripts/gen_vimdoc.py +++ b/scripts/gen_vimdoc.py @@ -52,7 +52,7 @@ import logging from xml.dom import minidom -MIN_PYTHON_VERSION = (3, 5) +MIN_PYTHON_VERSION = (3, 6) if sys.version_info < MIN_PYTHON_VERSION: print("requires Python {}.{}+".format(*MIN_PYTHON_VERSION)) @@ -95,8 +95,8 @@ CONFIG = { 'window.c', 'win_config.c', 'tabpage.c', + 'autocmd.c', 'ui.c', - 'extmark.c', ], # List of files/directories for doxygen to read, separated by blanks 'files': os.path.join(base_dir, 'src/nvim/api'), @@ -128,12 +128,16 @@ CONFIG = { 'shared.lua', 'uri.lua', 'ui.lua', + 'filetype.lua', + 'keymap.lua', ], 'files': ' '.join([ os.path.join(base_dir, 'src/nvim/lua/vim.lua'), os.path.join(base_dir, 'runtime/lua/vim/shared.lua'), os.path.join(base_dir, 'runtime/lua/vim/uri.lua'), os.path.join(base_dir, 'runtime/lua/vim/ui.lua'), + os.path.join(base_dir, 'runtime/lua/vim/filetype.lua'), + os.path.join(base_dir, 'runtime/lua/vim/keymap.lua'), ]), 'file_patterns': '*.lua', 'fn_name_prefix': '', @@ -148,6 +152,8 @@ CONFIG = { 'shared': 'vim', 'uri': 'vim', 'ui': 'vim.ui', + 'filetype': 'vim.filetype', + 'keymap': 'vim.keymap', }, 'append_only': [ 'shared.lua', 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/uncrustify.sh b/scripts/uncrustify.sh new file mode 100755 index 0000000000..ac5d542c29 --- /dev/null +++ b/scripts/uncrustify.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash + +set -e + +# Check that you have uncrustify +hash uncrustify + +COMMITISH="${1:-master}" +for file in $(git diff --diff-filter=d --name-only $COMMITISH | grep '\.[ch]$'); do + uncrustify -c src/uncrustify.cfg -l C --replace --no-backup "$file" +done diff --git a/scripts/vim-patch.sh b/scripts/vim-patch.sh index ac47f6aafd..67a2cc96fd 100755 --- a/scripts/vim-patch.sh +++ b/scripts/vim-patch.sh @@ -266,8 +266,6 @@ get_vimpatch() { msg_ok "Saved patch to '${NVIM_SOURCE_DIR}/${patch_file}'." } -# shellcheck disable=SC2015 -# ^ "Note that A && B || C is not if-then-else." stage_patch() { get_vimpatch "$1" local try_apply="${2:-}" @@ -282,23 +280,32 @@ stage_patch() { echo " branch; not creating a new branch." else printf '\nFetching "%s/master".\n' "${nvim_remote}" - output="$(git fetch "${nvim_remote}" master 2>&1)" && - msg_ok "${output}" || - (msg_err "${output}"; false) + if output="$(git fetch "$nvim_remote" master 2>&1)"; then + msg_ok "$output" + else + msg_err "$output" + exit 1 + fi local nvim_branch="${BRANCH_PREFIX}${vim_version}" echo echo "Creating new branch '${nvim_branch}' based on '${nvim_remote}/master'." cd "${NVIM_SOURCE_DIR}" - output="$(git checkout -b "${nvim_branch}" "${nvim_remote}/master" 2>&1)" && - msg_ok "${output}" || - (msg_err "${output}"; false) + if output="$(git checkout -b "$nvim_branch" "$nvim_remote/master" 2>&1)"; then + msg_ok "$output" + else + msg_err "$output" + exit 1 + fi fi printf "\nCreating empty commit with correct commit message.\n" - output="$(commit_message | git commit --allow-empty --file 2>&1 -)" && - msg_ok "${output}" || - (msg_err "${output}"; false) + if output="$(commit_message | git commit --allow-empty --file 2>&1 -)"; then + msg_ok "$output" + else + msg_err "$output" + exit 1 + fi local ret=0 if test -n "$try_apply" ; then @@ -340,8 +347,6 @@ git_hub_pr() { git hub pull new -m "${pr_message}" } -# shellcheck disable=SC2015 -# ^ "Note that A && B || C is not if-then-else." submit_pr() { require_executable git local push_first @@ -392,17 +397,23 @@ submit_pr() { fi fi echo "Pushing to '${push_remote}/${checked_out_branch}'." - output="$(git push "${push_remote}" "${checked_out_branch}" 2>&1)" && - msg_ok "${output}" || - (msg_err "${output}"; false) + if output="$(git push "$push_remote" "$checked_out_branch" 2>&1)"; then + msg_ok "$output" + else + msg_err "$output" + exit 1 + fi echo fi echo "Creating pull request." - output="$(${submit_fn} "${pr_title}" "${pr_body}" 2>&1)" && - msg_ok "${output}" || - (msg_err "${output}"; false) + if output="$($submit_fn "$pr_title" "$pr_body" 2>&1)"; then + msg_ok "$output" + else + msg_err "$output" + exit 1 + fi echo echo "Cleaning up files." @@ -692,14 +703,14 @@ review_commit() { message_length="$(wc -l <<< "${expected_commit_message}")" local commit_message commit_message="$(tail -n +4 "${NVIM_SOURCE_DIR}/n${patch_file}" | head -n "${message_length}")" - if [[ "${commit_message#${git_patch_prefix}}" == "${expected_commit_message}" ]]; then + if [[ "${commit_message#"$git_patch_prefix"}" == "${expected_commit_message}" ]]; then msg_ok "Found expected commit message." else msg_err "Wrong commit message." echo " Expected:" echo "${expected_commit_message}" echo " Actual:" - echo "${commit_message#${git_patch_prefix}}" + echo "${commit_message#"$git_patch_prefix"}" fi get_vimpatch "${vim_version}" diff --git a/src/cjson/lua_cjson.c b/src/cjson/lua_cjson.c index cf9e82c38e..b5f97bc485 100644 --- a/src/cjson/lua_cjson.c +++ b/src/cjson/lua_cjson.c @@ -776,7 +776,7 @@ static void json_append_data(lua_State *l, json_config_t *cfg, if (has_metatable) { - nlua_pushref(l, nlua_empty_dict_ref); + nlua_pushref(l, nlua_get_empty_dict_ref(l)); if (lua_rawequal(l, -2, -1)) { as_empty_dict = true; } else { @@ -822,7 +822,7 @@ static void json_append_data(lua_State *l, json_config_t *cfg, } break; case LUA_TUSERDATA: - nlua_pushref(l, nlua_nil_ref); + nlua_pushref(l, nlua_get_nil_ref(l)); bool is_nil = lua_rawequal(l, -2, -1); lua_pop(l, 1); if (is_nil) { @@ -1285,7 +1285,7 @@ static void json_parse_object_context(lua_State *l, json_parse_t *json) /* Handle empty objects */ if (token.type == T_OBJ_END) { - nlua_pushref(l, nlua_empty_dict_ref); \ + nlua_pushref(l, nlua_get_empty_dict_ref(l)); \ lua_setmetatable(l, -2); \ json_decode_ascend(json); return; @@ -1392,7 +1392,7 @@ static void json_process_value(lua_State *l, json_parse_t *json, if (use_luanil) { lua_pushnil(l); } else { - nlua_pushref(l, nlua_nil_ref); + nlua_pushref(l, nlua_get_nil_ref(l)); } break;; default: @@ -1549,7 +1549,15 @@ int lua_cjson_new(lua_State *l) }; /* Initialise number conversions */ - fpconv_init(); + lua_getfield(l, LUA_REGISTRYINDEX, "nvim.thread"); + bool is_thread = lua_toboolean(l, -1); + lua_pop(l, 1); + + // Since fpconv_init does not need to be called multiple times and is not + // thread safe, it should only be called in the main thread. + if (!is_thread) { + fpconv_init(); + } /* Test if array metatables are in registry */ lua_pushlightuserdata(l, json_lightudata_mask(&json_empty_array)); @@ -1582,7 +1590,7 @@ int lua_cjson_new(lua_State *l) compat_luaL_setfuncs(l, reg, 1); /* Set cjson.null */ - nlua_pushref(l, nlua_nil_ref); + nlua_pushref(l, nlua_get_nil_ref(l)); lua_setfield(l, -2, "null"); /* Set cjson.empty_array_mt */ diff --git a/src/mpack/lmpack.c b/src/mpack/lmpack.c index 126f2f3824..53d7092a0c 100644 --- a/src/mpack/lmpack.c +++ b/src/mpack/lmpack.c @@ -246,7 +246,7 @@ static mpack_uint32_t lmpack_objlen(lua_State *L, int *is_array) } end: - if ((size_t)-1 > (mpack_uint32_t)-1 && len > (mpack_uint32_t)-1) + if ((size_t)-1 > (mpack_uint32_t)-1 && len > (mpack_uint32_t)-1) // -V560 /* msgpack spec doesn't allow lengths > 32 bits */ len = (mpack_uint32_t)-1; assert(top == lua_gettop(L)); diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt index 9c4b778169..a6505feb6b 100644 --- a/src/nvim/CMakeLists.txt +++ b/src/nvim/CMakeLists.txt @@ -62,6 +62,9 @@ set(LUA_SHARED_MODULE_SOURCE ${PROJECT_SOURCE_DIR}/runtime/lua/vim/shared.lua) set(LUA_INSPECT_MODULE_SOURCE ${PROJECT_SOURCE_DIR}/runtime/lua/vim/inspect.lua) set(LUA_F_MODULE_SOURCE ${PROJECT_SOURCE_DIR}/runtime/lua/vim/F.lua) set(LUA_META_MODULE_SOURCE ${PROJECT_SOURCE_DIR}/runtime/lua/vim/_meta.lua) +set(LUA_FILETYPE_MODULE_SOURCE ${PROJECT_SOURCE_DIR}/runtime/lua/vim/filetype.lua) +set(LUA_LOAD_PACKAGE_MODULE_SOURCE ${PROJECT_SOURCE_DIR}/runtime/lua/vim/_load_package.lua) +set(LUA_KEYMAP_MODULE_SOURCE ${PROJECT_SOURCE_DIR}/runtime/lua/vim/keymap.lua) set(CHAR_BLOB_GENERATOR ${GENERATOR_DIR}/gen_char_blob.lua) set(LINT_SUPPRESS_FILE ${PROJECT_BINARY_DIR}/errors.json) set(LINT_SUPPRESS_URL_BASE "https://raw.githubusercontent.com/neovim/doc/gh-pages/reports/clint") @@ -334,6 +337,9 @@ add_custom_command( ${LUA_INSPECT_MODULE_SOURCE} inspect_module ${LUA_F_MODULE_SOURCE} lua_F_module ${LUA_META_MODULE_SOURCE} lua_meta_module + ${LUA_FILETYPE_MODULE_SOURCE} lua_filetype_module + ${LUA_LOAD_PACKAGE_MODULE_SOURCE} lua_load_package_module + ${LUA_KEYMAP_MODULE_SOURCE} lua_keymap_module DEPENDS ${CHAR_BLOB_GENERATOR} ${LUA_VIM_MODULE_SOURCE} @@ -341,6 +347,9 @@ add_custom_command( ${LUA_INSPECT_MODULE_SOURCE} ${LUA_F_MODULE_SOURCE} ${LUA_META_MODULE_SOURCE} + ${LUA_FILETYPE_MODULE_SOURCE} + ${LUA_LOAD_PACKAGE_MODULE_SOURCE} + ${LUA_KEYMAP_MODULE_SOURCE} VERBATIM ) @@ -490,6 +499,9 @@ add_executable(nvim ${NVIM_GENERATED_FOR_SOURCES} ${NVIM_GENERATED_FOR_HEADERS} ${EXTERNAL_SOURCES} ${EXTERNAL_HEADERS}) target_link_libraries(nvim ${NVIM_EXEC_LINK_LIBRARIES}) install_helper(TARGETS nvim) +if(MSVC) + install(FILES $<TARGET_PDB_FILE:nvim> DESTINATION ${CMAKE_INSTALL_BINDIR} OPTIONAL) +endif() set_property(TARGET nvim APPEND PROPERTY INCLUDE_DIRECTORIES ${LUA_PREFERRED_INCLUDE_DIRS}) diff --git a/src/nvim/api/autocmd.c b/src/nvim/api/autocmd.c new file mode 100644 index 0000000000..9a8eccd22c --- /dev/null +++ b/src/nvim/api/autocmd.c @@ -0,0 +1,707 @@ +// This is an open source non-commercial project. Dear PVS-Studio, please check +// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + +#include <stdbool.h> +#include <stdio.h> + +#include "lauxlib.h" +#include "nvim/api/autocmd.h" +#include "nvim/api/private/defs.h" +#include "nvim/api/private/helpers.h" +#include "nvim/ascii.h" +#include "nvim/buffer.h" +#include "nvim/eval/typval.h" +#include "nvim/fileio.h" +#include "nvim/lua/executor.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "api/autocmd.c.generated.h" +#endif + +#define AUCMD_MAX_PATTERNS 256 + +// Copy string or array of strings into an empty array. +// Get the event number, unless it is an error. Then goto `goto_name`. +#define GET_ONE_EVENT(event_nr, event_str, goto_name) \ + char_u *__next_ev; \ + event_T event_nr = \ + event_name2nr((char_u *)event_str.data.string.data, &__next_ev); \ + if (event_nr >= NUM_EVENTS) { \ + api_set_error(err, kErrorTypeValidation, "unexpected event"); \ + goto goto_name; \ + } + + +// ID for associating autocmds created via nvim_create_autocmd +// Used to delete autocmds from nvim_del_autocmd +static int64_t next_autocmd_id = 1; + +/// Get autocmds that match the requirements passed to {opts}. +/// +/// @param opts Optional Parameters: +/// - event : Name or list of name of events to match against +/// - group (string): Name of group to match against +/// - pattern: Pattern or list of patterns to match against +/// +/// @return A list of autocmds that match +Array nvim_get_autocmds(Dict(get_autocmds) *opts, Error *err) + FUNC_API_SINCE(9) +{ + // TODO(tjdevries): Would be cool to add nvim_get_autocmds({ id = ... }) + + Array autocmd_list = ARRAY_DICT_INIT; + char_u *pattern_filters[AUCMD_MAX_PATTERNS]; + char_u pattern_buflocal[BUFLOCAL_PAT_LEN]; + + bool event_set[NUM_EVENTS] = { false }; + bool check_event = false; + + int group = 0; + + if (opts->group.type != kObjectTypeNil) { + Object v = opts->group; + if (v.type != kObjectTypeString) { + api_set_error(err, kErrorTypeValidation, "group must be a string."); + goto cleanup; + } + + group = augroup_find(v.data.string.data); + + if (group < 0) { + api_set_error(err, kErrorTypeValidation, "invalid augroup passed."); + goto cleanup; + } + } + + if (opts->event.type != kObjectTypeNil) { + check_event = true; + + Object v = opts->event; + if (v.type == kObjectTypeString) { + GET_ONE_EVENT(event_nr, v, cleanup); + event_set[event_nr] = true; + } else if (v.type == kObjectTypeArray) { + FOREACH_ITEM(v.data.array, event_v, { + if (event_v.type != kObjectTypeString) { + api_set_error(err, + kErrorTypeValidation, + "Every event must be a string in 'event'"); + goto cleanup; + } + + GET_ONE_EVENT(event_nr, event_v, cleanup); + event_set[event_nr] = true; + }) + } else { + api_set_error(err, + kErrorTypeValidation, + "Not a valid 'event' value. Must be a string or an array"); + goto cleanup; + } + } + + int pattern_filter_count = 0; + if (opts->pattern.type != kObjectTypeNil) { + Object v = opts->pattern; + if (v.type == kObjectTypeString) { + pattern_filters[pattern_filter_count] = (char_u *)v.data.string.data; + pattern_filter_count += 1; + } else if (v.type == kObjectTypeArray) { + FOREACH_ITEM(v.data.array, item, { + pattern_filters[pattern_filter_count] = (char_u *)item.data.string.data; + pattern_filter_count += 1; + }); + } else { + api_set_error(err, + kErrorTypeValidation, + "Not a valid 'pattern' value. Must be a string or an array"); + goto cleanup; + } + + if (pattern_filter_count >= AUCMD_MAX_PATTERNS) { + api_set_error(err, + kErrorTypeValidation, + "Too many patterns. Please limit yourself to less"); + goto cleanup; + } + } + + FOR_ALL_AUEVENTS(event) { + if (check_event && !event_set[event]) { + continue; + } + + for (AutoPat *ap = au_get_autopat_for_event(event); + ap != NULL; + ap = ap->next) { + if (ap == NULL || ap->cmds == NULL) { + continue; + } + + // Skip autocmds from invalid groups if passed. + if (group != 0 && ap->group != group) { + continue; + } + + // Skip 'pattern' from invalid patterns if passed. + if (pattern_filter_count > 0) { + bool passed = false; + for (int i = 0; i < pattern_filter_count; i++) { + assert(i < AUCMD_MAX_PATTERNS); + assert(pattern_filters[i]); + + char_u *pat = pattern_filters[i]; + int patlen = (int)STRLEN(pat); + + if (aupat_is_buflocal(pat, patlen)) { + aupat_normalize_buflocal_pat(pattern_buflocal, + pat, + patlen, + aupat_get_buflocal_nr(pat, patlen)); + + pat = pattern_buflocal; + } + + if (strequal((char *)ap->pat, (char *)pat)) { + passed = true; + break; + } + } + + if (!passed) { + continue; + } + } + + for (AutoCmd *ac = ap->cmds; ac != NULL; ac = ac->next) { + if (aucmd_exec_is_deleted(ac->exec)) { + continue; + } + + Dictionary autocmd_info = ARRAY_DICT_INIT; + + if (ap->group != AUGROUP_DEFAULT) { + PUT(autocmd_info, "group", INTEGER_OBJ(ap->group)); + } + + if (ac->id > 0) { + PUT(autocmd_info, "id", INTEGER_OBJ(ac->id)); + } + + if (ac->desc != NULL) { + PUT(autocmd_info, "desc", CSTR_TO_OBJ(ac->desc)); + } + + PUT(autocmd_info, + "command", + STRING_OBJ(cstr_to_string(aucmd_exec_to_string(ac, ac->exec)))); + + PUT(autocmd_info, + "pattern", + STRING_OBJ(cstr_to_string((char *)ap->pat))); + + PUT(autocmd_info, + "event", + STRING_OBJ(cstr_to_string((char *)event_nr2name(event)))); + + PUT(autocmd_info, "once", BOOLEAN_OBJ(ac->once)); + + if (ap->buflocal_nr) { + PUT(autocmd_info, "buflocal", BOOLEAN_OBJ(true)); + PUT(autocmd_info, "buffer", INTEGER_OBJ(ap->buflocal_nr)); + } else { + PUT(autocmd_info, "buflocal", BOOLEAN_OBJ(false)); + } + + // TODO(sctx): It would be good to unify script_ctx to actually work with lua + // right now it's just super weird, and never really gives you the info that + // you would expect from this. + // + // I think we should be able to get the line number, filename, etc. from lua + // when we're executing something, and it should be easy to then save that + // info here. + // + // I think it's a big loss not getting line numbers of where options, autocmds, + // etc. are set (just getting "Sourced (lua)" or something is not that helpful. + // + // Once we do that, we can put these into the autocmd_info, but I don't think it's + // useful to do that at this time. + // + // PUT(autocmd_info, "sid", INTEGER_OBJ(ac->script_ctx.sc_sid)); + // PUT(autocmd_info, "lnum", INTEGER_OBJ(ac->script_ctx.sc_lnum)); + + ADD(autocmd_list, DICTIONARY_OBJ(autocmd_info)); + } + } + } + +cleanup: + return autocmd_list; +} + +/// Create an autocmd. +/// +/// @param event The event or events to register this autocmd +/// Required keys: +/// event: string | ArrayOf(string) +/// +/// Examples: +/// - event: "pat1,pat2,pat3", +/// - event: "pat1" +/// - event: { "pat1" } +/// - event: { "pat1", "pat2", "pat3" } +/// +/// @param opts Optional Parameters: +/// - callback: (string|function) +/// - (string): The name of the viml function to execute when triggering this autocmd +/// - (function): The lua function to execute when triggering this autocmd +/// - NOTE: Cannot be used with {command} +/// - command: (string) command +/// - vimscript command +/// - NOTE: Cannot be used with {callback} +/// Eg. command = "let g:value_set = v:true" +/// - pattern: (string|table) +/// - pattern or patterns to match against +/// - defaults to "*". +/// - NOTE: Cannot be used with {buffer} +/// - buffer: (bufnr) +/// - create a |autocmd-buflocal| autocmd. +/// - NOTE: Cannot be used with {pattern} +/// - group: (string) The augroup name +/// - once: (boolean) - See |autocmd-once| +/// - nested: (boolean) - See |autocmd-nested| +/// - desc: (string) - Description of the autocmd +/// +/// @returns opaque value to use with nvim_del_autocmd +Integer nvim_create_autocmd(uint64_t channel_id, Object event, Dict(create_autocmd) *opts, + Error *err) + FUNC_API_SINCE(9) +{ + int64_t autocmd_id = -1; + + const char_u pattern_buflocal[BUFLOCAL_PAT_LEN]; + int au_group = AUGROUP_DEFAULT; + char *desc = NULL; + + Array patterns = ARRAY_DICT_INIT; + Array event_array = ARRAY_DICT_INIT; + + AucmdExecutable aucmd = AUCMD_EXECUTABLE_INIT; + Callback cb = CALLBACK_NONE; + + + if (!unpack_string_or_array(&event_array, &event, "event", err)) { + goto cleanup; + } + + if (opts->callback.type != kObjectTypeNil && opts->command.type != kObjectTypeNil) { + api_set_error(err, kErrorTypeValidation, + "cannot pass both: 'callback' and 'command' for the same autocmd"); + goto cleanup; + } else if (opts->callback.type != kObjectTypeNil) { + // TODO(tjdevries): It's possible we could accept callable tables, + // but we don't do that many other places, so for the moment let's + // not do that. + + Object *callback = &opts->callback; + if (callback->type == kObjectTypeLuaRef) { + if (callback->data.luaref == LUA_NOREF) { + api_set_error(err, + kErrorTypeValidation, + "must pass an actual value"); + goto cleanup; + } + + if (!nlua_ref_is_function(callback->data.luaref)) { + api_set_error(err, + kErrorTypeValidation, + "must pass a function for callback"); + goto cleanup; + } + + cb.type = kCallbackLua; + cb.data.luaref = api_new_luaref(callback->data.luaref); + } else if (callback->type == kObjectTypeString) { + cb.type = kCallbackFuncref; + cb.data.funcref = vim_strsave((char_u *)callback->data.string.data); + } else { + api_set_error(err, + kErrorTypeException, + "'callback' must be a lua function or name of vim function"); + goto cleanup; + } + + aucmd.type = CALLABLE_CB; + aucmd.callable.cb = cb; + } else if (opts->command.type != kObjectTypeNil) { + Object *command = &opts->command; + if (command->type == kObjectTypeString) { + aucmd.type = CALLABLE_EX; + aucmd.callable.cmd = vim_strsave((char_u *)command->data.string.data); + } else { + api_set_error(err, + kErrorTypeValidation, + "'command' must be a string"); + goto cleanup; + } + } else { + api_set_error(err, kErrorTypeValidation, "must pass one of: 'command', 'callback'"); + goto cleanup; + } + + bool is_once = api_object_to_bool(opts->once, "once", false, err); + bool is_nested = api_object_to_bool(opts->nested, "nested", false, err); + + // TODO(tjdevries): accept number for namespace instead + if (opts->group.type != kObjectTypeNil) { + Object *v = &opts->group; + if (v->type != kObjectTypeString) { + api_set_error(err, kErrorTypeValidation, "'group' must be a string"); + goto cleanup; + } + + au_group = augroup_find(v->data.string.data); + + if (au_group == AUGROUP_ERROR) { + api_set_error(err, + kErrorTypeException, + "invalid augroup: %s", v->data.string.data); + + goto cleanup; + } + } + + if (opts->pattern.type != kObjectTypeNil && opts->buffer.type != kObjectTypeNil) { + api_set_error(err, kErrorTypeValidation, + "cannot pass both: 'pattern' and 'buffer' for the same autocmd"); + goto cleanup; + } else if (opts->pattern.type != kObjectTypeNil) { + Object *v = &opts->pattern; + + if (v->type == kObjectTypeString) { + char_u *pat = (char_u *)v->data.string.data; + size_t patlen = aucmd_pattern_length(pat); + while (patlen) { + ADD(patterns, STRING_OBJ(cbuf_to_string((char *)pat, patlen))); + + pat = aucmd_next_pattern(pat, patlen); + patlen = aucmd_pattern_length(pat); + } + } else if (v->type == kObjectTypeArray) { + if (!check_autocmd_string_array(patterns, "pattern", err)) { + goto cleanup; + } + + Array array = v->data.array; + for (size_t i = 0; i < array.size; i++) { + char_u *pat = (char_u *)array.items[i].data.string.data; + size_t patlen = aucmd_pattern_length(pat); + while (patlen) { + ADD(patterns, STRING_OBJ(cbuf_to_string((char *)pat, patlen))); + + pat = aucmd_next_pattern(pat, patlen); + patlen = aucmd_pattern_length(pat); + } + } + } else { + api_set_error(err, + kErrorTypeValidation, + "'pattern' must be a string"); + goto cleanup; + } + } else if (opts->buffer.type != kObjectTypeNil) { + if (opts->buffer.type != kObjectTypeInteger) { + api_set_error(err, + kErrorTypeValidation, + "'buffer' must be an integer"); + goto cleanup; + } + + buf_T *buf = find_buffer_by_handle((Buffer)opts->buffer.data.integer, err); + if (ERROR_SET(err)) { + goto cleanup; + } + + snprintf((char *)pattern_buflocal, BUFLOCAL_PAT_LEN, "<buffer=%d>", (int)buf->handle); + ADD(patterns, STRING_OBJ(cstr_to_string((char *)pattern_buflocal))); + } + + if (aucmd.type == CALLABLE_NONE) { + api_set_error(err, + kErrorTypeValidation, + "'command' or 'callback' is required"); + goto cleanup; + } + + if (opts->desc.type != kObjectTypeNil) { + if (opts->desc.type == kObjectTypeString) { + desc = opts->desc.data.string.data; + } else { + api_set_error(err, + kErrorTypeValidation, + "'desc' must be a string"); + goto cleanup; + } + } + + if (patterns.size == 0) { + ADD(patterns, STRING_OBJ(STATIC_CSTR_TO_STRING("*"))); + } + + if (event_array.size == 0) { + api_set_error(err, kErrorTypeValidation, "'event' is a required key"); + goto cleanup; + } + + autocmd_id = next_autocmd_id++; + FOREACH_ITEM(event_array, event_str, { + GET_ONE_EVENT(event_nr, event_str, cleanup); + + int retval; + + for (size_t i = 0; i < patterns.size; i++) { + Object pat = patterns.items[i]; + + // See: TODO(sctx) + WITH_SCRIPT_CONTEXT(channel_id, { + retval = autocmd_register(autocmd_id, + event_nr, + (char_u *)pat.data.string.data, + (int)pat.data.string.size, + au_group, + is_once, + is_nested, + desc, + aucmd); + }); + + if (retval == FAIL) { + api_set_error(err, kErrorTypeException, "Failed to set autocmd"); + goto cleanup; + } + } + }); + + +cleanup: + aucmd_exec_free(&aucmd); + api_free_array(event_array); + api_free_array(patterns); + + return autocmd_id; +} + +/// Delete an autocmd by {id}. Autocmds only return IDs when created +/// via the API. Will not error if called and no autocmds match +/// the {id}. +/// +/// @param id Integer The ID returned by nvim_create_autocmd +void nvim_del_autocmd(Integer id) + FUNC_API_SINCE(9) +{ + autocmd_delete_id(id); +} + +/// Create or get an augroup. +/// +/// To get an existing augroup ID, do: +/// <pre> +/// local id = vim.api.nvim_create_augroup(name, { +/// clear = false +/// }) +/// </pre> +/// +/// @param name String: The name of the augroup to create +/// @param opts Parameters +/// - clear (bool): Whether to clear existing commands or not. +/// Defaults to true. +/// See |autocmd-groups| +/// +/// @returns opaque value to use with nvim_del_augroup_by_id +Integer nvim_create_augroup(uint64_t channel_id, String name, Dict(create_augroup) *opts, + Error *err) + FUNC_API_SINCE(9) +{ + char *augroup_name = name.data; + bool clear_autocmds = api_object_to_bool(opts->clear, "clear", true, err); + + int augroup = -1; + WITH_SCRIPT_CONTEXT(channel_id, { + augroup = augroup_add(augroup_name); + if (augroup == AUGROUP_ERROR) { + api_set_error(err, kErrorTypeException, "Failed to set augroup"); + return -1; + } + + if (clear_autocmds) { + FOR_ALL_AUEVENTS(event) { + aupat_del_for_event_and_group(event, augroup); + } + } + }); + + return augroup; +} + +/// Delete an augroup by {id}. {id} can only be returned when augroup was +/// created with |nvim_create_augroup|. +/// +/// NOTE: behavior differs from augroup-delete. +/// +/// When deleting an augroup, autocmds contained by this augroup will also be deleted and cleared. +/// This augroup will no longer exist +void nvim_del_augroup_by_id(Integer id) + FUNC_API_SINCE(9) +{ + char *name = augroup_name((int)id); + augroup_del(name, false); +} + +/// Delete an augroup by {name}. +/// +/// NOTE: behavior differs from augroup-delete. +/// +/// When deleting an augroup, autocmds contained by this augroup will also be deleted and cleared. +/// This augroup will no longer exist +void nvim_del_augroup_by_name(String name) + FUNC_API_SINCE(9) +{ + augroup_del(name.data, false); +} + +/// Do one autocmd. +/// +/// @param event The event or events to execute +/// @param opts Optional Parameters: +/// - buffer (number) - buffer number +/// - NOTE: Cannot be used with {pattern} +/// - pattern (string|table) - optional, defaults to "*". +/// - NOTE: Cannot be used with {buffer} +/// - group (string) - autocmd group name +/// - modeline (boolean) - Default true, see |<nomodeline>| +void nvim_do_autocmd(Object event, Dict(do_autocmd) *opts, Error *err) + FUNC_API_SINCE(9) +{ + int au_group = AUGROUP_ALL; + bool modeline = true; + + buf_T *buf = curbuf; + bool set_buf = false; + + char_u *pattern = NULL; + bool set_pattern = false; + + Array event_array = ARRAY_DICT_INIT; + + if (!unpack_string_or_array(&event_array, &event, "event", err)) { + goto cleanup; + } + + if (opts->group.type != kObjectTypeNil) { + if (opts->group.type != kObjectTypeString) { + api_set_error(err, kErrorTypeValidation, "'group' must be a string"); + goto cleanup; + } + + au_group = augroup_find(opts->group.data.string.data); + + if (au_group == AUGROUP_ERROR) { + api_set_error(err, + kErrorTypeException, + "invalid augroup: %s", opts->group.data.string.data); + + goto cleanup; + } + } + + if (opts->buffer.type != kObjectTypeNil) { + Object buf_obj = opts->buffer; + if (buf_obj.type != kObjectTypeInteger && buf_obj.type != kObjectTypeBuffer) { + api_set_error(err, kErrorTypeException, "invalid buffer: %d", buf_obj.type); + goto cleanup; + } + + buf = find_buffer_by_handle((Buffer)buf_obj.data.integer, err); + set_buf = true; + + if (ERROR_SET(err)) { + goto cleanup; + } + } + + if (opts->pattern.type != kObjectTypeNil) { + if (opts->pattern.type != kObjectTypeString) { + api_set_error(err, kErrorTypeValidation, "'pattern' must be a string"); + goto cleanup; + } + + pattern = vim_strsave((char_u *)opts->pattern.data.string.data); + set_pattern = true; + } + + modeline = api_object_to_bool(opts->modeline, "modeline", true, err); + + if (set_pattern && set_buf) { + api_set_error(err, kErrorTypeValidation, "must not set 'buffer' and 'pattern'"); + goto cleanup; + } + + bool did_aucmd = false; + FOREACH_ITEM(event_array, event_str, { + GET_ONE_EVENT(event_nr, event_str, cleanup) + + did_aucmd |= apply_autocmds_group(event_nr, pattern, NULL, true, au_group, buf, NULL); + }) + + if (did_aucmd && modeline) { + do_modelines(0); + } + +cleanup: + api_free_array(event_array); + XFREE_CLEAR(pattern); +} + +static bool check_autocmd_string_array(Array arr, char *k, Error *err) +{ + for (size_t i = 0; i < arr.size; i++) { + if (arr.items[i].type != kObjectTypeString) { + api_set_error(err, + kErrorTypeValidation, + "All entries in '%s' must be strings", + k); + return false; + } + + // Disallow newlines in the middle of the line. + const String l = arr.items[i].data.string; + if (memchr(l.data, NL, l.size)) { + api_set_error(err, kErrorTypeValidation, + "String cannot contain newlines"); + return false; + } + } + return true; +} + +static bool unpack_string_or_array(Array *array, Object *v, char *k, Error *err) +{ + if (v->type == kObjectTypeString) { + ADD(*array, copy_object(*v)); + } else if (v->type == kObjectTypeArray) { + if (!check_autocmd_string_array(v->data.array, k, err)) { + return false; + } + *array = copy_array(v->data.array); + } else { + api_set_error(err, + kErrorTypeValidation, + "'%s' must be an array or a string.", + k); + return false; + } + + return true; +} + +#undef GET_ONE_EVENT diff --git a/src/nvim/api/autocmd.h b/src/nvim/api/autocmd.h new file mode 100644 index 0000000000..f9432830d9 --- /dev/null +++ b/src/nvim/api/autocmd.h @@ -0,0 +1,11 @@ +#ifndef NVIM_API_AUTOCMD_H +#define NVIM_API_AUTOCMD_H + +#include <stdint.h> + +#include "nvim/api/private/defs.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "api/autocmd.h.generated.h" +#endif +#endif // NVIM_API_AUTOCMD_H diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c index ce9c4e27ad..3dd647e76f 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. @@ -835,7 +936,7 @@ Integer nvim_buf_get_changedtick(Buffer buffer, Error *err) /// @param[out] err Error details, if any /// @returns Array of maparg()-like dictionaries describing mappings. /// The "buffer" key holds the associated buffer handle. -ArrayOf(Dictionary) nvim_buf_get_keymap(Buffer buffer, String mode, Error *err) +ArrayOf(Dictionary) nvim_buf_get_keymap(uint64_t channel_id, Buffer buffer, String mode, Error *err) FUNC_API_SINCE(3) { buf_T *buf = find_buffer_by_handle(buffer, err); @@ -844,7 +945,7 @@ ArrayOf(Dictionary) nvim_buf_get_keymap(Buffer buffer, String mode, Error *err) return (Array)ARRAY_DICT_INIT; } - return keymap_array(mode, buf); + return keymap_array(mode, buf, channel_id == LUA_INTERNAL_CALL); } /// Sets a buffer-local |mapping| for the given mode. @@ -852,11 +953,11 @@ ArrayOf(Dictionary) nvim_buf_get_keymap(Buffer buffer, String mode, Error *err) /// @see |nvim_set_keymap()| /// /// @param buffer Buffer handle, or 0 for current buffer -void nvim_buf_set_keymap(Buffer buffer, String mode, String lhs, String rhs, Dict(keymap) *opts, - Error *err) +void nvim_buf_set_keymap(uint64_t channel_id, Buffer buffer, String mode, String lhs, String rhs, + Dict(keymap) *opts, Error *err) FUNC_API_SINCE(6) { - modify_keymap(buffer, false, mode, lhs, rhs, opts, err); + modify_keymap(channel_id, buffer, false, mode, lhs, rhs, opts, err); } /// Unmaps a buffer-local |mapping| for the given mode. @@ -864,11 +965,12 @@ void nvim_buf_set_keymap(Buffer buffer, String mode, String lhs, String rhs, Dic /// @see |nvim_del_keymap()| /// /// @param buffer Buffer handle, or 0 for current buffer -void nvim_buf_del_keymap(Buffer buffer, String mode, String lhs, Error *err) +void nvim_buf_del_keymap(uint64_t channel_id, Buffer buffer, String mode, + String lhs, Error *err) FUNC_API_SINCE(6) { String rhs = { .data = "", .size = 0 }; - modify_keymap(buffer, true, mode, lhs, rhs, NULL, err); + modify_keymap(channel_id, buffer, true, mode, lhs, rhs, NULL, err); } /// Gets a map of buffer-local |user-commands|. @@ -1273,6 +1375,63 @@ Object nvim_buf_call(Buffer buffer, LuaRef fun, Error *err) return res; } +/// Create a new user command |user-commands| in the given buffer. +/// +/// @param buffer Buffer handle, or 0 for current buffer. +/// @param[out] err Error details, if any. +/// @see nvim_add_user_command +void nvim_buf_add_user_command(Buffer buffer, String name, Object command, + Dict(user_command) *opts, Error *err) + FUNC_API_SINCE(9) +{ + buf_T *target_buf = find_buffer_by_handle(buffer, err); + if (ERROR_SET(err)) { + return; + } + + buf_T *save_curbuf = curbuf; + curbuf = target_buf; + add_user_command(name, command, opts, UC_BUFFER, err); + curbuf = save_curbuf; +} + +/// Delete a buffer-local user-defined command. +/// +/// Only commands created with |:command-buffer| or +/// |nvim_buf_add_user_command()| can be deleted with this function. +/// +/// @param buffer Buffer handle, or 0 for current buffer. +/// @param name Name of the command to delete. +/// @param[out] err Error details, if any. +void nvim_buf_del_user_command(Buffer buffer, String name, Error *err) + FUNC_API_SINCE(9) +{ + garray_T *gap; + if (buffer == -1) { + gap = &ucmds; + } else { + buf_T *buf = find_buffer_by_handle(buffer, err); + gap = &buf->b_ucmds; + } + + for (int i = 0; i < gap->ga_len; i++) { + ucmd_T *cmd = USER_CMD_GA(gap, i); + if (!STRCMP(name.data, cmd->uc_name)) { + free_ucmd(cmd); + + gap->ga_len -= 1; + + if (i < gap->ga_len) { + memmove(cmd, cmd + 1, (size_t)(gap->ga_len - i) * sizeof(ucmd_T)); + } + + return; + } + } + + api_set_error(err, kErrorTypeException, "No such user-defined command: %s", name.data); +} + Dictionary nvim__buf_stats(Buffer buffer, Error *err) { Dictionary rv = ARRAY_DICT_INIT; @@ -1329,11 +1488,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/deprecated.c b/src/nvim/api/deprecated.c index 76b699800e..d2013c597c 100644 --- a/src/nvim/api/deprecated.c +++ b/src/nvim/api/deprecated.c @@ -22,11 +22,11 @@ /// @deprecated /// @see nvim_exec -String nvim_command_output(String command, Error *err) +String nvim_command_output(uint64_t channel_id, String command, Error *err) FUNC_API_SINCE(1) FUNC_API_DEPRECATED_SINCE(7) { - return nvim_exec(command, true, err); + return nvim_exec(channel_id, command, true, err); } /// @deprecated Use nvim_exec_lua() instead. @@ -130,7 +130,7 @@ Integer nvim_buf_set_virtual_text(Buffer buffer, Integer src_id, Integer line, A return 0; } - uint64_t ns_id = src2ns(&src_id); + uint32_t ns_id = src2ns(&src_id); int width; VirtText virt_text = parse_virt_text(chunks, err, &width); @@ -148,11 +148,12 @@ Integer nvim_buf_set_virtual_text(Buffer buffer, Integer src_id, Integer line, A return src_id; } - Decoration *decor = xcalloc(1, sizeof(*decor)); - decor->virt_text = virt_text; - decor->virt_text_width = width; + Decoration decor = DECORATION_INIT; + decor.virt_text = virt_text; + decor.virt_text_width = width; + decor.priority = 0; - extmark_set(buf, ns_id, NULL, (int)line, 0, -1, -1, decor, true, + extmark_set(buf, ns_id, NULL, (int)line, 0, -1, -1, &decor, true, false, kExtmarkNoUndo); return src_id; } diff --git a/src/nvim/api/extmark.c b/src/nvim/api/extmark.c index 742b953c2a..3a968f07ab 100644 --- a/src/nvim/api/extmark.c +++ b/src/nvim/api/extmark.c @@ -85,12 +85,12 @@ const char *describe_ns(NS ns_id) } // Is the Namespace in use? -static bool ns_initialized(uint64_t ns) +static bool ns_initialized(uint32_t ns) { if (ns < 1) { return false; } - return ns < (uint64_t)next_namespace_id; + return ns < (uint32_t)next_namespace_id; } @@ -106,22 +106,55 @@ static Array extmark_to_array(ExtmarkInfo extmark, bool id, bool add_dict) if (add_dict) { Dictionary dict = ARRAY_DICT_INIT; + PUT(dict, "right_gravity", BOOLEAN_OBJ(extmark.right_gravity)); + if (extmark.end_row >= 0) { PUT(dict, "end_row", INTEGER_OBJ(extmark.end_row)); PUT(dict, "end_col", INTEGER_OBJ(extmark.end_col)); + PUT(dict, "end_right_gravity", BOOLEAN_OBJ(extmark.end_right_gravity)); } - if (extmark.decor) { - Decoration *decor = extmark.decor; - if (decor->hl_id) { - String name = cstr_to_string((const char *)syn_id2name(decor->hl_id)); - PUT(dict, "hl_group", STRING_OBJ(name)); + Decoration *decor = &extmark.decor; + if (decor->hl_id) { + String name = cstr_to_string((const char *)syn_id2name(decor->hl_id)); + PUT(dict, "hl_group", STRING_OBJ(name)); + PUT(dict, "hl_eol", BOOLEAN_OBJ(decor->hl_eol)); + } + if (decor->hl_mode) { + PUT(dict, "hl_mode", STRING_OBJ(cstr_to_string(hl_mode_str[decor->hl_mode]))); + } + + if (kv_size(decor->virt_text)) { + Array chunks = ARRAY_DICT_INIT; + for (size_t i = 0; i < decor->virt_text.size; i++) { + Array chunk = ARRAY_DICT_INIT; + VirtTextChunk *vtc = &decor->virt_text.items[i]; + ADD(chunk, STRING_OBJ(cstr_to_string(vtc->text))); + if (vtc->hl_id > 0) { + ADD(chunk, + STRING_OBJ(cstr_to_string((const char *)syn_id2name(vtc->hl_id)))); + } + ADD(chunks, ARRAY_OBJ(chunk)); } - if (kv_size(decor->virt_text)) { + PUT(dict, "virt_text", ARRAY_OBJ(chunks)); + PUT(dict, "virt_text_hide", BOOLEAN_OBJ(decor->virt_text_hide)); + if (decor->virt_text_pos == kVTWinCol) { + PUT(dict, "virt_text_win_col", INTEGER_OBJ(decor->col)); + } + PUT(dict, "virt_text_pos", + STRING_OBJ(cstr_to_string(virt_text_pos_str[decor->virt_text_pos]))); + } + + if (kv_size(decor->virt_lines)) { + Array all_chunks = ARRAY_DICT_INIT; + bool virt_lines_leftcol = false; + for (size_t i = 0; i < decor->virt_lines.size; i++) { Array chunks = ARRAY_DICT_INIT; - for (size_t i = 0; i < decor->virt_text.size; i++) { + VirtText *vt = &decor->virt_lines.items[i].line; + virt_lines_leftcol = decor->virt_lines.items[i].left_col; + for (size_t j = 0; j < vt->size; j++) { Array chunk = ARRAY_DICT_INIT; - VirtTextChunk *vtc = &decor->virt_text.items[i]; + VirtTextChunk *vtc = &vt->items[j]; ADD(chunk, STRING_OBJ(cstr_to_string(vtc->text))); if (vtc->hl_id > 0) { ADD(chunk, @@ -129,9 +162,14 @@ static Array extmark_to_array(ExtmarkInfo extmark, bool id, bool add_dict) } ADD(chunks, ARRAY_OBJ(chunk)); } - PUT(dict, "virt_text", ARRAY_OBJ(chunks)); + ADD(all_chunks, ARRAY_OBJ(chunks)); } + PUT(dict, "virt_lines", ARRAY_OBJ(all_chunks)); + PUT(dict, "virt_lines_above", BOOLEAN_OBJ(decor->virt_lines_above)); + PUT(dict, "virt_lines_leftcol", BOOLEAN_OBJ(virt_lines_leftcol)); + } + if (decor->hl_id || kv_size(decor->virt_text)) { PUT(dict, "priority", INTEGER_OBJ(decor->priority)); } @@ -166,7 +204,7 @@ ArrayOf(Integer) nvim_buf_get_extmark_by_id(Buffer buffer, Integer ns_id, return rv; } - if (!ns_initialized((uint64_t)ns_id)) { + if (!ns_initialized((uint32_t)ns_id)) { api_set_error(err, kErrorTypeValidation, "Invalid ns_id"); return rv; } @@ -191,7 +229,7 @@ ArrayOf(Integer) nvim_buf_get_extmark_by_id(Buffer buffer, Integer ns_id, } - ExtmarkInfo extmark = extmark_from_id(buf, (uint64_t)ns_id, (uint64_t)id); + ExtmarkInfo extmark = extmark_from_id(buf, (uint32_t)ns_id, (uint32_t)id); if (extmark.row < 0) { return rv; } @@ -252,7 +290,7 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, Object e return rv; } - if (!ns_initialized((uint64_t)ns_id)) { + if (!ns_initialized((uint32_t)ns_id)) { api_set_error(err, kErrorTypeValidation, "Invalid ns_id"); return rv; } @@ -310,7 +348,7 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, Object e } - ExtmarkInfoArray marks = extmark_get(buf, (uint64_t)ns_id, l_row, l_col, + ExtmarkInfoArray marks = extmark_get(buf, (uint32_t)ns_id, l_row, l_col, u_row, u_col, (int64_t)limit, reverse); for (size_t i = 0; i < kv_size(marks); i++) { @@ -404,6 +442,10 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, Object e /// for left). Defaults to false. /// - priority: a priority value for the highlight group. For /// example treesitter highlighting uses a value of 100. +/// - strict: boolean that indicates extmark should not be placed +/// if the line or column value is past the end of the +/// buffer or end of the line respectively. Defaults to true. +/// /// @param[out] err Error details, if any /// @return Id of the created/updated extmark Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer col, @@ -417,14 +459,14 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer goto error; } - if (!ns_initialized((uint64_t)ns_id)) { + if (!ns_initialized((uint32_t)ns_id)) { api_set_error(err, kErrorTypeValidation, "Invalid ns_id"); goto error; } - uint64_t id = 0; + uint32_t id = 0; if (opts->id.type == kObjectTypeInteger && opts->id.data.integer > 0) { - id = (uint64_t)opts->id.data.integer; + id = (uint32_t)opts->id.data.integer; } else if (HAS_KEY(opts->id)) { api_set_error(err, kErrorTypeValidation, "id is not a positive integer"); goto error; @@ -441,9 +483,18 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer opts->end_row = opts->end_line; } +#define OPTION_TO_BOOL(target, name, val) \ + target = api_object_to_bool(opts->name, #name, val, err); \ + if (ERROR_SET(err)) { \ + goto error; \ + } + + bool strict = true; + OPTION_TO_BOOL(strict, strict, true); + if (opts->end_row.type == kObjectTypeInteger) { Integer val = opts->end_row.data.integer; - if (val < 0 || val > buf->b_ml.ml_line_count) { + if (val < 0 || (val > buf->b_ml.ml_line_count && strict)) { api_set_error(err, kErrorTypeValidation, "end_row value outside range"); goto error; } else { @@ -512,12 +563,6 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer goto error; } -#define OPTION_TO_BOOL(target, name, val) \ - target = api_object_to_bool(opts->name, #name, val, err); \ - if (ERROR_SET(err)) { \ - goto error; \ - } - OPTION_TO_BOOL(decor.virt_text_hide, virt_text_hide, false); OPTION_TO_BOOL(decor.hl_eol, hl_eol, false); @@ -596,16 +641,30 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer bool ephemeral = false; OPTION_TO_BOOL(ephemeral, ephemeral, false); - if (line < 0 || line > buf->b_ml.ml_line_count) { + if (line < 0) { api_set_error(err, kErrorTypeValidation, "line value outside range"); goto error; + } else if (line > buf->b_ml.ml_line_count) { + if (strict) { + api_set_error(err, kErrorTypeValidation, "line value outside range"); + goto error; + } else { + line = buf->b_ml.ml_line_count; + } } else if (line < buf->b_ml.ml_line_count) { len = ephemeral ? MAXCOL : STRLEN(ml_get_buf(buf, (linenr_T)line+1, false)); } if (col == -1) { col = (Integer)len; - } else if (col < -1 || col > (Integer)len) { + } else if (col > (Integer)len) { + if (strict) { + api_set_error(err, kErrorTypeValidation, "col value outside range"); + goto error; + } else { + col = (Integer)len; + } + } else if (col < -1) { api_set_error(err, kErrorTypeValidation, "col value outside range"); goto error; } @@ -621,27 +680,17 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer line2 = (int)line; } if (col2 > (Integer)len) { - api_set_error(err, kErrorTypeValidation, "end_col value outside range"); - goto error; + if (strict) { + api_set_error(err, kErrorTypeValidation, "end_col value outside range"); + goto error; + } else { + col2 = (int)len; + } } } else if (line2 >= 0) { col2 = 0; } - Decoration *d = NULL; - - if (ephemeral) { - d = &decor; - } else if (kv_size(decor.virt_text) || kv_size(decor.virt_lines) - || decor.priority != DECOR_PRIORITY_BASE - || decor.hl_eol) { - // TODO(bfredl): this is a bit sketchy. eventually we should - // have predefined decorations for both marks/ephemerals - d = xcalloc(1, sizeof(*d)); - *d = decor; - } else if (decor.hl_id) { - d = decor_hl(decor.hl_id); - } // TODO(bfredl): synergize these two branches even more if (ephemeral && decor_state.buf == buf) { @@ -652,12 +701,8 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer goto error; } - extmark_set(buf, (uint64_t)ns_id, &id, (int)line, (colnr_T)col, line2, col2, - d, right_gravity, end_right_gravity, kExtmarkNoUndo); - - if (kv_size(decor.virt_lines)) { - redraw_buf_line_later(buf, MIN(buf->b_ml.ml_line_count, line+1+(decor.virt_lines_above?0:1))); - } + extmark_set(buf, (uint32_t)ns_id, &id, (int)line, (colnr_T)col, line2, col2, + &decor, right_gravity, end_right_gravity, kExtmarkNoUndo); } return (Integer)id; @@ -682,23 +727,23 @@ Boolean nvim_buf_del_extmark(Buffer buffer, Integer ns_id, Integer id, Error *er if (!buf) { return false; } - if (!ns_initialized((uint64_t)ns_id)) { + if (!ns_initialized((uint32_t)ns_id)) { api_set_error(err, kErrorTypeValidation, "Invalid ns_id"); return false; } - return extmark_del(buf, (uint64_t)ns_id, (uint64_t)id); + return extmark_del(buf, (uint32_t)ns_id, (uint32_t)id); } -uint64_t src2ns(Integer *src_id) +uint32_t src2ns(Integer *src_id) { if (*src_id == 0) { *src_id = nvim_create_namespace((String)STRING_INIT); } if (*src_id < 0) { - return UINT64_MAX; + return (((uint32_t)1) << 31) - 1; } else { - return (uint64_t)(*src_id); + return (uint32_t)(*src_id); } } @@ -753,7 +798,7 @@ Integer nvim_buf_add_highlight(Buffer buffer, Integer ns_id, String hl_group, In col_end = MAXCOL; } - uint64_t ns = src2ns(&ns_id); + uint32_t ns = src2ns(&ns_id); if (!(line < buf->b_ml.ml_line_count)) { // safety check, we can't add marks outside the range @@ -773,10 +818,13 @@ Integer nvim_buf_add_highlight(Buffer buffer, Integer ns_id, String hl_group, In end_line++; } + Decoration decor = DECORATION_INIT; + decor.hl_id = hl_id; + extmark_set(buf, ns, NULL, (int)line, (colnr_T)col_start, end_line, (colnr_T)col_end, - decor_hl(hl_id), true, false, kExtmarkNoUndo); + &decor, true, false, kExtmarkNoUndo); return ns_id; } @@ -808,7 +856,7 @@ void nvim_buf_clear_namespace(Buffer buffer, Integer ns_id, Integer line_start, if (line_end < 0 || line_end > MAXLNUM) { line_end = MAXLNUM; } - extmark_clear(buf, (ns_id < 0 ? 0 : (uint64_t)ns_id), + extmark_clear(buf, (ns_id < 0 ? 0 : (uint32_t)ns_id), (int)line_start, 0, (int)line_end-1, MAXCOL); } diff --git a/src/nvim/api/keysets.lua b/src/nvim/api/keysets.lua index f3e7f2f1dc..b05816f8ac 100644 --- a/src/nvim/api/keysets.lua +++ b/src/nvim/api/keysets.lua @@ -21,6 +21,7 @@ return { "virt_lines"; "virt_lines_above"; "virt_lines_leftcol"; + "strict"; }; keymap = { "noremap"; @@ -29,10 +30,25 @@ return { "script"; "expr"; "unique"; + "callback"; + "desc"; }; get_commands = { "builtin"; }; + user_command = { + "addr"; + "bang"; + "bar"; + "complete"; + "count"; + "desc"; + "force"; + "keepscript"; + "nargs"; + "range"; + "register"; + }; float_config = { "row"; "col"; @@ -62,5 +78,62 @@ return { option = { "scope"; }; + highlight = { + "bold"; + "standout"; + "strikethrough"; + "underline"; + "undercurl"; + "italic"; + "reverse"; + "nocombine"; + "default"; + "global"; + "cterm"; + "foreground"; "fg"; + "background"; "bg"; + "ctermfg"; + "ctermbg"; + "special"; "sp"; + "link"; + "fallback"; + "blend"; + "temp"; + }; + highlight_cterm = { + "bold"; + "standout"; + "strikethrough"; + "underline"; + "undercurl"; + "italic"; + "reverse"; + "nocombine"; + }; + -- Autocmds + create_autocmd = { + "buffer"; + "callback"; + "command"; + "desc"; + "group"; + "once"; + "nested"; + "pattern"; + }; + do_autocmd = { + "buffer"; + "group"; + "modeline"; + "pattern"; + }; + get_autocmds = { + "event"; + "group"; + "pattern"; + }; + create_augroup = { + "clear"; + }; } diff --git a/src/nvim/api/private/converter.c b/src/nvim/api/private/converter.c index 36da6c13a9..49e3cf7df7 100644 --- a/src/nvim/api/private/converter.c +++ b/src/nvim/api/private/converter.c @@ -10,6 +10,9 @@ #include "nvim/api/private/helpers.h" #include "nvim/assert.h" #include "nvim/eval/typval.h" +#include "nvim/eval/userfunc.h" +#include "nvim/lua/converter.h" +#include "nvim/lua/executor.h" /// Helper structure for vim_to_object typedef struct { @@ -54,7 +57,7 @@ typedef struct { const size_t len_ = (size_t)(len); \ const blob_T *const blob_ = (blob); \ kvi_push(edata->stack, STRING_OBJ(((String) { \ - .data = len_ != 0 ? xmemdup(blob_->bv_ga.ga_data, len_) : NULL, \ + .data = len_ != 0 ? xmemdupz(blob_->bv_ga.ga_data, len_) : xstrdup(""), \ .size = len_ \ }))); \ } while (0) @@ -228,6 +231,14 @@ static inline void typval_encode_dict_end(EncodedData *const edata) /// @return The converted value Object vim_to_object(typval_T *obj) { + if (obj->v_type == VAR_FUNC) { + ufunc_T *fp = find_func(obj->vval.v_string); + assert(fp != NULL); + if (fp->uf_cb == nlua_CFunction_func_call) { + LuaRef ref = api_new_luaref(((LuaCFunctionState *)fp->uf_cb_state)->lua_callable.func_ref); + return LUAREF_OBJ(ref); + } + } EncodedData edata; kvi_init(edata.stack); const int evo_ret = encode_vim_to_object(&edata, obj, @@ -340,6 +351,16 @@ bool object_to_vim(Object obj, typval_T *tv, Error *err) tv->vval.v_dict = dict; break; } + + case kObjectTypeLuaRef: { + LuaCFunctionState *state = xmalloc(sizeof(LuaCFunctionState)); + state->lua_callable.func_ref = api_new_luaref(obj.data.luaref); + char_u *name = register_cfunc(&nlua_CFunction_func_call, &nlua_CFunction_func_free, state); + tv->v_type = VAR_FUNC; + tv->vval.v_string = vim_strsave(name); + break; + } + default: abort(); } diff --git a/src/nvim/api/private/dispatch.c b/src/nvim/api/private/dispatch.c index 8ab7743e01..f670f06357 100644 --- a/src/nvim/api/private/dispatch.c +++ b/src/nvim/api/private/dispatch.c @@ -6,22 +6,30 @@ #include <msgpack.h> #include <stdbool.h> -#include "nvim/api/buffer.h" #include "nvim/api/deprecated.h" -#include "nvim/api/extmark.h" #include "nvim/api/private/defs.h" #include "nvim/api/private/dispatch.h" #include "nvim/api/private/helpers.h" +#include "nvim/log.h" +#include "nvim/map.h" +#include "nvim/msgpack_rpc/helpers.h" +#include "nvim/vim.h" + +// =========================================================================== +// NEW API FILES MUST GO HERE. +// +// When creating a new API file, you must include it here, +// so that the dispatcher can find the C functions that you are creating! +// =========================================================================== +#include "nvim/api/autocmd.h" +#include "nvim/api/buffer.h" +#include "nvim/api/extmark.h" #include "nvim/api/tabpage.h" #include "nvim/api/ui.h" #include "nvim/api/vim.h" #include "nvim/api/vimscript.h" #include "nvim/api/win_config.h" #include "nvim/api/window.h" -#include "nvim/log.h" -#include "nvim/map.h" -#include "nvim/msgpack_rpc/helpers.h" -#include "nvim/vim.h" static Map(String, MsgpackRpcRequestHandler) methods = MAP_INIT; diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index 9b407eab8b..35e8589255 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -139,10 +139,10 @@ bool try_end(Error *err) got_int = false; } else if (msg_list != NULL && *msg_list != NULL) { int should_free; - char *msg = (char *)get_exception_string(*msg_list, - ET_ERROR, - NULL, - &should_free); + char *msg = get_exception_string(*msg_list, + ET_ERROR, + NULL, + &should_free); api_set_error(err, kErrorTypeException, "%s", msg); free_global_msglist(); @@ -396,19 +396,14 @@ void set_option_to(uint64_t channel_id, void *to, int type, String name, Object stringval = value.data.string.data; } - const sctx_T save_current_sctx = current_sctx; - current_sctx.sc_sid = - channel_id == LUA_INTERNAL_CALL ? SID_LUA : SID_API_CLIENT; - current_sctx.sc_lnum = 0; - current_channel_id = channel_id; + WITH_SCRIPT_CONTEXT(channel_id, { + const int opt_flags = (type == SREQ_WIN && !(flags & SOPT_GLOBAL)) + ? 0 : (type == SREQ_GLOBAL) + ? OPT_GLOBAL : OPT_LOCAL; - const int opt_flags = (type == SREQ_WIN && !(flags & SOPT_GLOBAL)) - ? 0 : (type == SREQ_GLOBAL) - ? OPT_GLOBAL : OPT_LOCAL; - set_option_value_for(name.data, numval, stringval, - opt_flags, type, to, err); - - current_sctx = save_current_sctx; + set_option_value_for(name.data, numval, stringval, + opt_flags, type, to, err); + }); } @@ -591,9 +586,10 @@ Array string_to_array(const String input, bool crlf) /// @param buffer Buffer handle for a specific buffer, or 0 for the current /// buffer, or -1 to signify global behavior ("all buffers") /// @param is_unmap When true, removes the mapping that matches {lhs}. -void modify_keymap(Buffer buffer, bool is_unmap, String mode, String lhs, String rhs, - Dict(keymap) *opts, Error *err) +void modify_keymap(uint64_t channel_id, Buffer buffer, bool is_unmap, String mode, String lhs, + String rhs, Dict(keymap) *opts, Error *err) { + LuaRef lua_funcref = LUA_NOREF; bool global = (buffer == -1); if (global) { buffer = 0; @@ -604,6 +600,12 @@ void modify_keymap(Buffer buffer, bool is_unmap, String mode, String lhs, String return; } + const sctx_T save_current_sctx = api_set_sctx(channel_id); + + if (opts != NULL && opts->callback.type == kObjectTypeLuaRef) { + lua_funcref = opts->callback.data.luaref; + opts->callback.data.luaref = LUA_NOREF; + } MapArguments parsed_args = MAP_ARGUMENTS_INIT; if (opts) { #define KEY_TO_BOOL(name) \ @@ -623,9 +625,13 @@ void modify_keymap(Buffer buffer, bool is_unmap, String mode, String lhs, String parsed_args.buffer = !global; set_maparg_lhs_rhs((char_u *)lhs.data, lhs.size, - (char_u *)rhs.data, rhs.size, + (char_u *)rhs.data, rhs.size, lua_funcref, CPO_TO_CPO_FLAGS, &parsed_args); - + if (opts != NULL && opts->desc.type == kObjectTypeString) { + parsed_args.desc = xstrdup(opts->desc.data.string.data); + } else { + parsed_args.desc = NULL; + } if (parsed_args.lhs_len > MAXMAPLEN) { api_set_error(err, kErrorTypeValidation, "LHS exceeds maximum map length: %s", lhs.data); goto fail_and_free; @@ -658,7 +664,8 @@ void modify_keymap(Buffer buffer, bool is_unmap, String mode, String lhs, String bool is_noremap = parsed_args.noremap; assert(!(is_unmap && is_noremap)); - if (!is_unmap && (parsed_args.rhs_len == 0 && !parsed_args.rhs_is_noop)) { + if (!is_unmap && lua_funcref == LUA_NOREF + && (parsed_args.rhs_len == 0 && !parsed_args.rhs_is_noop)) { if (rhs.size == 0) { // assume that the user wants RHS to be a <Nop> parsed_args.rhs_is_noop = true; } else { @@ -668,9 +675,13 @@ void modify_keymap(Buffer buffer, bool is_unmap, String mode, String lhs, String api_set_error(err, kErrorTypeValidation, "Parsing of nonempty RHS failed: %s", rhs.data); goto fail_and_free; } - } else if (is_unmap && parsed_args.rhs_len) { - api_set_error(err, kErrorTypeValidation, - "Gave nonempty RHS in unmap command: %s", parsed_args.rhs); + } else if (is_unmap && (parsed_args.rhs_len || parsed_args.rhs_lua != LUA_NOREF)) { + if (parsed_args.rhs_len) { + api_set_error(err, kErrorTypeValidation, + "Gave nonempty RHS in unmap command: %s", parsed_args.rhs); + } else { + api_set_error(err, kErrorTypeValidation, "Gave nonempty RHS for unmap"); + } goto fail_and_free; } @@ -700,10 +711,13 @@ void modify_keymap(Buffer buffer, bool is_unmap, String mode, String lhs, String goto fail_and_free; } // switch + parsed_args.rhs_lua = LUA_NOREF; // don't clear ref on success fail_and_free: + current_sctx = save_current_sctx; + NLUA_CLEAR_REF(parsed_args.rhs_lua); xfree(parsed_args.rhs); xfree(parsed_args.orig_rhs); - return; + XFREE_CLEAR(parsed_args.desc); } /// Collects `n` buffer lines into array `l`, optionally replacing newlines @@ -742,6 +756,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) { @@ -961,6 +1021,10 @@ Object copy_object(Object obj) case kObjectTypeDictionary: return DICTIONARY_OBJ(copy_dictionary(obj.data.dictionary)); + + case kObjectTypeLuaRef: + return LUAREF_OBJ(api_new_luaref(obj.data.luaref)); + default: abort(); } @@ -969,18 +1033,16 @@ Object copy_object(Object obj) static void set_option_value_for(char *key, int numval, char *stringval, int opt_flags, int opt_type, void *from, Error *err) { - win_T *save_curwin = NULL; - tabpage_T *save_curtab = NULL; + switchwin_T switchwin; aco_save_T aco; try_start(); switch (opt_type) { case SREQ_WIN: - if (switch_win_noblock(&save_curwin, &save_curtab, (win_T *)from, - win_find_tabpage((win_T *)from), true) + if (switch_win_noblock(&switchwin, (win_T *)from, win_find_tabpage((win_T *)from), true) == FAIL) { - restore_win_noblock(save_curwin, save_curtab, true); + restore_win_noblock(&switchwin, true); if (try_end(err)) { return; } @@ -990,7 +1052,7 @@ static void set_option_value_for(char *key, int numval, char *stringval, int opt return; } set_option_value_err(key, numval, stringval, opt_flags, err); - restore_win_noblock(save_curwin, save_curtab, true); + restore_win_noblock(&switchwin, true); break; case SREQ_BUF: aucmd_prepbuf(&aco, (buf_T *)from); @@ -1048,8 +1110,9 @@ void api_set_error(Error *err, ErrorType errType, const char *format, ...) /// /// @param mode The abbreviation for the mode /// @param buf The buffer to get the mapping array. NULL for global +/// @param from_lua Whether it is called from internal lua api. /// @returns Array of maparg()-like dictionaries describing mappings -ArrayOf(Dictionary) keymap_array(String mode, buf_T *buf) +ArrayOf(Dictionary) keymap_array(String mode, buf_T *buf, bool from_lua) { Array mappings = ARRAY_DICT_INIT; dict_T *const dict = tv_dict_alloc(); @@ -1069,8 +1132,19 @@ ArrayOf(Dictionary) keymap_array(String mode, buf_T *buf) // Check for correct mode if (int_mode & current_maphash->m_mode) { mapblock_fill_dict(dict, current_maphash, buffer_value, false); - ADD(mappings, vim_to_object((typval_T[]) { { .v_type = VAR_DICT, .vval.v_dict = dict } })); - + Object api_dict = vim_to_object((typval_T[]) { { .v_type = VAR_DICT, + .vval.v_dict = dict } }); + if (from_lua) { + Dictionary d = api_dict.data.dictionary; + for (size_t j = 0; j < d.size; j++) { + if (strequal("callback", d.items[j].key.data)) { + d.items[j].value.type = kObjectTypeLuaRef; + d.items[j].value.data.luaref = api_new_luaref((LuaRef)d.items[j].value.data.integer); + break; + } + } + } + ADD(mappings, api_dict); tv_dict_clear(dict); } } @@ -1108,7 +1182,7 @@ bool extmark_get_index_from_obj(buf_T *buf, Integer ns_id, Object obj, int return false; } - ExtmarkInfo extmark = extmark_from_id(buf, (uint64_t)ns_id, (uint64_t)id); + ExtmarkInfo extmark = extmark_from_id(buf, (uint32_t)ns_id, (uint32_t)id); if (extmark.row >= 0) { *row = extmark.row; *col = extmark.col; @@ -1342,3 +1416,230 @@ const char *get_default_stl_hl(win_T *wp) return "StatusLineNC"; } } + +void add_user_command(String name, Object command, Dict(user_command) *opts, int flags, Error *err) +{ + uint32_t argt = 0; + long def = -1; + cmd_addr_T addr_type_arg = ADDR_NONE; + int compl = EXPAND_NOTHING; + char *compl_arg = NULL; + char *rep = NULL; + 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; + } + + if (HAS_KEY(opts->range) && HAS_KEY(opts->count)) { + api_set_error(err, kErrorTypeValidation, "'range' and 'count' are mutually exclusive"); + goto err; + } + + if (opts->nargs.type == kObjectTypeInteger) { + switch (opts->nargs.data.integer) { + case 0: + // Default value, nothing to do + break; + case 1: + argt |= EX_EXTRA | EX_NOSPC | EX_NEEDARG; + break; + default: + api_set_error(err, kErrorTypeValidation, "Invalid value for 'nargs'"); + goto err; + } + } else if (opts->nargs.type == kObjectTypeString) { + if (opts->nargs.data.string.size > 1) { + api_set_error(err, kErrorTypeValidation, "Invalid value for 'nargs'"); + goto err; + } + + switch (opts->nargs.data.string.data[0]) { + case '*': + argt |= EX_EXTRA; + break; + case '?': + argt |= EX_EXTRA | EX_NOSPC; + break; + case '+': + argt |= EX_EXTRA | EX_NEEDARG; + break; + default: + api_set_error(err, kErrorTypeValidation, "Invalid value for 'nargs'"); + goto err; + } + } else if (HAS_KEY(opts->nargs)) { + api_set_error(err, kErrorTypeValidation, "Invalid value for 'nargs'"); + goto err; + } + + if (HAS_KEY(opts->complete) && !argt) { + api_set_error(err, kErrorTypeValidation, "'complete' used without 'nargs'"); + goto err; + } + + if (opts->range.type == kObjectTypeBoolean) { + if (opts->range.data.boolean) { + argt |= EX_RANGE; + addr_type_arg = ADDR_LINES; + } + } else if (opts->range.type == kObjectTypeString) { + if (opts->range.data.string.data[0] == '%' && opts->range.data.string.size == 1) { + argt |= EX_RANGE | EX_DFLALL; + addr_type_arg = ADDR_LINES; + } else { + api_set_error(err, kErrorTypeValidation, "Invalid value for 'range'"); + goto err; + } + } else if (opts->range.type == kObjectTypeInteger) { + argt |= EX_RANGE | EX_ZEROR; + def = opts->range.data.integer; + addr_type_arg = ADDR_LINES; + } else if (HAS_KEY(opts->range)) { + api_set_error(err, kErrorTypeValidation, "Invalid value for 'range'"); + goto err; + } + + if (opts->count.type == kObjectTypeBoolean) { + if (opts->count.data.boolean) { + argt |= EX_COUNT | EX_ZEROR | EX_RANGE; + addr_type_arg = ADDR_OTHER; + def = 0; + } + } else if (opts->count.type == kObjectTypeInteger) { + argt |= EX_COUNT | EX_ZEROR | EX_RANGE; + addr_type_arg = ADDR_OTHER; + def = opts->count.data.integer; + } else if (HAS_KEY(opts->count)) { + api_set_error(err, kErrorTypeValidation, "Invalid value for 'count'"); + goto err; + } + + if (opts->addr.type == kObjectTypeString) { + if (parse_addr_type_arg((char_u *)opts->addr.data.string.data, (int)opts->addr.data.string.size, + &addr_type_arg) != OK) { + api_set_error(err, kErrorTypeValidation, "Invalid value for 'addr'"); + goto err; + } + + if (addr_type_arg != ADDR_LINES) { + argt |= EX_ZEROR; + } + } else if (HAS_KEY(opts->addr)) { + api_set_error(err, kErrorTypeValidation, "Invalid value for 'addr'"); + goto err; + } + + if (api_object_to_bool(opts->bang, "bang", false, err)) { + argt |= EX_BANG; + } else if (ERROR_SET(err)) { + goto err; + } + + if (api_object_to_bool(opts->bar, "bar", false, err)) { + argt |= EX_TRLBAR; + } else if (ERROR_SET(err)) { + goto err; + } + + + if (api_object_to_bool(opts->register_, "register", false, err)) { + argt |= EX_REGSTR; + } else if (ERROR_SET(err)) { + goto err; + } + + if (api_object_to_bool(opts->keepscript, "keepscript", false, err)) { + argt |= EX_KEEPSCRIPT; + } else if (ERROR_SET(err)) { + goto err; + } + + bool force = api_object_to_bool(opts->force, "force", true, err); + if (ERROR_SET(err)) { + goto err; + } + + if (opts->complete.type == kObjectTypeLuaRef) { + compl = EXPAND_USER_LUA; + compl_luaref = api_new_luaref(opts->complete.data.luaref); + } else if (opts->complete.type == kObjectTypeString) { + if (parse_compl_arg((char_u *)opts->complete.data.string.data, + (int)opts->complete.data.string.size, &compl, &argt, + (char_u **)&compl_arg) != OK) { + api_set_error(err, kErrorTypeValidation, "Invalid value for 'complete'"); + goto err; + } + } else if (HAS_KEY(opts->complete)) { + api_set_error(err, kErrorTypeValidation, "Invalid value for 'complete'"); + goto err; + } + + switch (command.type) { + case kObjectTypeLuaRef: + luaref = api_new_luaref(command.data.luaref); + if (opts->desc.type == kObjectTypeString) { + rep = opts->desc.data.string.data; + } else { + snprintf((char *)IObuff, IOSIZE, "<Lua function %d>", luaref); + rep = (char *)IObuff; + } + break; + case kObjectTypeString: + rep = command.data.string.data; + break; + default: + api_set_error(err, kErrorTypeValidation, "'command' must be a string or Lua function"); + goto err; + } + + if (uc_add_command((char_u *)name.data, name.size, (char_u *)rep, argt, def, flags, + compl, (char_u *)compl_arg, compl_luaref, addr_type_arg, luaref, + force) != OK) { + api_set_error(err, kErrorTypeException, "Failed to create user command"); + goto err; + } + + return; + +err: + NLUA_CLEAR_REF(luaref); + NLUA_CLEAR_REF(compl_luaref); +} + +int find_sid(uint64_t channel_id) +{ + switch (channel_id) { + case VIML_INTERNAL_CALL: + // TODO(autocmd): Figure out what this should be + // return SID_API_CLIENT; + case LUA_INTERNAL_CALL: + return SID_LUA; + default: + return SID_API_CLIENT; + } +} + +/// Sets sctx for API calls. +/// +/// @param channel_id api clients id. Used to determine if it's a internal +/// call or a rpc call. +/// @return returns previous value of current_sctx. To be used +/// to be used for restoring sctx to previous state. +sctx_T api_set_sctx(uint64_t channel_id) +{ + sctx_T old_current_sctx = current_sctx; + if (channel_id != VIML_INTERNAL_CALL) { + current_sctx.sc_sid = + channel_id == LUA_INTERNAL_CALL ? SID_LUA : SID_API_CLIENT; + current_sctx.sc_lnum = 0; + } + return old_current_sctx; +} diff --git a/src/nvim/api/private/helpers.h b/src/nvim/api/private/helpers.h index 6d0aec9c90..bc7c2e6a60 100644 --- a/src/nvim/api/private/helpers.h +++ b/src/nvim/api/private/helpers.h @@ -138,10 +138,29 @@ typedef struct { msg_list = saved_msg_list; /* Restore the exception context. */ \ } while (0) +// Useful macro for executing some `code` for each item in an array. +#define FOREACH_ITEM(a, __foreach_item, code) \ + for (size_t __foreach_i = 0; __foreach_i < (a).size; __foreach_i++) { \ + Object __foreach_item = (a).items[__foreach_i]; \ + code; \ + } + + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "api/private/helpers.h.generated.h" # include "keysets.h.generated.h" #endif +#define WITH_SCRIPT_CONTEXT(channel_id, code) \ + do { \ + const sctx_T save_current_sctx = current_sctx; \ + current_sctx.sc_sid = \ + (channel_id) == LUA_INTERNAL_CALL ? SID_LUA : SID_API_CLIENT; \ + current_sctx.sc_lnum = 0; \ + current_channel_id = channel_id; \ + code; \ + current_sctx = save_current_sctx; \ + } while (0); + #endif // NVIM_API_PRIVATE_HELPERS_H diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index f81cdf9deb..9d0b096a36 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -19,6 +19,7 @@ #include "nvim/ascii.h" #include "nvim/buffer.h" #include "nvim/buffer_defs.h" +#include "nvim/charset.h" #include "nvim/context.h" #include "nvim/decoration.h" #include "nvim/edit.h" @@ -30,7 +31,9 @@ #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" #include "nvim/mark.h" #include "nvim/memline.h" @@ -121,7 +124,13 @@ Dictionary nvim__get_hl_defs(Integer ns_id, Error *err) /// Set a highlight group. /// -/// @param ns_id number of namespace for this highlight +/// 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. /// @param name highlight group name, like ErrorMsg /// @param val highlight definition map, like |nvim_get_hl_by_name|. /// in addition the following keys are also recognized: @@ -135,9 +144,8 @@ Dictionary nvim__get_hl_defs(Integer ns_id, Error *err) /// same as attributes of gui color /// @param[out] err Error details, if any /// -/// TODO: ns_id = 0, should modify :highlight namespace -/// TODO val should take update vs reset flag -void nvim_set_hl(Integer ns_id, String name, Dictionary val, Error *err) +// TODO(bfredl): val should take update vs reset flag +void nvim_set_hl(Integer ns_id, String name, Dict(highlight) *val, Error *err) FUNC_API_SINCE(7) { int hl_id = syn_check_group(name.data, (int)name.size); @@ -145,7 +153,7 @@ void nvim_set_hl(Integer ns_id, String name, Dictionary val, Error *err) HlAttrs attrs = dict2hlattrs(val, true, &link_id, err); if (!ERROR_SET(err)) { - ns_hl_def((NS)ns_id, hl_id, attrs, link_id); + ns_hl_def((NS)ns_id, hl_id, attrs, link_id, val); } } @@ -169,7 +177,7 @@ void nvim__set_hl_ns(Integer ns_id, Error *err) // event path for redraws caused by "fast" events. This could tie in with // better throttling of async events causing redraws, such as non-batched // nvim_buf_set_extmark calls from async contexts. - if (!provider_active && !ns_hl_changed) { + if (!provider_active && !ns_hl_changed && must_redraw < NOT_VALID) { multiqueue_put(main_loop.events, on_redraw_event, 0); } ns_hl_changed = true; @@ -187,21 +195,23 @@ static void on_redraw_event(void **argv) /// 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 pass the result to +/// with escape_ks=false) to replace |keycodes|, then pass the result to /// nvim_feedkeys(). /// /// Example: /// <pre> /// :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) /// </pre> /// /// @param keys to be typed /// @param mode behavior flags, see |feedkeys()| -/// @param escape_csi If true, escape K_SPECIAL/CSI bytes in `keys` +/// @param 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 feedkeys() -/// @see vim_strsave_escape_csi -void nvim_feedkeys(String keys, String mode, Boolean escape_csi) +/// @see vim_strsave_escape_ks +void nvim_feedkeys(String keys, String mode, Boolean escape_ks) FUNC_API_SINCE(1) { bool remap = true; @@ -232,10 +242,10 @@ void nvim_feedkeys(String keys, String mode, Boolean escape_csi) } char *keys_esc; - if (escape_csi) { - // Need to escape K_SPECIAL and CSI before putting the string in the + if (escape_ks) { + // Need to escape K_SPECIAL before putting the string in the // typeahead buffer. - keys_esc = (char *)vim_strsave_escape_csi((char_u *)keys.data); + keys_esc = (char *)vim_strsave_escape_ks((char_u *)keys.data); } else { keys_esc = keys.data; } @@ -245,7 +255,7 @@ void nvim_feedkeys(String keys, String mode, Boolean escape_csi) typebuf_was_filled = true; } - if (escape_csi) { + if (escape_ks) { xfree(keys_esc); } @@ -383,7 +393,7 @@ error: /// @param str String to be converted. /// @param from_part Legacy Vim parameter. Usually true. /// @param do_lt Also translate <lt>. Ignored if `special` is false. -/// @param special Replace |keycodes|, e.g. <CR> becomes a "\n" char. +/// @param special Replace |keycodes|, e.g. <CR> becomes a "\r" char. /// @see replace_termcodes /// @see cpoptions String nvim_replace_termcodes(String str, Boolean from_part, Boolean do_lt, Boolean special) @@ -491,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; } @@ -540,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); } @@ -596,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. @@ -662,7 +687,7 @@ Object nvim_get_option(String name, Error *err) /// /// @param name Option name /// @param opts Optional parameters -/// - scope: One of 'global' or 'local'. Analagous to +/// - scope: One of 'global' or 'local'. Analogous to /// |:setglobal| and |:setlocal|, respectively. /// @param[out] err Error details, if any /// @return Option value @@ -724,7 +749,7 @@ end: /// @param name Option name /// @param value New option value /// @param opts Optional parameters -/// - scope: One of 'global' or 'local'. Analagous to +/// - scope: One of 'global' or 'local'. Analogous to /// |:setglobal| and |:setlocal|, respectively. /// @param[out] err Error details, if any void nvim_set_option_value(String name, Object value, Dict(option) *opts, Error *err) @@ -1163,7 +1188,7 @@ static void term_close(void *data) /// Send data to channel `id`. For a job, it writes it to the /// stdin of the process. For the stdio channel |channel-stdio|, /// it writes to Nvim's stdout. For an internal terminal instance -/// (|nvim_open_term()|) it writes directly to terimal output. +/// (|nvim_open_term()|) it writes directly to terminal output. /// See |channel-bytes| for more information. /// /// This function writes raw data, not RPC messages. If the channel @@ -1538,10 +1563,10 @@ Dictionary nvim_get_mode(void) /// @param mode Mode short-name ("n", "i", "v", ...) /// @returns Array of maparg()-like dictionaries describing mappings. /// The "buffer" key is always zero. -ArrayOf(Dictionary) nvim_get_keymap(String mode) +ArrayOf(Dictionary) nvim_get_keymap(uint64_t channel_id, String mode) FUNC_API_SINCE(3) { - return keymap_array(mode, NULL); + return keymap_array(mode, NULL, channel_id == LUA_INTERNAL_CALL); } /// Sets a global |mapping| for the given mode. @@ -1561,18 +1586,23 @@ ArrayOf(Dictionary) nvim_get_keymap(String mode) /// nmap <nowait> <Space><NL> <Nop> /// </pre> /// +/// @param channel_id /// @param mode Mode short-name (map command prefix: "n", "i", "v", "x", …) /// or "!" for |:map!|, or empty string for |:map|. /// @param lhs Left-hand-side |{lhs}| of the mapping. /// @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|. +/// as keys excluding |<buffer>| but 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. /// Values are Booleans. Unknown key is an error. /// @param[out] err Error details, if any. -void nvim_set_keymap(String mode, String lhs, String rhs, Dict(keymap) *opts, Error *err) +void nvim_set_keymap(uint64_t channel_id, String mode, String lhs, String rhs, Dict(keymap) *opts, + Error *err) FUNC_API_SINCE(6) { - modify_keymap(-1, false, mode, lhs, rhs, opts, err); + modify_keymap(channel_id, -1, false, mode, lhs, rhs, opts, err); } /// Unmaps a global |mapping| for the given mode. @@ -1580,10 +1610,10 @@ void nvim_set_keymap(String mode, String lhs, String rhs, Dict(keymap) *opts, Er /// To unmap a buffer-local mapping, use |nvim_buf_del_keymap()|. /// /// @see |nvim_set_keymap()| -void nvim_del_keymap(String mode, String lhs, Error *err) +void nvim_del_keymap(uint64_t channel_id, String mode, String lhs, Error *err) FUNC_API_SINCE(6) { - nvim_buf_del_keymap(-1, mode, lhs, err); + nvim_buf_del_keymap(channel_id, -1, mode, lhs, err); } /// Gets a map of global (non-buffer-local) Ex commands. @@ -1741,7 +1771,7 @@ Array nvim_list_chans(void) /// 1. To perform several requests from an async context atomically, i.e. /// without interleaving redraws, RPC requests from other clients, or user /// interactions (however API methods may trigger autocommands or event -/// processing which have such side-effects, e.g. |:sleep| may wake timers). +/// processing which have such side effects, e.g. |:sleep| may wake timers). /// 2. To minimize RPC overhead (roundtrips) of a sequence of many requests. /// /// @param channel_id @@ -1927,7 +1957,7 @@ Dictionary nvim__stats(void) Dictionary rv = ARRAY_DICT_INIT; PUT(rv, "fsync", INTEGER_OBJ(g_stats.fsync)); PUT(rv, "redraw", INTEGER_OBJ(g_stats.redraw)); - PUT(rv, "lua_refcount", INTEGER_OBJ(nlua_refcount)); + PUT(rv, "lua_refcount", INTEGER_OBJ(nlua_get_global_ref_count())); return rv; } @@ -2101,7 +2131,7 @@ void nvim__screenshot(String path) } -/// Deletes a uppercase/file named mark. See |mark-motions|. +/// Deletes an uppercase/file named mark. See |mark-motions|. /// /// @note fails with error if a lowercase or buffer local named mark is used. /// @param name Mark name @@ -2231,7 +2261,7 @@ Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Error * Dictionary result = ARRAY_DICT_INIT; int maxwidth; - char fillchar = 0; + int fillchar = 0; Window window = 0; bool use_tabline = false; bool highlights = false; @@ -2246,12 +2276,12 @@ Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Error * } if (HAS_KEY(opts->fillchar)) { - if (opts->fillchar.type != kObjectTypeString || opts->fillchar.data.string.size > 1) { - api_set_error(err, kErrorTypeValidation, "fillchar must be an ASCII character"); + if (opts->fillchar.type != kObjectTypeString || opts->fillchar.data.string.size == 0 + || char2cells(fillchar = utf_ptr2char((char_u *)opts->fillchar.data.string.data)) != 1 + || (size_t)utf_char2len(fillchar) != opts->fillchar.data.string.size) { + api_set_error(err, kErrorTypeValidation, "fillchar must be a single-width character"); return result; } - - fillchar = opts->fillchar.data.string.data[0]; } if (HAS_KEY(opts->highlights)) { @@ -2278,11 +2308,16 @@ Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Error * fillchar = ' '; } else { wp = find_window_by_handle(window, err); + + if (wp == NULL) { + api_set_error(err, kErrorTypeException, "unknown winid %d", window); + return result; + } ewp = wp; if (fillchar == 0) { int attr; - fillchar = (char)fillchar_status(&attr, wp); + fillchar = fillchar_status(&attr, wp); } } @@ -2310,7 +2345,7 @@ Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Error * sizeof(buf), (char_u *)str.data, false, - (char_u)fillchar, + fillchar, maxwidth, hltab_ptr, NULL); @@ -2363,3 +2398,55 @@ Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Error * return result; } + +/// Create a new user command |user-commands| +/// +/// {name} is the name of the new command. The name must begin with an uppercase letter. +/// +/// {command} is the replacement text or Lua function to execute. +/// +/// Example: +/// <pre> +/// :call nvim_add_user_command('SayHello', 'echo "Hello world!"', {}) +/// :SayHello +/// Hello world! +/// </pre> +/// +/// @param name Name of the new user command. Must begin with an uppercase letter. +/// @param command Replacement command to execute when this user command is executed. When called +/// from Lua, the command can also be a Lua function. The function is called with a +/// single table argument that contains the following keys: +/// - args: (string) The args passed to the command, if any |<args>| +/// - fargs: (table) The args split by unescaped whitespace (when more than one +/// argument is allowed), if any |<f-args>| +/// - bang: (boolean) "true" if the command was executed with a ! modifier |<bang>| +/// - line1: (number) The starting line of the command range |<line1>| +/// - line2: (number) The final line of the command range |<line2>| +/// - range: (number) The number of items in the command range: 0, 1, or 2 |<range>| +/// - count: (number) Any count supplied |<count>| +/// - reg: (string) The optional register, if specified |<reg>| +/// - mods: (string) Command modifiers, if any |<mods>| +/// @param opts Optional command attributes. See |command-attributes| for more details. To use +/// boolean attributes (such as |:command-bang| or |:command-bar|) set the value to +/// "true". In addition to the string options listed in |:command-complete|, the +/// "complete" key also accepts a Lua function which works like the "customlist" +/// completion mode |:command-completion-customlist|. Additional parameters: +/// - desc: (string) Used for listing the command when a Lua function is used for +/// {command}. +/// - force: (boolean, default true) Override any previous definition. +/// @param[out] err Error details, if any. +void nvim_add_user_command(String name, Object command, Dict(user_command) *opts, Error *err) + FUNC_API_SINCE(9) +{ + add_user_command(name, command, opts, 0, err); +} + +/// Delete a user-defined command. +/// +/// @param name Name of the command to delete. +/// @param[out] err Error details, if any. +void nvim_del_user_command(String name, Error *err) + FUNC_API_SINCE(9) +{ + nvim_buf_del_user_command(-1, name, err); +} diff --git a/src/nvim/api/vimscript.c b/src/nvim/api/vimscript.c index 640144b234..4123674fe7 100644 --- a/src/nvim/api/vimscript.c +++ b/src/nvim/api/vimscript.c @@ -37,7 +37,7 @@ /// @param[out] err Error details (Vim error), if any /// @return Output (non-error, non-shell |:!|) if `output` is true, /// else empty string. -String nvim_exec(String src, Boolean output, Error *err) +String nvim_exec(uint64_t channel_id, String src, Boolean output, Error *err) FUNC_API_SINCE(7) { const int save_msg_silent = msg_silent; @@ -52,11 +52,16 @@ String nvim_exec(String src, Boolean output, Error *err) if (output) { msg_silent++; } + + const sctx_T save_current_sctx = api_set_sctx(channel_id); + do_source_str(src.data, "nvim_exec()"); if (output) { capture_ga = save_capture_ga; msg_silent = save_msg_silent; } + + current_sctx = save_current_sctx; try_end(err); if (ERROR_SET(err)) { diff --git a/src/nvim/api/window.c b/src/nvim/api/window.c index 6e68c057dc..9c473ff724 100644 --- a/src/nvim/api/window.c +++ b/src/nvim/api/window.c @@ -39,7 +39,7 @@ Buffer nvim_win_get_buf(Window window, Error *err) return win->w_buffer->handle; } -/// Sets the current buffer in a window, without side-effects +/// Sets the current buffer in a window, without side effects /// /// @param window Window handle, or 0 for current window /// @param buffer Buffer handle @@ -71,6 +71,7 @@ ArrayOf(Integer, 2) nvim_win_get_cursor(Window window, Error *err) } /// Sets the (1,0)-indexed cursor position in the window. |api-indexing| +/// Unlike |win_execute()| this scrolls the window. /// /// @param window Window handle, or 0 for current window /// @param pos (row, col) tuple representing the new position @@ -118,6 +119,8 @@ void nvim_win_set_cursor(Window window, ArrayOf(Integer, 2) pos, Error *err) update_topline_win(win); redraw_later(win, VALID); + redraw_for_cursorline(win); + win->w_redr_status = true; } /// Gets the window height @@ -395,7 +398,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); } @@ -455,17 +458,12 @@ Object nvim_win_call(Window window, LuaRef fun, Error *err) } tabpage_T *tabpage = win_find_tabpage(win); - win_T *save_curwin; - tabpage_T *save_curtab; - try_start(); Object res = OBJECT_INIT; - if (switch_win_noblock(&save_curwin, &save_curtab, win, tabpage, true) == - OK) { + WIN_EXECUTE(win, tabpage, { Array args = ARRAY_DICT_INIT; res = nlua_call_ref(fun, NULL, args, true, err); - } - restore_win_noblock(save_curwin, save_curtab, true); + }); try_end(err); return res; } diff --git a/src/nvim/aucmd.c b/src/nvim/aucmd.c deleted file mode 100644 index d7f73fa4a1..0000000000 --- a/src/nvim/aucmd.c +++ /dev/null @@ -1,123 +0,0 @@ -// This is an open source non-commercial project. Dear PVS-Studio, please check -// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com - -#include "nvim/aucmd.h" -#include "nvim/buffer.h" -#include "nvim/eval.h" -#include "nvim/ex_docmd.h" -#include "nvim/ex_getln.h" -#include "nvim/fileio.h" -#include "nvim/main.h" -#include "nvim/os/os.h" -#include "nvim/ui.h" -#include "nvim/vim.h" - -#ifdef INCLUDE_GENERATED_DECLARATIONS -# include "aucmd.c.generated.h" -#endif - -void do_autocmd_uienter(uint64_t chanid, bool attached) -{ - static bool recursive = false; - - if (recursive) { - return; // disallow recursion - } - recursive = true; - - save_v_event_T save_v_event; - dict_T *dict = get_v_event(&save_v_event); - assert(chanid < VARNUMBER_MAX); - tv_dict_add_nr(dict, S_LEN("chan"), (varnumber_T)chanid); - tv_dict_set_keys_readonly(dict); - apply_autocmds(attached ? EVENT_UIENTER : EVENT_UILEAVE, - NULL, NULL, false, curbuf); - restore_v_event(dict, &save_v_event); - - recursive = false; -} - -void init_default_autocmds(void) -{ - // open terminals when opening files that start with term:// -#define PROTO "term://" - do_cmdline_cmd("augroup nvim_terminal"); - do_cmdline_cmd("autocmd BufReadCmd " PROTO "* ++nested " - "if !exists('b:term_title')|call termopen(" - // Capture the command string - "matchstr(expand(\"<amatch>\"), " - "'\\c\\m" PROTO "\\%(.\\{-}//\\%(\\d\\+:\\)\\?\\)\\?\\zs.*'), " - // capture the working directory - "{'cwd': expand(get(matchlist(expand(\"<amatch>\"), " - "'\\c\\m" PROTO "\\(.\\{-}\\)//'), 1, ''))})" - "|endif"); - do_cmdline_cmd("augroup END"); -#undef PROTO - - // limit syntax synchronization in the command window - do_cmdline_cmd("augroup nvim_cmdwin"); - do_cmdline_cmd("autocmd! CmdwinEnter [:>] syntax sync minlines=1 maxlines=1"); - do_cmdline_cmd("augroup END"); -} - -static void focusgained_event(void **argv) -{ - bool *gainedp = argv[0]; - do_autocmd_focusgained(*gainedp); - xfree(gainedp); -} -void aucmd_schedule_focusgained(bool gained) -{ - bool *gainedp = xmalloc(sizeof(*gainedp)); - *gainedp = gained; - loop_schedule_deferred(&main_loop, - event_create(focusgained_event, 1, gainedp)); -} - -static void do_autocmd_focusgained(bool gained) -{ - static bool recursive = false; - static Timestamp last_time = (time_t)0; - bool need_redraw = false; - - if (recursive) { - return; // disallow recursion - } - recursive = true; - need_redraw |= apply_autocmds((gained ? EVENT_FOCUSGAINED : EVENT_FOCUSLOST), - NULL, NULL, false, curbuf); - - // When activated: Check if any file was modified outside of Vim. - // Only do this when not done within the last two seconds as: - // 1. Some filesystems have modification time granularity in seconds. Fat32 - // has a granularity of 2 seconds. - // 2. We could get multiple notifications in a row. - if (gained && last_time + (Timestamp)2000 < os_now()) { - need_redraw = check_timestamps(true); - last_time = os_now(); - } - - if (need_redraw) { - // Something was executed, make sure the cursor is put back where it - // belongs. - need_wait_return = false; - - if (State & CMDLINE) { - redrawcmdline(); - } else if ((State & NORMAL) || (State & INSERT)) { - if (must_redraw != 0) { - update_screen(0); - } - - setcursor(); - } - - ui_flush(); - } - - if (need_maketitle) { - maketitle(); - } - - recursive = false; -} diff --git a/src/nvim/aucmd.h b/src/nvim/aucmd.h deleted file mode 100644 index 9a4dd79a78..0000000000 --- a/src/nvim/aucmd.h +++ /dev/null @@ -1,11 +0,0 @@ -#ifndef NVIM_AUCMD_H -#define NVIM_AUCMD_H - -#include <stdint.h> - -#ifdef INCLUDE_GENERATED_DECLARATIONS -# include "aucmd.h.generated.h" -#endif - -#endif // NVIM_AUCMD_H - 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 463bd5e0e6..66222f6a6a 100644 --- a/src/nvim/autocmd.c +++ b/src/nvim/autocmd.c @@ -2,7 +2,9 @@ // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com // autocmd.c: Autocommand related functions +#include <signal.h> +#include "lauxlib.h" #include "nvim/api/private/helpers.h" #include "nvim/ascii.h" #include "nvim/autocmd.h" @@ -13,8 +15,11 @@ #include "nvim/eval.h" #include "nvim/eval/userfunc.h" #include "nvim/ex_docmd.h" +#include "nvim/ex_getln.h" #include "nvim/fileio.h" #include "nvim/getchar.h" +#include "nvim/lua/executor.h" +#include "nvim/map.h" #include "nvim/option.h" #include "nvim/os/input.h" #include "nvim/regexp.h" @@ -28,6 +33,14 @@ # include "autocmd.c.generated.h" #endif +// Naming Conventions: +// - general autocmd behavior start with au_ +// - AutoCmd start with aucmd_ +// - Autocmd.exec stat with aucmd_exec +// - AutoPat start with aupat_ +// - Groups start with augroup_ +// - Events start with event_ + // // The autocommands are stored in a list for each event. // Autocommands for the same pattern, that are consecutive, are joined @@ -67,21 +80,20 @@ // Code for automatic commands. static AutoPatCmd *active_apc_list = NULL; // stack of active autocommands -/// List of autocmd group names -static garray_T augroups = { 0, 0, sizeof(char_u *), 10, NULL }; -#define AUGROUP_NAME(i) (((char **)augroups.ga_data)[i]) -#define BUFLOCAL_PAT_LEN 25 +// ID for associating autocmds created via nvim_create_autocmd +// Used to delete autocmds from nvim_del_autocmd +static int next_augroup_id = 1; // use get_deleted_augroup() to get this static const char *deleted_augroup = NULL; -// The ID of the current group. Group 0 is the default one. +// The ID of the current group. static int current_augroup = AUGROUP_DEFAULT; -static int au_need_clean = false; // need to delete marked patterns +// Whether we need to delete marked patterns. +// While deleting autocmds, they aren't actually remover, just marked. +static int au_need_clean = false; -static event_T last_event; -static int last_group; static int autocmd_blocked = 0; // block all autocmds static bool autocmd_nested = false; @@ -89,6 +101,22 @@ static bool autocmd_include_groups = false; static char_u *old_termresponse = NULL; +/// Iterates over all the AutoPats for a particular event +#define FOR_ALL_AUPATS_IN_EVENT(event, ap) \ + for (AutoPat *ap = first_autopat[event]; ap != NULL; ap = ap->next) // NOLINT + +// Map of autocmd group names. +// name -> ID +static Map(String, int) augroup_map = MAP_INIT; + +static void augroup_map_del(char *name) +{ + String key = map_key(String, int)(&augroup_map, cstr_as_string(name)); + map_del(String, int)(&augroup_map, key); + api_free_string(key); +} + + static inline const char *get_deleted_augroup(void) FUNC_ATTR_ALWAYS_INLINE { if (deleted_augroup == NULL) { @@ -98,7 +126,7 @@ static inline const char *get_deleted_augroup(void) FUNC_ATTR_ALWAYS_INLINE } // Show the autocommands for one AutoPat. -static void show_autocmd(AutoPat *ap, event_T event) +static void aupat_show(AutoPat *ap) { AutoCmd *ac; @@ -107,39 +135,21 @@ static void show_autocmd(AutoPat *ap, event_T event) if (got_int) { return; } + // pattern has been removed if (ap->pat == NULL) { return; } - msg_putchar('\n'); - if (got_int) { - return; - } - if (event != last_event || ap->group != last_group) { - if (ap->group != AUGROUP_DEFAULT) { - if (AUGROUP_NAME(ap->group) == NULL) { - msg_puts_attr(get_deleted_augroup(), HL_ATTR(HLF_E)); - } else { - msg_puts_attr(AUGROUP_NAME(ap->group), HL_ATTR(HLF_T)); - } - msg_puts(" "); - } - msg_puts_attr(event_nr2name(event), HL_ATTR(HLF_T)); - last_event = event; - last_group = ap->group; - msg_putchar('\n'); - if (got_int) { - return; - } - } msg_col = 4; msg_outtrans(ap->pat); for (ac = ap->cmds; ac != NULL; ac = ac->next) { - if (ac->cmd == NULL) { // skip removed commands + // skip removed commands + if (aucmd_exec_is_deleted(ac->exec)) { continue; } + if (msg_col >= 14) { msg_putchar('\n'); } @@ -147,7 +157,7 @@ static void show_autocmd(AutoPat *ap, event_T event) if (got_int) { return; } - msg_outtrans(ac->cmd); + msg_outtrans((char_u *)aucmd_exec_to_string(ac, ac->exec)); if (p_verbose > 0) { last_set_msg(ac->script_ctx); } @@ -163,27 +173,96 @@ static void show_autocmd(AutoPat *ap, event_T event) } } +static void au_show_for_all_events(int group) +{ + FOR_ALL_AUEVENTS(event) { + au_show_for_event(group, event); + } +} + +static void au_show_for_event(int group, event_T event) +{ + // Return early if there are no autocmds for this event + if (au_event_is_empty(event)) { + return; + } + + int previous_group = AUGROUP_ERROR; + FOR_ALL_AUPATS_IN_EVENT(event, ap) { + if (group != AUGROUP_ALL && group != ap->group) { + continue; + } + + char *name = augroup_name(ap->group); + + msg_putchar('\n'); + // When switching groups, we need to show the new group information. + if (ap->group != previous_group) { + // show the group name, if it's not the default group + if (ap->group != AUGROUP_DEFAULT) { + if (name == NULL) { + msg_puts_attr(get_deleted_augroup(), HL_ATTR(HLF_E)); + } else { + msg_puts_attr(name, HL_ATTR(HLF_T)); + } + msg_puts(" "); + } + + // show the event name + msg_puts_attr(event_nr2name(event), HL_ATTR(HLF_T)); + msg_putchar('\n'); + } + + if (got_int) { + return; + } + + aupat_show(ap); + + previous_group = ap->group; + } +} + // Mark an autocommand handler for deletion. -static void au_remove_pat(AutoPat *ap) +static void aupat_del(AutoPat *ap) { XFREE_CLEAR(ap->pat); ap->buflocal_nr = -1; au_need_clean = true; } +void aupat_del_for_event_and_group(event_T event, int group) +{ + FOR_ALL_AUPATS_IN_EVENT(event, ap) { + if (ap->group == group) { + aupat_del(ap); + } + } + + au_need_clean = true; + au_cleanup(); // may really delete removed patterns/commands now +} + // Mark all commands for a pattern for deletion. -static void au_remove_cmds(AutoPat *ap) +static void aupat_remove_cmds(AutoPat *ap) { for (AutoCmd *ac = ap->cmds; ac != NULL; ac = ac->next) { - XFREE_CLEAR(ac->cmd); + aucmd_exec_free(&ac->exec); + + if (ac->desc != NULL) { + XFREE_CLEAR(ac->desc); + } } au_need_clean = true; } // Delete one command from an autocmd pattern. -static void au_del_cmd(AutoCmd *ac) +static void aucmd_del(AutoCmd *ac) { - XFREE_CLEAR(ac->cmd); + aucmd_exec_free(&ac->exec); + if (ac->desc != NULL) { + XFREE_CLEAR(ac->desc); + } au_need_clean = true; } @@ -191,16 +270,15 @@ static void au_del_cmd(AutoCmd *ac) /// This is only done when not executing autocommands. static void au_cleanup(void) { - AutoPat *ap, **prev_ap; - event_T event; + AutoPat *ap; + AutoPat **prev_ap; if (autocmd_busy || !au_need_clean) { return; } // Loop over all events. - for (event = (event_T)0; (int)event < NUM_EVENTS; - event = (event_T)((int)event + 1)) { + FOR_ALL_AUEVENTS(event) { // Loop over all autocommand patterns. prev_ap = &(first_autopat[(int)event]); for (ap = *prev_ap; ap != NULL; ap = *prev_ap) { @@ -211,9 +289,13 @@ static void au_cleanup(void) for (AutoCmd *ac = *prev_ac; ac != NULL; ac = *prev_ac) { // Remove the command if the pattern is to be deleted or when // the command has been marked for deletion. - if (ap->pat == NULL || ac->cmd == NULL) { + if (ap->pat == NULL || aucmd_exec_is_deleted(ac->exec)) { *prev_ac = ac->next; - xfree(ac->cmd); + aucmd_exec_free(&ac->exec); + if (ac->desc != NULL) { + XFREE_CLEAR(ac->desc); + } + xfree(ac); } else { has_cmd = true; @@ -224,7 +306,7 @@ static void au_cleanup(void) if (ap->pat != NULL && !has_cmd) { // Pattern was not marked for deletion, but all of its commands were. // So mark the pattern for deletion. - au_remove_pat(ap); + aupat_del(ap); } // Remove the pattern if it has been marked for deletion. @@ -250,12 +332,17 @@ static void au_cleanup(void) au_need_clean = false; } + +// Get the first AutoPat for a particular event. +AutoPat *au_get_autopat_for_event(event_T event) +{ + return first_autopat[(int)event]; +} + // Called when buffer is freed, to remove/invalidate related buffer-local // autocmds. void aubuflocal_remove(buf_T *buf) { - AutoPat *ap; - event_T event; AutoPatCmd *apc; // invalidate currently executing autocommands @@ -266,12 +353,11 @@ void aubuflocal_remove(buf_T *buf) } // invalidate buflocals looping through events - for (event = (event_T)0; (int)event < NUM_EVENTS; - event = (event_T)((int)event + 1)) { - // loop over all autocommand patterns - for (ap = first_autopat[(int)event]; ap != NULL; ap = ap->next) { + FOR_ALL_AUEVENTS(event) { + FOR_ALL_AUPATS_IN_EVENT(event, ap) { if (ap->buflocal_nr == buf->b_fnum) { - au_remove_pat(ap); + aupat_del(ap); + if (p_verbose >= 6) { verbose_enter(); smsg(_("auto-removing autocommand: %s <buffer=%d>"), @@ -284,60 +370,71 @@ void aubuflocal_remove(buf_T *buf) au_cleanup(); } -// Add an autocmd group name. -// Return its ID. Returns AUGROUP_ERROR (< 0) for error. -static int au_new_group(char_u *name) +// Add an autocmd group name or return existing group matching name. +// Return its ID. +int augroup_add(char *name) { - int i = au_find_group(name); - if (i == AUGROUP_ERROR) { // the group doesn't exist yet, add it. - // First try using a free entry. - for (i = 0; i < augroups.ga_len; i++) { - if (AUGROUP_NAME(i) == NULL) { - break; - } - } - if (i == augroups.ga_len) { - ga_grow(&augroups, 1); - } + assert(STRICMP(name, "end") != 0); - AUGROUP_NAME(i) = xstrdup((char *)name); - if (i == augroups.ga_len) { - augroups.ga_len++; - } + int existing_id = augroup_find(name); + if (existing_id > 0) { + assert(existing_id != AUGROUP_DELETED); + return existing_id; } - return i; + if (existing_id == AUGROUP_DELETED) { + augroup_map_del(name); + } + + int next_id = next_augroup_id++; + String name_copy = cstr_to_string(name); + map_put(String, int)(&augroup_map, name_copy, next_id); + + return next_id; } -static void au_del_group(char_u *name) +/// Delete the augroup that matches name. +/// @param stupid_legacy_mode bool: This paremeter determines whether to run the augroup +/// deletion in the same fashion as `:augroup! {name}` where if there are any remaining +/// autocmds left in the augroup, it will change the name of the augroup to `--- DELETED ---` +/// but leave the autocmds existing. These are _separate_ augroups, so if you do this for +/// multiple augroups, you will have a bunch of `--- DELETED ---` augroups at the same time. +/// There is no way, as far as I could tell, how to actually delete them at this point as a user +/// +/// I did not consider this good behavior, so now when NOT in stupid_legacy_mode, we actually +/// delete these groups and their commands, like you would expect (and don't leave hanging +/// `--- DELETED ---` groups around) +void augroup_del(char *name, bool stupid_legacy_mode) { - int i = au_find_group(name); + int i = augroup_find(name); if (i == AUGROUP_ERROR) { // the group doesn't exist semsg(_("E367: No such group: \"%s\""), name); } else if (i == current_augroup) { emsg(_("E936: Cannot delete the current group")); } else { - event_T event; - AutoPat *ap; - int in_use = false; - - for (event = (event_T)0; (int)event < NUM_EVENTS; - event = (event_T)((int)event + 1)) { - for (ap = first_autopat[(int)event]; ap != NULL; ap = ap->next) { - if (ap->group == i && ap->pat != NULL) { - give_warning((char_u *)_("W19: Deleting augroup that is still in use"), true); - in_use = true; - event = NUM_EVENTS; - break; + if (stupid_legacy_mode) { + FOR_ALL_AUEVENTS(event) { + FOR_ALL_AUPATS_IN_EVENT(event, ap) { + if (ap->group == i && ap->pat != NULL) { + give_warning((char_u *)_("W19: Deleting augroup that is still in use"), true); + map_put(String, int)(&augroup_map, cstr_as_string(name), AUGROUP_DELETED); + return; + } } } - } - xfree(AUGROUP_NAME(i)); - if (in_use) { - AUGROUP_NAME(i) = (char *)get_deleted_augroup(); } else { - AUGROUP_NAME(i) = NULL; + FOR_ALL_AUEVENTS(event) { + FOR_ALL_AUPATS_IN_EVENT(event, ap) { + if (ap->group == i) { + aupat_del(ap); + } + } + } } + + // Remove the group because it's not currently in use. + augroup_map_del(name); + au_cleanup(); } } @@ -346,25 +443,70 @@ static void au_del_group(char_u *name) /// @param name augroup name /// /// @return the ID or AUGROUP_ERROR (< 0) for error. -static int au_find_group(const char_u *name) +int augroup_find(const char *name) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { - for (int i = 0; i < augroups.ga_len; i++) { - if (AUGROUP_NAME(i) != NULL && AUGROUP_NAME(i) != get_deleted_augroup() - && STRCMP(AUGROUP_NAME(i), name) == 0) { - return i; - } + int existing_id = map_get(String, int)(&augroup_map, cstr_as_string((char *)name)); + if (existing_id == AUGROUP_DELETED) { + return existing_id; } + + if (existing_id > 0) { + return existing_id; + } + return AUGROUP_ERROR; } +/// Gets the name for a particular group. +char *augroup_name(int group) +{ + assert(group != 0); + + if (group == AUGROUP_DELETED) { + return (char *)get_deleted_augroup(); + } + + if (group == AUGROUP_ALL) { + group = current_augroup; + } + + // next_augroup_id is the "source of truth" about what autocmds have existed + // + // The map_size is not the source of truth because groups can be removed from + // the map. When this happens, the map size is reduced. That's why this function + // relies on next_augroup_id instead. + + // "END" is always considered the last augroup ID. + // Used for expand_get_event_name and expand_get_augroup_name + if (group == next_augroup_id) { + return "END"; + } + + // If it's larger than the largest group, then it doesn't have a name + if (group > next_augroup_id) { + return NULL; + } + + String key; + int value; + map_foreach(&augroup_map, key, value, { + if (value == group) { + return key.data; + } + }); + + // If it's not in the map anymore, then it must have been deleted. + return (char *)get_deleted_augroup(); +} + /// Return true if augroup "name" exists. /// /// @param name augroup name -bool au_has_group(const char_u *name) +bool augroup_exists(const char *name) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { - return au_find_group(name) != AUGROUP_ERROR; + return augroup_find(name) > 0; } /// ":augroup {name}". @@ -374,23 +516,27 @@ void do_augroup(char_u *arg, int del_group) if (*arg == NUL) { emsg(_(e_argreq)); } else { - au_del_group(arg); + augroup_del((char *)arg, true); } } 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 = augroup_add((char *)arg); } else { // ":aug": list the group names msg_start(); - for (int i = 0; i < augroups.ga_len; i++) { - if (AUGROUP_NAME(i) != NULL) { - msg_puts(AUGROUP_NAME(i)); - msg_puts(" "); + + String name; + int value; + map_foreach(&augroup_map, name, value, { + if (value > 0) { + msg_puts(name.data); + } else { + msg_puts(augroup_name(value)); } - } + + msg_puts(" "); + }); + msg_clr_eos(); msg_end(); } @@ -399,25 +545,30 @@ void do_augroup(char_u *arg, int del_group) #if defined(EXITFREE) void free_all_autocmds(void) { - for (current_augroup = -1; current_augroup < augroups.ga_len; - current_augroup++) { - do_autocmd((char_u *)"", true); - } - - for (int i = 0; i < augroups.ga_len; i++) { - char *const s = ((char **)(augroups.ga_data))[i]; - if ((const char *)s != get_deleted_augroup()) { - xfree(s); + FOR_ALL_AUEVENTS(event) { + FOR_ALL_AUPATS_IN_EVENT(event, ap) { + aupat_del(ap); } } - ga_clear(&augroups); + + au_need_clean = true; + au_cleanup(); // may really delete removed patterns/commands now + + // Delete the augroup_map, including free the data + String name; + int id; + map_foreach(&augroup_map, name, id, { + (void)id; + api_free_string(name); + }) + map_destroy(String, int)(&augroup_map); } #endif // Return the event number for event name "start". // Return NUM_EVENTS if the event name was not found. // Return a pointer to the next event name in "end". -static event_T event_name2nr(const char_u *start, char_u **end) +event_T event_name2nr(const char_u *start, char_u **end) { const char_u *p; int i; @@ -447,7 +598,7 @@ static event_T event_name2nr(const char_u *start, char_u **end) /// @param[in] event Event to return name for. /// /// @return Event name, static string. Returns "Unknown" for unknown events. -static const char *event_nr2name(event_T event) +const char *event_nr2name(event_T event) FUNC_ATTR_NONNULL_RET FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_CONST { int i; @@ -460,33 +611,6 @@ static const char *event_nr2name(event_T event) return "Unknown"; } -/// Scan over the events. "*" stands for all events. -/// true when group name was found -static char_u *find_end_event(char_u *arg, int have_group) -{ - char_u *pat; - char_u *p; - - if (*arg == '*') { - if (arg[1] && !ascii_iswhite(arg[1])) { - semsg(_("E215: Illegal character after *: %s"), arg); - return NULL; - } - pat = arg + 1; - } else { - for (pat = arg; *pat && *pat != '|' && !ascii_iswhite(*pat); pat = p) { - if ((int)event_name2nr(pat, &p) >= NUM_EVENTS) { - if (have_group) { - semsg(_("E216: No such event: %s"), pat); - } else { - semsg(_("E216: No such group or event: %s"), pat); - } - return NULL; - } - } - } - return pat; -} /// Return true if "event" is included in 'eventignore'. /// @@ -595,7 +719,7 @@ void do_autocmd(char_u *arg_in, int forceit) char_u *envpat = NULL; char_u *cmd; int need_free = false; - int nested = false; + bool nested = false; bool once = false; int group; @@ -604,12 +728,12 @@ void do_autocmd(char_u *arg_in, int forceit) group = AUGROUP_ALL; // no argument, use all groups } else { // Check for a legal group name. If not, use AUGROUP_ALL. - group = au_get_grouparg(&arg); + group = arg_augroup_get(&arg); } // Scan over the events. // If we find an illegal name, return here, don't do anything. - pat = find_end_event(arg, group != AUGROUP_ALL); + pat = arg_event_skip(arg, group != AUGROUP_ALL); if (pat == NULL) { return; } @@ -646,37 +770,22 @@ void do_autocmd(char_u *arg_in, int forceit) } cmd = skipwhite(cmd); + + bool invalid_flags = false; for (size_t i = 0; i < 2; i++) { if (*cmd != NUL) { - // Check for "++once" flag. - if (STRNCMP(cmd, "++once", 6) == 0 && ascii_iswhite(cmd[6])) { - if (once) { - semsg(_(e_duparg2), "++once"); - } - once = true; - cmd = skipwhite(cmd + 6); - } + invalid_flags |= arg_autocmd_flag_get(&once, &cmd, "++once", 6); + invalid_flags |= arg_autocmd_flag_get(&nested, &cmd, "++nested", 8); - // Check for "++nested" flag. - if ((STRNCMP(cmd, "++nested", 8) == 0 && ascii_iswhite(cmd[8]))) { - if (nested) { - semsg(_(e_duparg2), "++nested"); - } - nested = true; - cmd = skipwhite(cmd + 8); - } - - // Check for the old (deprecated) "nested" flag. - if (STRNCMP(cmd, "nested", 6) == 0 && ascii_iswhite(cmd[6])) { - if (nested) { - semsg(_(e_duparg2), "nested"); - } - nested = true; - cmd = skipwhite(cmd + 6); - } + // Check the deprecated "nested" flag. + invalid_flags |= arg_autocmd_flag_get(&nested, &cmd, "nested", 6); } } + if (invalid_flags) { + return; + } + // Find the start of the commands. // Expand <sfile> in it. if (*cmd != NUL) { @@ -688,36 +797,37 @@ void do_autocmd(char_u *arg_in, int forceit) } } + bool is_showing = !forceit && *cmd == NUL; + // Print header when showing autocommands. - if (!forceit && *cmd == NUL) { + if (is_showing) { // Highlight title msg_puts_title(_("\n--- Autocommands ---")); - } - // Loop over the events. - last_event = (event_T)-1; // for listing the event name - last_group = AUGROUP_ERROR; // for listing the group name - if (*arg == '*' || *arg == NUL || *arg == '|') { - if (!forceit && *cmd != NUL) { - emsg(_(e_cannot_define_autocommands_for_all_events)); + if (*arg == '*' || *arg == '|' || *arg == NUL) { + au_show_for_all_events(group); + } else { + event_T event = event_name2nr(arg, &arg); + assert(event < NUM_EVENTS); + au_show_for_event(group, event); + } + } else { + if (*arg == '*' || *arg == NUL || *arg == '|') { + if (!forceit && *cmd != NUL) { + emsg(_(e_cannot_define_autocommands_for_all_events)); + } else { + do_all_autocmd_events(pat, once, nested, cmd, forceit, group); + } } else { - for (event_T event = (event_T)0; event < NUM_EVENTS; - event = (event_T)(event + 1)) { + while (*arg && *arg != '|' && !ascii_iswhite(*arg)) { + event_T event = event_name2nr(arg, &arg); + assert(event < NUM_EVENTS); if (do_autocmd_event(event, pat, once, nested, cmd, forceit, group) == FAIL) { break; } } } - } else { - while (*arg && *arg != '|' && !ascii_iswhite(*arg)) { - event_T event = event_name2nr(arg, &arg); - assert(event < NUM_EVENTS); - if (do_autocmd_event(event, pat, once, nested, cmd, forceit, group) - == FAIL) { - break; - } - } } if (need_free) { @@ -726,30 +836,14 @@ void do_autocmd(char_u *arg_in, int forceit) xfree(envpat); } -// Find the group ID in a ":autocmd" or ":doautocmd" argument. -// The "argp" argument is advanced to the following argument. -// -// Returns the group ID or AUGROUP_ALL. -static int au_get_grouparg(char_u **argp) +void do_all_autocmd_events(char_u *pat, bool once, int nested, char_u *cmd, bool delete, int group) { - char_u *group_name; - char_u *p; - char_u *arg = *argp; - int group = AUGROUP_ALL; - - for (p = arg; *p && !ascii_iswhite(*p) && *p != '|'; p++) { - } - if (p > arg) { - group_name = vim_strnsave(arg, (size_t)(p - arg)); - group = au_find_group(group_name); - if (group == AUGROUP_ERROR) { - group = AUGROUP_ALL; // no match, use all groups - } else { - *argp = skipwhite(p); // match, skip over group name + FOR_ALL_AUEVENTS(event) { + if (do_autocmd_event(event, pat, once, nested, cmd, delete, group) + == FAIL) { + return; } - xfree(group_name); } - return group; } // do_autocmd() for one event. @@ -759,216 +853,294 @@ static int au_get_grouparg(char_u **argp) // If *cmd == NUL: show entries. // If forceit == true: delete entries. // If group is not AUGROUP_ALL: only use this group. -static int do_autocmd_event(event_T event, char_u *pat, bool once, int nested, char_u *cmd, - int forceit, int group) +int do_autocmd_event(event_T event, char_u *pat, bool once, int nested, char_u *cmd, bool delete, + int group) + FUNC_ATTR_NONNULL_ALL { + // Cannot be used to show all patterns. See au_show_for_event or au_show_for_all_events + assert(*pat != NUL || delete); + AutoPat *ap; AutoPat **prev_ap; - AutoCmd *ac; - AutoCmd **prev_ac; - int brace_level; - char_u *endpat; int findgroup; - int allgroups; int patlen; int is_buflocal; int buflocal_nr; char_u buflocal_pat[BUFLOCAL_PAT_LEN]; // for "<buffer=X>" + bool is_adding_cmd = *cmd != NUL; + if (group == AUGROUP_ALL) { findgroup = current_augroup; } else { findgroup = group; } - allgroups = (group == AUGROUP_ALL && !forceit && *cmd == NUL); - // Show or delete all patterns for an event. - if (*pat == NUL) { - for (ap = first_autopat[event]; ap != NULL; ap = ap->next) { - if (forceit) { // delete the AutoPat, if it's in the current group - if (ap->group == findgroup) { - au_remove_pat(ap); - } - } else if (group == AUGROUP_ALL || ap->group == group) { - show_autocmd(ap, event); - } - } + // Delete all aupat for an event. + if (*pat == NUL && delete) { + aupat_del_for_event_and_group(event, findgroup); + return OK; } // Loop through all the specified patterns. - for (; *pat; pat = (*endpat == ',' ? endpat + 1 : endpat)) { - // Find end of the pattern. - // Watch out for a comma in braces, like "*.\{obj,o\}". - endpat = pat; - // ignore single comma - if (*endpat == ',') { - continue; - } - brace_level = 0; - for (; *endpat && (*endpat != ',' || brace_level || endpat[-1] == '\\'); - endpat++) { - if (*endpat == '{') { - brace_level++; - } else if (*endpat == '}') { - brace_level--; - } - } - patlen = (int)(endpat - pat); - - // detect special <buflocal[=X]> buffer-local patterns - is_buflocal = false; + patlen = (int)aucmd_pattern_length(pat); + while (patlen) { + // detect special <buffer[=X]> buffer-local patterns + is_buflocal = aupat_is_buflocal(pat, patlen); buflocal_nr = 0; - if (patlen >= 8 && STRNCMP(pat, "<buffer", 7) == 0 - && pat[patlen - 1] == '>') { - // "<buffer...>": Error will be printed only for addition. - // printing and removing will proceed silently. - is_buflocal = true; - if (patlen == 8) { - // "<buffer>" - buflocal_nr = curbuf->b_fnum; - } else if (patlen > 9 && pat[7] == '=') { - if (patlen == 13 && STRNICMP(pat, "<buffer=abuf>", 13) == 0) { - // "<buffer=abuf>" - buflocal_nr = autocmd_bufnr; - } else if (skipdigits(pat + 8) == pat + patlen - 1) { - // "<buffer=123>" - buflocal_nr = atoi((char *)pat + 8); - } - } - } - if (is_buflocal) { + buflocal_nr = aupat_get_buflocal_nr(pat, patlen); + // normalize pat into standard "<buffer>#N" form - snprintf((char *)buflocal_pat, - BUFLOCAL_PAT_LEN, - "<buffer=%d>", - buflocal_nr); + aupat_normalize_buflocal_pat(buflocal_pat, pat, patlen, buflocal_nr); - pat = buflocal_pat; // can modify pat and patlen - patlen = (int)STRLEN(buflocal_pat); // but not endpat + pat = buflocal_pat; + patlen = (int)STRLEN(buflocal_pat); } - // Find AutoPat entries with this pattern. When adding a command it - // always goes at or after the last one, so start at the end. - if (!forceit && *cmd != NUL && last_autopat[(int)event] != NULL) { - prev_ap = &last_autopat[(int)event]; - } else { + if (delete) { + assert(*pat != NUL); + + // Find AutoPat entries with this pattern. + // always goes at or after the last one, so start at the end. prev_ap = &first_autopat[(int)event]; - } - while ((ap = *prev_ap) != NULL) { - if (ap->pat != NULL) { - // Accept a pattern when: - // - a group was specified and it's that group, or a group was - // not specified and it's the current group, or a group was - // not specified and we are listing - // - the length of the pattern matches - // - the pattern matches. - // For <buffer[=X]>, this condition works because we normalize - // all buffer-local patterns. - if ((allgroups || ap->group == findgroup) && ap->patlen == patlen - && STRNCMP(pat, ap->pat, patlen) == 0) { - // Remove existing autocommands. - // If adding any new autocmd's for this AutoPat, don't - // delete the pattern from the autopat list, append to - // this list. - if (forceit) { - if (*cmd != NUL && ap->next == NULL) { - au_remove_cmds(ap); + while ((ap = *prev_ap) != NULL) { + if (ap->pat != NULL) { + // Accept a pattern when: + // - a group was specified and it's that group + // - the length of the pattern matches + // - the pattern matches. + // For <buffer[=X]>, this condition works because we normalize + // all buffer-local patterns. + if (ap->group == findgroup + && ap->patlen == patlen + && STRNCMP(pat, ap->pat, patlen) == 0) { + // Remove existing autocommands. + // If adding any new autocmd's for this AutoPat, don't + // delete the pattern from the autopat list, append to + // this list. + if (is_adding_cmd && ap->next == NULL) { + aupat_remove_cmds(ap); break; } - au_remove_pat(ap); - } else if (*cmd == NUL) { - // Show autocmd's for this autopat, or buflocals <buffer=X> - show_autocmd(ap, event); - } else if (ap->next == NULL) { - // Add autocmd to this autopat, if it's the last one. - break; + aupat_del(ap); } } + prev_ap = &ap->next; } - prev_ap = &ap->next; } - // Add a new command. - if (*cmd != NUL) { - // If the pattern we want to add a command to does appear at the - // end of the list (or not is not in the list at all), add the - // pattern at the end of the list. - if (ap == NULL) { - // refuse to add buffer-local ap if buffer number is invalid - if (is_buflocal - && (buflocal_nr == 0 || buflist_findnr(buflocal_nr) == NULL)) { - semsg(_("E680: <buffer=%d>: invalid buffer number "), buflocal_nr); - return FAIL; - } + if (is_adding_cmd) { + AucmdExecutable exec = AUCMD_EXECUTABLE_INIT; + exec.type = CALLABLE_EX; + exec.callable.cmd = cmd; + autocmd_register(0, event, pat, patlen, group, once, nested, NULL, exec); + } - ap = xmalloc(sizeof(AutoPat)); - ap->pat = vim_strnsave(pat, (size_t)patlen); - ap->patlen = patlen; + pat = aucmd_next_pattern(pat, (size_t)patlen); + patlen = (int)aucmd_pattern_length(pat); + } - if (is_buflocal) { - ap->buflocal_nr = buflocal_nr; - ap->reg_prog = NULL; - } else { - char_u *reg_pat; + au_cleanup(); + return OK; +} - ap->buflocal_nr = 0; - reg_pat = file_pat_to_reg_pat(pat, endpat, &ap->allow_dirs, true); - if (reg_pat != NULL) { - ap->reg_prog = vim_regcomp(reg_pat, RE_MAGIC); - } - xfree(reg_pat); - if (reg_pat == NULL || ap->reg_prog == NULL) { - xfree(ap->pat); - xfree(ap); - return FAIL; - } - } +int autocmd_register(int64_t id, event_T event, char_u *pat, int patlen, int group, bool once, + bool nested, char *desc, AucmdExecutable aucmd) +{ + // 0 is not a valid group. + assert(group != 0); - // need to initialize last_mode for the first ModeChanged autocmd - if (event == EVENT_MODECHANGED && !has_event(EVENT_MODECHANGED)) { - xfree(last_mode); - last_mode = get_mode(); - } + AutoPat *ap; + AutoPat **prev_ap; + AutoCmd *ac; + AutoCmd **prev_ac; + int is_buflocal; + int buflocal_nr; + int findgroup; + char_u buflocal_pat[BUFLOCAL_PAT_LEN]; // for "<buffer=X>" - // If the event is CursorMoved, update the last cursor position - // position to avoid immediately triggering the autocommand - if (event == EVENT_CURSORMOVED && !has_event(EVENT_CURSORMOVED)) { - curwin->w_last_cursormoved = curwin->w_cursor; - } + if (patlen > (int)STRLEN(pat)) { + return FAIL; + } - ap->cmds = NULL; - *prev_ap = ap; - last_autopat[(int)event] = ap; - ap->next = NULL; - if (group == AUGROUP_ALL) { - ap->group = current_augroup; - } else { - ap->group = group; + if (group == AUGROUP_ALL) { + findgroup = current_augroup; + } else { + findgroup = group; + } + + + // detect special <buffer[=X]> buffer-local patterns + is_buflocal = aupat_is_buflocal(pat, patlen); + buflocal_nr = 0; + + if (is_buflocal) { + buflocal_nr = aupat_get_buflocal_nr(pat, patlen); + + // normalize pat into standard "<buffer>#N" form + aupat_normalize_buflocal_pat(buflocal_pat, pat, patlen, buflocal_nr); + + pat = buflocal_pat; + patlen = (int)STRLEN(buflocal_pat); + } + + if (last_autopat[(int)event] != NULL) { + prev_ap = &last_autopat[(int)event]; + } else { + prev_ap = &first_autopat[(int)event]; + } + + while ((ap = *prev_ap) != NULL) { + if (ap->pat != NULL) { + // Accept a pattern when: + // - a group was specified and it's that group + // - the length of the pattern matches + // - the pattern matches. + // For <buffer[=X]>, this condition works because we normalize + // all buffer-local patterns. + if (ap->group == findgroup + && ap->patlen == patlen + && STRNCMP(pat, ap->pat, patlen) == 0) { + if (ap->next == NULL) { + // Add autocmd to this autopat, if it's the last one. + break; } } + } + prev_ap = &ap->next; + } + + // If the pattern we want to add a command to does appear at the + // end of the list (or not is not in the list at all), add the + // pattern at the end of the list. + if (ap == NULL) { + // refuse to add buffer-local ap if buffer number is invalid + if (is_buflocal + && (buflocal_nr == 0 || buflist_findnr(buflocal_nr) == NULL)) { + semsg(_("E680: <buffer=%d>: invalid buffer number "), buflocal_nr); + return FAIL; + } + + ap = xmalloc(sizeof(AutoPat)); + ap->pat = vim_strnsave(pat, (size_t)patlen); + ap->patlen = patlen; + + if (is_buflocal) { + ap->buflocal_nr = buflocal_nr; + ap->reg_prog = NULL; + } else { + char_u *reg_pat; - // Add the autocmd at the end of the AutoCmd list. - prev_ac = &(ap->cmds); - while ((ac = *prev_ac) != NULL) { - prev_ac = &ac->next; + ap->buflocal_nr = 0; + reg_pat = file_pat_to_reg_pat(pat, pat + patlen, &ap->allow_dirs, true); + if (reg_pat != NULL) { + ap->reg_prog = vim_regcomp(reg_pat, RE_MAGIC); + } + xfree(reg_pat); + if (reg_pat == NULL || ap->reg_prog == NULL) { + xfree(ap->pat); + xfree(ap); + return FAIL; } - ac = xmalloc(sizeof(AutoCmd)); - ac->cmd = vim_strsave(cmd); - ac->script_ctx = current_sctx; - ac->script_ctx.sc_lnum += sourcing_lnum; - ac->next = NULL; - *prev_ac = ac; - ac->once = once; - ac->nested = nested; + } + + // need to initialize last_mode for the first ModeChanged autocmd + if (event == EVENT_MODECHANGED && !has_event(EVENT_MODECHANGED)) { + xfree(last_mode); + last_mode = get_mode(); + } + + // If the event is CursorMoved, update the last cursor position + // position to avoid immediately triggering the autocommand + if (event == EVENT_CURSORMOVED && !has_event(EVENT_CURSORMOVED)) { + curwin->w_last_cursormoved = curwin->w_cursor; + } + + ap->cmds = NULL; + *prev_ap = ap; + last_autopat[(int)event] = ap; + ap->next = NULL; + if (group == AUGROUP_ALL) { + ap->group = current_augroup; + } else { + ap->group = group; } } - au_cleanup(); // may really delete removed patterns/commands now + // Add the autocmd at the end of the AutoCmd list. + prev_ac = &(ap->cmds); + while ((ac = *prev_ac) != NULL) { + prev_ac = &ac->next; + } + + ac = xmalloc(sizeof(AutoCmd)); + *prev_ac = ac; + + ac->id = id; + ac->exec = aucmd_exec_copy(aucmd); + ac->script_ctx = current_sctx; + ac->script_ctx.sc_lnum += sourcing_lnum; + nlua_set_sctx(&ac->script_ctx); + ac->next = NULL; + ac->once = once; + ac->nested = nested; + ac->desc = NULL; + + // TODO(tjdevries): What to do about :autocmd and where/how to show lua stuffs there. + // perhaps: <lua>DESCRIPTION or similar + if (desc != NULL) { + ac->desc = xstrdup(desc); + } else { + ac->desc = aucmd_exec_default_desc(aucmd); + } + return OK; } +size_t aucmd_pattern_length(char_u *pat) +{ + if (*pat == NUL) { + return 0; + } + + char_u *endpat; + + for (; *pat; pat = (*endpat == ',' ? endpat + 1 : endpat)) { + // Find end of the pattern. + // Watch out for a comma in braces, like "*.\{obj,o\}". + endpat = pat; + // ignore single comma + if (*endpat == ',') { + continue; + } + int brace_level = 0; + for (; *endpat && (*endpat != ',' || brace_level || endpat[-1] == '\\'); + endpat++) { + if (*endpat == '{') { + brace_level++; + } else if (*endpat == '}') { + brace_level--; + } + } + + return (size_t)(endpat - pat); + } + + return STRLEN(pat); +} + +char_u *aucmd_next_pattern(char_u *pat, size_t patlen) +{ + pat = pat + patlen; + if (*pat == ',') { + pat = pat + 1; + } + + return pat; +} + /// Implementation of ":doautocmd [group] event [fname]". /// Return OK for success, FAIL for failure; /// @@ -984,7 +1156,7 @@ int do_doautocmd(char_u *arg, bool do_msg, bool *did_something) } // Check for a legal group name. If not, use AUGROUP_ALL. - group = au_get_grouparg(&arg); + group = arg_augroup_get(&arg); if (*arg == '*') { emsg(_("E217: Can't execute autocommands for ALL events")); @@ -993,7 +1165,7 @@ int do_doautocmd(char_u *arg, bool do_msg, bool *did_something) // Scan over the events. // If we find an illegal name, return here, don't do anything. - fname = find_end_event(arg, group != AUGROUP_ALL); + fname = arg_event_skip(arg, group != AUGROUP_ALL); if (fname == NULL) { return FAIL; } @@ -1069,8 +1241,6 @@ void ex_doautoall(exarg_T *eap) do_modelines(0); } } - - check_cursor(); // just in case lines got deleted } /// Check *argp for <nomodeline>. When it is present return false, otherwise @@ -1160,7 +1330,10 @@ void aucmd_prepbuf(aco_save_T *aco, buf_T *buf) // Prevent chdir() call in win_enter_ext(), through do_autochdir() int save_acd = p_acd; p_acd = false; + // no redrawing and don't set the window title + RedrawingDisabled++; win_enter(aucmd_win, false); + RedrawingDisabled--; p_acd = save_acd; unblock_autocmds(); curwin = aucmd_win; @@ -1168,6 +1341,10 @@ void aucmd_prepbuf(aco_save_T *aco, buf_T *buf) curbuf = buf; aco->new_curwin_handle = curwin->handle; set_bufref(&aco->new_curbuf, curbuf); + + // disable the Visual area, the position may be invalid in another buffer + aco->save_VIsual_active = VIsual_active; + VIsual_active = false; } /// Cleanup after executing autocommands for a (hidden) buffer. @@ -1264,6 +1441,12 @@ win_found: check_cursor(); } } + + check_cursor(); // just in case lines got deleted + VIsual_active = aco->save_VIsual_active; + if (VIsual_active) { + check_pos(curbuf, &VIsual); + } } /// Execute autocommands for "event" and file name "fname". @@ -1370,8 +1553,8 @@ bool trigger_cursorhold(void) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT /// @param eap Ex command arguments /// /// @return true if some commands were executed. -static bool apply_autocmds_group(event_T event, char_u *fname, char_u *fname_io, bool force, - int group, buf_T *buf, exarg_T *eap) +bool apply_autocmds_group(event_T event, char_u *fname, char_u *fname_io, bool force, int group, + buf_T *buf, exarg_T *eap) { char_u *sfname = NULL; // short file name char_u *tail; @@ -1394,7 +1577,7 @@ static bool apply_autocmds_group(event_T event, char_u *fname, char_u *fname_io, proftime_T wait_time; bool did_save_redobuff = false; save_redo_T save_redo; - const bool save_KeyTyped = KeyTyped; + const bool save_KeyTyped = KeyTyped; // NOLINT // Quickly return if there are no autocommands for this event or // autocommands are blocked. @@ -1509,12 +1692,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 { @@ -1805,6 +1989,11 @@ void auto_next_pat(AutoPatCmd *apc, int stop_at_last) /// @return allocated string, or NULL for end of autocommands. char_u *getnextac(int c, void *cookie, int indent, bool do_concat) { + // These arguments are required for do_cmdline. + (void)c; + (void)indent; + (void)do_concat; + AutoPatCmd *acp = (AutoPatCmd *)cookie; char_u *retval; AutoCmd *ac; @@ -1817,7 +2006,8 @@ char_u *getnextac(int c, void *cookie, int indent, bool do_concat) // repeat until we find an autocommand to execute for (;;) { // skip removed commands - while (acp->nextcmd != NULL && acp->nextcmd->cmd == NULL) { + while (acp->nextcmd != NULL + && aucmd_exec_is_deleted(acp->nextcmd->exec)) { if (acp->nextcmd->last) { acp->nextcmd = NULL; } else { @@ -1847,14 +2037,34 @@ char_u *getnextac(int c, void *cookie, int indent, bool do_concat) if (p_verbose >= 9) { verbose_enter_scroll(); - smsg(_("autocommand %s"), ac->cmd); + smsg(_("autocommand %s"), aucmd_exec_to_string(ac, ac->exec)); msg_puts("\n"); // don't overwrite this either verbose_leave_scroll(); } - retval = vim_strsave(ac->cmd); + + if (ac->exec.type == CALLABLE_CB) { + typval_T argsin = TV_INITIAL_VALUE; + typval_T rettv = TV_INITIAL_VALUE; + callback_call(&ac->exec.callable.cb, 0, &argsin, &rettv); + + // TODO(tjdevries): + // + // Major Hack Alert: + // We just return "not-null" and continue going. + // This would be a good candidate for a refactor. You would need to refactor: + // 1. do_cmdline to accept something besides a string + // OR + // 2. make where we call do_cmdline for autocmds not have to return anything, + // and instead we loop over all the matches and just execute one-by-one. + // However, my expectation would be that could be expensive. + retval = vim_strsave((char_u *)""); + } else { + retval = vim_strsave(ac->exec.callable.cmd); + } + // Remove one-shot ("once") autocmd in anticipation of its execution. if (ac->once) { - au_del_cmd(ac); + aucmd_del(ac); } autocmd_nested = ac->nested; current_sctx = ac->script_ctx; @@ -1919,19 +2129,12 @@ bool has_autocmd(event_T event, char_u *sfname, buf_T *buf) FUNC_ATTR_WARN_UNUSE // Function given to ExpandGeneric() to obtain the list of autocommand group // names. -char_u *get_augroup_name(expand_T *xp, int idx) +char_u *expand_get_augroup_name(expand_T *xp, int idx) { - if (idx == augroups.ga_len) { // add "END" add the end - return (char_u *)"END"; - } - if (idx >= augroups.ga_len) { // end of list - return NULL; - } - if (AUGROUP_NAME(idx) == NULL || AUGROUP_NAME(idx) == get_deleted_augroup()) { - // skip deleted entries - return (char_u *)""; - } - return (char_u *)AUGROUP_NAME(idx); + // Required for ExpandGeneric + (void)xp; + + return (char_u *)augroup_name(idx + 1); } /// @param doautocmd true for :doauto*, false for :autocmd @@ -1943,7 +2146,7 @@ char_u *set_context_in_autocmd(expand_T *xp, char_u *arg, int doautocmd) // check for a group name, skip it if present autocmd_include_groups = false; p = arg; - group = au_get_grouparg(&arg); + group = arg_augroup_get(&arg); // If there only is a group name that's what we expand. if (*arg == NUL && group != AUGROUP_ALL && !ascii_iswhite(arg[-1])) { @@ -1984,16 +2187,25 @@ char_u *set_context_in_autocmd(expand_T *xp, char_u *arg, int doautocmd) } // Function given to ExpandGeneric() to obtain the list of event names. -char_u *get_event_name(expand_T *xp, int idx) +char_u *expand_get_event_name(expand_T *xp, int idx) { - if (idx < augroups.ga_len) { // First list group names, if wanted - if (!autocmd_include_groups || AUGROUP_NAME(idx) == NULL - || AUGROUP_NAME(idx) == get_deleted_augroup()) { - return (char_u *)""; // skip deleted entries + // xp is a required parameter to be used with ExpandGeneric + (void)xp; + + + // List group names + char *name = augroup_name(idx + 1); + if (name != NULL) { + // skip when not including groups or skip deleted entries + if (!autocmd_include_groups || name == get_deleted_augroup()) { + return (char_u *)""; } - return (char_u *)AUGROUP_NAME(idx); + + return (char_u *)name; } - return (char_u *)event_names[idx - augroups.ga_len].name; + + // List event names + return (char_u *)event_names[idx - next_augroup_id].name; } /// Check whether given autocommand is supported @@ -2036,7 +2248,7 @@ bool au_exists(const char *const arg) FUNC_ATTR_WARN_UNUSED_RESULT } // First, look for an autocmd group name. - group = au_find_group((char_u *)arg_save); + group = augroup_find(arg_save); char *event_name; if (group == AUGROUP_ERROR) { // Didn't match a group name, assume the first argument is an event. @@ -2100,3 +2312,362 @@ theend: xfree(arg_save); return retval; } + +// Checks if a pattern is buflocal +bool aupat_is_buflocal(char_u *pat, int patlen) +{ + return patlen >= 8 + && STRNCMP(pat, "<buffer", 7) == 0 + && (pat)[patlen - 1] == '>'; +} + +int aupat_get_buflocal_nr(char_u *pat, int patlen) +{ + assert(aupat_is_buflocal(pat, patlen)); + + // "<buffer>" + if (patlen == 8) { + return curbuf->b_fnum; + } + + if (patlen > 9 && (pat)[7] == '=') { + // "<buffer=abuf>" + if (patlen == 13 && STRNICMP(pat, "<buffer=abuf>", 13) == 0) { + return autocmd_bufnr; + } + + // "<buffer=123>" + if (skipdigits(pat + 8) == pat + patlen - 1) { + return atoi((char *)pat + 8); + } + } + + return 0; +} + +// normalize buffer pattern +void aupat_normalize_buflocal_pat(char_u *dest, char_u *pat, int patlen, int buflocal_nr) +{ + assert(aupat_is_buflocal(pat, patlen)); + + if (buflocal_nr == 0) { + buflocal_nr = curbuf->handle; + } + + // normalize pat into standard "<buffer>#N" form + snprintf((char *)dest, + BUFLOCAL_PAT_LEN, + "<buffer=%d>", + buflocal_nr); +} + +int autocmd_delete_event(int group, event_T event, char_u *pat) + FUNC_ATTR_NONNULL_ALL +{ + return do_autocmd_event(event, pat, false, false, (char_u *)"", true, group); +} + +/// Deletes an autocmd by ID. +/// Only autocmds created via the API have IDs associated with them. There +/// is no way to delete a specific autocmd created via :autocmd +void autocmd_delete_id(int64_t id) +{ + FOR_ALL_AUEVENTS(event) { + FOR_ALL_AUPATS_IN_EVENT(event, ap) { + for (AutoCmd *ac = ap->cmds; ac != NULL; ac = ac->next) { + if (ac->id == id) { + aucmd_del(ac); + } + } + } + } +} + +// =========================================================================== +// AucmdExecutable Functions +// =========================================================================== +char *aucmd_exec_default_desc(AucmdExecutable acc) +{ + size_t msglen = 100; + + switch (acc.type) { + case CALLABLE_CB: + switch (acc.callable.cb.type) { + case kCallbackLua: { + char *msg = (char *)xmallocz(msglen); + snprintf(msg, msglen, "<Lua function %d>", acc.callable.cb.data.luaref); + return msg; + } + case kCallbackFuncref: { + // TODO(tjdevries): Is this enough space for this? + char *msg = (char *)xmallocz(msglen); + snprintf(msg, msglen, "<vim function: %s>", acc.callable.cb.data.funcref); + return msg; + } + case kCallbackPartial: { + char *msg = (char *)xmallocz(msglen); + snprintf(msg, msglen, "<vim partial: %s>", acc.callable.cb.data.partial->pt_name); + return msg; + } + default: + return NULL; + } + default: + return NULL; + } + + abort(); +} +char *aucmd_exec_to_string(AutoCmd *ac, AucmdExecutable acc) +{ + switch (acc.type) { + case CALLABLE_EX: + return (char *)acc.callable.cmd; + case CALLABLE_CB: + return ac->desc; + case CALLABLE_NONE: + return "This is not possible"; + } + + abort(); +} + +void aucmd_exec_free(AucmdExecutable *acc) +{ + switch (acc->type) { + case CALLABLE_EX: + XFREE_CLEAR(acc->callable.cmd); + break; + case CALLABLE_CB: + callback_free(&acc->callable.cb); + break; + case CALLABLE_NONE: + return; + } + + acc->type = CALLABLE_NONE; +} + +AucmdExecutable aucmd_exec_copy(AucmdExecutable src) +{ + AucmdExecutable dest = AUCMD_EXECUTABLE_INIT; + + switch (src.type) { + case CALLABLE_EX: + dest.type = CALLABLE_EX; + dest.callable.cmd = vim_strsave(src.callable.cmd); + return dest; + case CALLABLE_CB: + dest.type = CALLABLE_CB; + callback_copy(&dest.callable.cb, &src.callable.cb); + return dest; + case CALLABLE_NONE: + return dest; + } + + abort(); +} + +bool aucmd_exec_is_deleted(AucmdExecutable acc) +{ + switch (acc.type) { + case CALLABLE_EX: + return acc.callable.cmd == NULL; + case CALLABLE_CB: + return callback_is_freed(acc.callable.cb); + case CALLABLE_NONE: + return true; + } + + abort(); +} + +bool au_event_is_empty(event_T event) +{ + return first_autopat[event] == NULL; +} + +// Arg Parsing Functions + +/// Scan over the events. "*" stands for all events. +/// true when group name was found +static char_u *arg_event_skip(char_u *arg, int have_group) +{ + char_u *pat; + char_u *p; + + if (*arg == '*') { + if (arg[1] && !ascii_iswhite(arg[1])) { + semsg(_("E215: Illegal character after *: %s"), arg); + return NULL; + } + pat = arg + 1; + } else { + for (pat = arg; *pat && *pat != '|' && !ascii_iswhite(*pat); pat = p) { + if ((int)event_name2nr(pat, &p) >= NUM_EVENTS) { + if (have_group) { + semsg(_("E216: No such event: %s"), pat); + } else { + semsg(_("E216: No such group or event: %s"), pat); + } + return NULL; + } + } + } + return pat; +} + +// Find the group ID in a ":autocmd" or ":doautocmd" argument. +// The "argp" argument is advanced to the following argument. +// +// Returns the group ID or AUGROUP_ALL. +static int arg_augroup_get(char_u **argp) +{ + char_u *group_name; + char_u *p; + char_u *arg = *argp; + int group = AUGROUP_ALL; + + for (p = arg; *p && !ascii_iswhite(*p) && *p != '|'; p++) { + } + if (p > arg) { + group_name = vim_strnsave(arg, (size_t)(p - arg)); + group = augroup_find((char *)group_name); + if (group == AUGROUP_ERROR) { + group = AUGROUP_ALL; // no match, use all groups + } else { + *argp = skipwhite(p); // match, skip over group name + } + xfree(group_name); + } + return group; +} + +/// Handles grabbing arguments from `:autocmd` such as ++once and ++nested +static bool arg_autocmd_flag_get(bool *flag, char_u **cmd_ptr, char *pattern, int len) +{ + if (STRNCMP(*cmd_ptr, pattern, len) == 0 && ascii_iswhite((*cmd_ptr)[len])) { + if (*flag) { + semsg(_(e_duparg2), pattern); + return true; + } + + *flag = true; + *cmd_ptr = skipwhite((*cmd_ptr) + len); + } + + return false; +} + + +// UI Enter +void do_autocmd_uienter(uint64_t chanid, bool attached) +{ + static bool recursive = false; + + if (recursive) { + return; // disallow recursion + } + recursive = true; + + save_v_event_T save_v_event; + dict_T *dict = get_v_event(&save_v_event); + assert(chanid < VARNUMBER_MAX); + tv_dict_add_nr(dict, S_LEN("chan"), (varnumber_T)chanid); + tv_dict_set_keys_readonly(dict); + apply_autocmds(attached ? EVENT_UIENTER : EVENT_UILEAVE, + NULL, NULL, false, curbuf); + restore_v_event(dict, &save_v_event); + + recursive = false; +} + +// FocusGained + +static void focusgained_event(void **argv) +{ + bool *gainedp = argv[0]; + do_autocmd_focusgained(*gainedp); + xfree(gainedp); +} + +void autocmd_schedule_focusgained(bool gained) +{ + bool *gainedp = xmalloc(sizeof(*gainedp)); + *gainedp = gained; + loop_schedule_deferred(&main_loop, + event_create(focusgained_event, 1, gainedp)); +} + +static void do_autocmd_focusgained(bool gained) +{ + static bool recursive = false; + static Timestamp last_time = (time_t)0; + bool need_redraw = false; + + if (recursive) { + return; // disallow recursion + } + recursive = true; + need_redraw |= apply_autocmds((gained ? EVENT_FOCUSGAINED : EVENT_FOCUSLOST), + NULL, NULL, false, curbuf); + + // When activated: Check if any file was modified outside of Vim. + // Only do this when not done within the last two seconds as: + // 1. Some filesystems have modification time granularity in seconds. Fat32 + // has a granularity of 2 seconds. + // 2. We could get multiple notifications in a row. + if (gained && last_time + (Timestamp)2000 < os_now()) { + need_redraw = check_timestamps(true); + last_time = os_now(); + } + + if (need_redraw) { + // Something was executed, make sure the cursor is put back where it + // belongs. + need_wait_return = false; + + if (State & CMDLINE) { + redrawcmdline(); + } else if ((State & NORMAL) || (State & INSERT)) { + if (must_redraw != 0) { + update_screen(0); + } + + setcursor(); + } + + ui_flush(); + } + + if (need_maketitle) { + maketitle(); + } + + recursive = false; +} + +// initialization + +void init_default_autocmds(void) +{ + // open terminals when opening files that start with term:// +#define PROTO "term://" + do_cmdline_cmd("augroup nvim_terminal"); + do_cmdline_cmd("autocmd BufReadCmd " PROTO "* ++nested " + "if !exists('b:term_title')|call termopen(" + // Capture the command string + "matchstr(expand(\"<amatch>\"), " + "'\\c\\m" PROTO "\\%(.\\{-}//\\%(\\d\\+:\\)\\?\\)\\?\\zs.*'), " + // capture the working directory + "{'cwd': expand(get(matchlist(expand(\"<amatch>\"), " + "'\\c\\m" PROTO "\\(.\\{-}\\)//'), 1, ''))})" + "|endif"); + do_cmdline_cmd("augroup END"); +#undef PROTO + + // limit syntax synchronization in the command window + do_cmdline_cmd("augroup nvim_cmdwin"); + do_cmdline_cmd("autocmd! CmdwinEnter [:>] syntax sync minlines=1 maxlines=1"); + do_cmdline_cmd("augroup END"); +} diff --git a/src/nvim/autocmd.h b/src/nvim/autocmd.h index ac12e2acf3..53ec7f2bb7 100644 --- a/src/nvim/autocmd.h +++ b/src/nvim/autocmd.h @@ -4,6 +4,11 @@ #include "nvim/buffer_defs.h" #include "nvim/ex_cmds_defs.h" +// event_T definition +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "auevents_enum.generated.h" +#endif + // Struct to save values in before executing autocommands for a buffer that is // not the current buffer. typedef struct { @@ -14,25 +19,27 @@ typedef struct { handle_T save_prevwin_handle; ///< ID of saved prevwin bufref_T new_curbuf; ///< new curbuf char_u *globaldir; ///< saved value of globaldir + bool save_VIsual_active; ///< saved VIsual_active } aco_save_T; typedef struct AutoCmd { - char_u *cmd; // Command to be executed (NULL when - // command has been removed) + AucmdExecutable exec; bool once; // "One shot": removed after execution bool nested; // If autocommands nest here bool last; // last command in list + int64_t id; // ID used for uniquely tracking an autocmd. sctx_T script_ctx; // script context where defined - struct AutoCmd *next; // Next AutoCmd in list + char *desc; // Description for the autocmd. + struct AutoCmd *next; // Next AutoCmd in list } AutoCmd; typedef struct AutoPat { - struct AutoPat *next; // next AutoPat in AutoPat list; MUST - // be the first entry - char_u *pat; // pattern as typed (NULL when pattern - // has been removed) - regprog_T *reg_prog; // compiled regprog for pattern - AutoCmd *cmds; // list of commands to do + struct AutoPat *next; // next AutoPat in AutoPat list; MUST + // be the first entry + char_u *pat; // pattern as typed (NULL when pattern + // has been removed) + regprog_T *reg_prog; // compiled regprog for pattern + AutoCmd *cmds; // list of commands to do int group; // group ID int patlen; // strlen() of pat int buflocal_nr; // !=0 for buffer-local AutoPat @@ -40,13 +47,7 @@ typedef struct AutoPat { char last; // last pattern for apply_autocmds() } AutoPat; -#ifdef INCLUDE_GENERATED_DECLARATIONS -# include "auevents_enum.generated.h" -#endif - -/// /// Struct used to keep status while executing autocommands for an event. -/// typedef struct AutoPatCmd { AutoPat *curpat; // next AutoPat to examine AutoCmd *nextcmd; // next AutoCmd to execute @@ -74,8 +75,16 @@ EXTERN bool au_did_filetype INIT(= false); # include "autocmd.h.generated.h" #endif -#define AUGROUP_DEFAULT -1 // default autocmd group -#define AUGROUP_ERROR -2 // erroneous autocmd group -#define AUGROUP_ALL -3 // all autocmd groups +#define AUGROUP_DEFAULT (-1) // default autocmd group +#define AUGROUP_ERROR (-2) // erroneous autocmd group +#define AUGROUP_ALL (-3) // all autocmd groups +#define AUGROUP_DELETED (-4) // all autocmd groups +// #define AUGROUP_NS -5 // TODO(tjdevries): Support namespaced based augroups + +#define BUFLOCAL_PAT_LEN 25 + +/// Iterates over all the events for auto commands +#define FOR_ALL_AUEVENTS(event) \ + for (event_T event = (event_T)0; (int)event < (int)NUM_EVENTS; event = (event_T)((int)event + 1)) // NOLINT #endif // NVIM_AUTOCMD_H diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index 89baea83f8..9e82b4e80b 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,7 +1454,11 @@ void set_curbuf(buf_T *buf, int action) } if (bufref_valid(&prevbufref) && !aborting()) { win_T *previouswin = curwin; - if (prevbuf == curbuf) { + + // Do not sync when in Insert mode and the buffer is open in + // another window, might be a timer doing something in another + // window. + if (prevbuf == curbuf && ((State & INSERT) == 0 || curbuf->b_nwindows <= 1)) { u_sync(false); } close_buffer(prevbuf == curwin->w_buffer ? curwin : NULL, @@ -1494,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) { @@ -1504,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); } @@ -1896,10 +1900,8 @@ void free_buf_options(buf_T *buf, int free_p_ff) clear_string_option(&buf->b_p_flp); clear_string_option(&buf->b_p_isk); clear_string_option(&buf->b_p_vsts); - xfree(buf->b_p_vsts_nopaste); - buf->b_p_vsts_nopaste = NULL; - xfree(buf->b_p_vsts_array); - buf->b_p_vsts_array = NULL; + XFREE_CLEAR(buf->b_p_vsts_nopaste); + XFREE_CLEAR(buf->b_p_vsts_array); clear_string_option(&buf->b_p_vts); XFREE_CLEAR(buf->b_p_vts_array); clear_string_option(&buf->b_p_keymap); @@ -3326,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); @@ -3418,7 +3420,7 @@ typedef enum { /// /// @return The final width of the statusline int build_stl_str_hl(win_T *wp, char_u *out, size_t outlen, char_u *fmt, int use_sandbox, - char_u fillchar, int maxwidth, stl_hlrec_t **hltab, StlClickRecord **tabtab) + int fillchar, int maxwidth, stl_hlrec_t **hltab, StlClickRecord **tabtab) { static size_t stl_items_len = 20; // Initial value, grows as needed. static stl_item_t *stl_items = NULL; @@ -3437,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); } @@ -3461,9 +3467,6 @@ int build_stl_str_hl(win_T *wp, char_u *out, size_t outlen, char_u *fmt, int use if (fillchar == 0) { fillchar = ' '; - } else if (utf_char2len(fillchar) > 1) { - // Can't handle a multi-byte fill character yet. - fillchar = '-'; } // The cursor in windows other than the current one isn't always @@ -3516,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); @@ -3661,7 +3664,7 @@ int build_stl_str_hl(win_T *wp, char_u *out, size_t outlen, char_u *fmt, int use out_p = out_p - n + 1; // Fill up space left over by half a double-wide char. while (++group_len < stl_items[stl_groupitems[groupdepth]].minwid) { - *out_p++ = fillchar; + MB_CHAR2BYTES(fillchar, out_p); } // } @@ -3684,14 +3687,14 @@ int build_stl_str_hl(win_T *wp, char_u *out, size_t outlen, char_u *fmt, int use if (min_group_width < 0) { min_group_width = 0 - min_group_width; while (group_len++ < min_group_width && out_p < out_end_p) { - *out_p++ = fillchar; + MB_CHAR2BYTES(fillchar, out_p); } // If the group is right-aligned, shift everything to the right and // prepend with filler characters. } else { // { Move the group to the right - memmove(t + min_group_width - group_len, t, (size_t)(out_p - t)); - group_len = min_group_width - group_len; + group_len = (min_group_width - group_len) * utf_char2len(fillchar); + memmove(t + group_len, t, (size_t)(out_p - t)); if (out_p + group_len >= (out_end_p + 1)) { group_len = (long)(out_end_p - out_p); } @@ -3705,7 +3708,7 @@ int build_stl_str_hl(win_T *wp, char_u *out, size_t outlen, char_u *fmt, int use // Prepend the fill characters for (; group_len > 0; group_len--) { - *t++ = fillchar; + MB_CHAR2BYTES(fillchar, t); } } } @@ -4001,14 +4004,7 @@ int build_stl_str_hl(win_T *wp, char_u *out, size_t outlen, char_u *fmt, int use case STL_VIRTCOL: case STL_VIRTCOL_ALT: { - // In list mode virtcol needs to be recomputed - colnr_T virtcol = wp->w_virtcol; - if (wp->w_p_list && wp->w_p_lcs_chars.tab1 == NUL) { - wp->w_p_list = false; - getvcol(wp, &wp->w_cursor, NULL, &virtcol, NULL); - wp->w_p_list = true; - } - virtcol++; + colnr_T virtcol = wp->w_virtcol + 1; // Don't display %V if it's the same as %c. if (opt == STL_VIRTCOL_ALT && (virtcol == (colnr_T)(!(State & INSERT) && empty_line @@ -4237,7 +4233,7 @@ int build_stl_str_hl(win_T *wp, char_u *out, size_t outlen, char_u *fmt, int use if (l + 1 == minwid && fillchar == '-' && ascii_isdigit(*t)) { *out_p++ = ' '; } else { - *out_p++ = fillchar; + MB_CHAR2BYTES(fillchar, out_p); } } minwid = 0; @@ -4248,20 +4244,21 @@ int build_stl_str_hl(win_T *wp, char_u *out, size_t outlen, char_u *fmt, int use } // { Copy the string text into the output buffer - while (*t && out_p < out_end_p) { - *out_p++ = *t++; + for (; *t && out_p < out_end_p; t++) { // Change a space by fillchar, unless fillchar is '-' and a // digit follows. - if (fillable && out_p[-1] == ' ' - && (!ascii_isdigit(*t) || fillchar != '-')) { - out_p[-1] = fillchar; + if (fillable && *t == ' ' + && (!ascii_isdigit(*(t + 1)) || fillchar != '-')) { + MB_CHAR2BYTES(fillchar, out_p); + } else { + *out_p++ = *t; } } // } // For left-aligned items, fill any remaining space with the fillchar for (; l < minwid && out_p < out_end_p; l++) { - *out_p++ = fillchar; + MB_CHAR2BYTES(fillchar, out_p); } // Otherwise if the item is a number, copy that to the output buffer. @@ -4351,7 +4348,7 @@ int build_stl_str_hl(win_T *wp, char_u *out, size_t outlen, char_u *fmt, int use // Only free the string buffer if we allocated it. // Note: This is not needed if `str` is pointing at `tmp` if (opt == STL_VIM_EXPR) { - xfree(str); + XFREE_CLEAR(str); } if (num >= 0 || (!itemisflag && str && *str)) { @@ -4454,7 +4451,7 @@ int build_stl_str_hl(win_T *wp, char_u *out, size_t outlen, char_u *fmt, int use // Fill up for half a double-wide character. while (++width < maxwidth) { - *trunc_p++ = fillchar; + MB_CHAR2BYTES(fillchar, trunc_p); *trunc_p = NUL; } // } @@ -4505,13 +4502,13 @@ int build_stl_str_hl(win_T *wp, char_u *out, size_t outlen, char_u *fmt, int use standard_spaces * (num_separators - 1); for (int i = 0; i < num_separators; i++) { - int dislocation = (i == (num_separators - 1)) - ? final_spaces : standard_spaces; + int dislocation = (i == (num_separators - 1)) ? final_spaces : standard_spaces; + dislocation *= utf_char2len(fillchar); char_u *start = stl_items[stl_separator_locations[i]].start; char_u *seploc = start + dislocation; STRMOVE(seploc, start); - for (char_u *s = start; s < seploc; s++) { - *s = fillchar; + for (char_u *s = start; s < seploc; ) { + MB_CHAR2BYTES(fillchar, s); } for (int item_idx = stl_separator_locations[i] + 1; @@ -4576,7 +4573,7 @@ int build_stl_str_hl(win_T *wp, char_u *out, size_t outlen, char_u *fmt, int use cur_tab_rec->def.func = NULL; } - // When inside update_screen we do not want redrawing a stausline, ruler, + // When inside update_screen we do not want redrawing a statusline, ruler, // title, etc. to trigger another redraw, it may cause an endless loop. if (updating_screen) { must_redraw = save_must_redraw; @@ -4830,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... @@ -5021,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... @@ -5102,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; @@ -5144,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 { @@ -5463,33 +5460,54 @@ 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) +{ + // The maximum can be determined from 'signcolumn' which is window scoped so + // need to invalidate signcols if the maximum is greater than the previous + // maximum. + if (maximum > buf->b_signcols_max) { + buf->b_signcols_valid = false; + } + + 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; + buf->b_signcols_max = maximum; redraw_buf_later(buf, NOT_VALID); } diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h index 49e527e98b..1e0c837056 100644 --- a/src/nvim/buffer_defs.h +++ b/src/nvim/buffer_defs.h @@ -204,6 +204,10 @@ typedef struct { #define w_p_nu w_onebuf_opt.wo_nu // 'number' int wo_rnu; #define w_p_rnu w_onebuf_opt.wo_rnu // 'relativenumber' + char_u *wo_ve; +#define w_p_ve w_onebuf_opt.wo_ve // 'virtualedit' + unsigned wo_ve_flags; +#define w_ve_flags w_onebuf_opt.wo_ve_flags // flags for 'virtualedit' long wo_nuw; #define w_p_nuw w_onebuf_opt.wo_nuw // 'numberwidth' int wo_wfh; @@ -352,6 +356,7 @@ struct mapblock { char_u *m_keys; // mapped from, lhs char_u *m_str; // mapped to, rhs char_u *m_orig_str; // rhs as entered by the user + LuaRef m_luaref; // lua function reference as rhs int m_keylen; // strlen(m_keys) int m_mode; // valid mode int m_noremap; // if non-zero no re-mapping for m_str @@ -359,6 +364,7 @@ struct mapblock { char m_nowait; // <nowait> used char m_expr; // <expr> used, m_str is an expression sctx_T m_script_ctx; // SCTX where map was defined + char *m_desc; // description of keymap }; /// Used for highlighting in the status line. @@ -581,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 @@ -856,6 +864,7 @@ struct file_buffer { sign_entry_T *b_signlist; // list of placed signs int b_signcols; // last calculated number of sign columns bool b_signcols_valid; // calculated sign columns is valid + int b_signcols_max; // Maximum value b_signcols is valid for. Terminal *terminal; // Terminal instance associated with the buffer @@ -864,8 +873,7 @@ struct file_buffer { int b_mapped_ctrl_c; // modes where CTRL-C is mapped MarkTree b_marktree[1]; - Map(uint64_t, ExtmarkItem) b_extmark_index[1]; - Map(uint64_t, ExtmarkNs) b_extmark_ns[1]; // extmark namespaces + Map(uint32_t, uint32_t) b_extmark_ns[1]; // extmark namespaces size_t b_virt_line_blocks; // number of virt_line blocks // array of channel_id:s which have asked to receive updates for this @@ -1346,6 +1354,7 @@ struct window_S { // recomputed int w_nrwidth; // width of 'number' and 'relativenumber' // column being used + int w_scwidth; // width of 'signcolumn' /* * === end of cached values === diff --git a/src/nvim/change.c b/src/nvim/change.c index 1dbbfff024..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; } @@ -789,7 +790,7 @@ int del_bytes(colnr_T count, bool fixpos_arg, bool use_delcombine) // fixpos is true, we don't want to end up positioned at the NUL, // unless "restart_edit" is set or 'virtualedit' contains "onemore". if (col > 0 && fixpos && restart_edit == 0 - && (ve_flags & VE_ONEMORE) == 0) { + && (get_ve_flags() & VE_ONEMORE) == 0) { curwin->w_cursor.col--; curwin->w_cursor.coladd = 0; curwin->w_cursor.col -= utf_head_off(oldp, oldp + curwin->w_cursor.col); @@ -952,11 +953,13 @@ int copy_indent(int size, char_u *src) /// /// "second_line_indent": indent for after ^^D in Insert mode or if flag /// OPENLINE_COM_LIST +/// "did_do_comment" is set to true when intentionally putting the comment +/// leader in fromt of the new line. /// /// @param dir FORWARD or BACKWARD /// /// @return true on success, false on failure -int open_line(int dir, int flags, int second_line_indent) +int open_line(int dir, int flags, int second_line_indent, bool *did_do_comment) { char_u *next_line = NULL; // copy of the next line char_u *p_extra = NULL; // what goes to next line @@ -969,6 +972,7 @@ int open_line(int dir, int flags, int second_line_indent) bool retval = false; // return value int extra_len = 0; // length of p_extra string int lead_len; // length of comment leader + int comment_start = 0; // start index of the comment leader char_u *lead_flags; // position in 'comments' for comment leader char_u *leader = NULL; // copy of comment leader char_u *allocated = NULL; // allocated memory @@ -977,6 +981,7 @@ int open_line(int dir, int flags, int second_line_indent) pos_T *pos; bool do_si = (!p_paste && curbuf->b_p_si && !curbuf->b_p_cin && *curbuf->b_p_inde == NUL); + bool do_cindent; bool no_si = false; // reset did_si afterwards int first_char = NUL; // init for GCC int vreplace_mode; @@ -1189,11 +1194,30 @@ int open_line(int dir, int flags, int second_line_indent) did_ai = true; } + // May do indenting after opening a new line. + do_cindent = !p_paste && (curbuf->b_p_cin || *curbuf->b_p_inde != NUL) + && in_cinkeys(dir == FORWARD ? KEY_OPEN_FORW : KEY_OPEN_BACK, + ' ', linewhite(curwin->w_cursor.lnum)); + // 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) { lead_len = get_leader_len(saved_line, &lead_flags, dir == BACKWARD, true); + 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, + &lead_flags, false, true); + if (lead_len != 0) { + lead_len += comment_start; + if (did_do_comment != NULL) { + *did_do_comment = true; + } + } + } + } } else { lead_len = 0; } @@ -1349,6 +1373,13 @@ int open_line(int dir, int flags, int second_line_indent) STRLCPY(leader, saved_line, lead_len + 1); + // TODO(vim): handle multi-byte and double width chars + for (int li = 0; li < comment_start; li++) { + if (!ascii_iswhite(leader[li])) { + leader[li] = ' '; + } + } + // Replace leader with lead_repl, right or left adjusted if (lead_repl != NULL) { int c = 0; @@ -1758,13 +1789,7 @@ int open_line(int dir, int flags, int second_line_indent) ai_col = (colnr_T)getwhitecols_curline(); } // May do indenting after opening a new line. - if (!p_paste - && (curbuf->b_p_cin - || *curbuf->b_p_inde != NUL - ) - && in_cinkeys(dir == FORWARD - ? KEY_OPEN_FORW - : KEY_OPEN_BACK, ' ', linewhite(curwin->w_cursor.lnum))) { + if (do_cindent) { do_c_expr_indent(); ai_col = (colnr_T)getwhitecols_curline(); } 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/channel.h b/src/nvim/channel.h index 81b75e2d31..50d6b3600a 100644 --- a/src/nvim/channel.h +++ b/src/nvim/channel.h @@ -96,6 +96,8 @@ struct Channel { EXTERN PMap(uint64_t) channels INIT(= MAP_INIT); +EXTERN Callback on_print INIT(= CALLBACK_INIT); + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "channel.h.generated.h" #endif diff --git a/src/nvim/charset.c b/src/nvim/charset.c index 599d662993..f4882e57e1 100644 --- a/src/nvim/charset.c +++ b/src/nvim/charset.c @@ -217,9 +217,7 @@ int buf_init_chartab(buf_T *buf, int global) } } else if (i == 1) { // (re)set printable - // For double-byte we keep the cell width, so - // that we can detect it from the first byte. - if (((c < ' ') || (c > '~'))) { + if (c < ' ' || c > '~') { if (tilde) { g_chartab[c] = (uint8_t)((g_chartab[c] & ~CT_CELL_MASK) + ((dy_flags & DY_UHEX) ? 4 : 2)); @@ -539,7 +537,7 @@ char_u *transchar_buf(const buf_T *buf, int c) c = K_SECOND(c); } - if ((!chartab_initialized && (((c >= ' ') && (c <= '~')))) + if ((!chartab_initialized && (c >= ' ' && c <= '~')) || ((c <= 0xFF) && vim_isprintc_strict(c))) { // printable character transchar_charbuf[i] = (char_u)c; @@ -1441,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, @@ -1587,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/context.c b/src/nvim/context.c index 614a3ce30e..9434b4e0ea 100644 --- a/src/nvim/context.c +++ b/src/nvim/context.c @@ -256,7 +256,8 @@ static inline void ctx_save_funcs(Context *ctx, bool scriptonly) size_t cmd_len = sizeof("func! ") + STRLEN(name); char *cmd = xmalloc(cmd_len); snprintf(cmd, cmd_len, "func! %s", name); - String func_body = nvim_exec(cstr_as_string(cmd), true, &err); + String func_body = nvim_exec(VIML_INTERNAL_CALL, cstr_as_string(cmd), + true, &err); xfree(cmd); if (!ERROR_SET(&err)) { ADD(ctx->funcs, STRING_OBJ(func_body)); diff --git a/src/nvim/cursor.c b/src/nvim/cursor.c index 6e2c6232d7..55f55a46b2 100644 --- a/src/nvim/cursor.c +++ b/src/nvim/cursor.c @@ -15,6 +15,7 @@ #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/move.h" +#include "nvim/option.h" #include "nvim/plines.h" #include "nvim/screen.h" #include "nvim/state.h" @@ -110,7 +111,7 @@ static int coladvance2(pos_T *pos, bool addspaces, bool finetune, colnr_T wcol_a || (State & TERM_FOCUS) || restart_edit != NUL || (VIsual_active && *p_sel != 'o') - || ((ve_flags & VE_ONEMORE) && wcol < MAXCOL); + || ((get_ve_flags() & VE_ONEMORE) && wcol < MAXCOL); line = ml_get_buf(curbuf, pos->lnum, false); if (wcol >= MAXCOL) { @@ -366,6 +367,7 @@ void check_cursor_col_win(win_T *win) colnr_T len; colnr_T oldcol = win->w_cursor.col; colnr_T oldcoladd = win->w_cursor.col + win->w_cursor.coladd; + unsigned int cur_ve_flags = get_ve_flags(); len = (colnr_T)STRLEN(ml_get_buf(win->w_buffer, win->w_cursor.lnum, false)); if (len == 0) { @@ -377,7 +379,7 @@ void check_cursor_col_win(win_T *win) * - 'virtualedit' is set */ if ((State & INSERT) || restart_edit || (VIsual_active && *p_sel != 'o') - || (ve_flags & VE_ONEMORE) + || (cur_ve_flags & VE_ONEMORE) || virtual_active()) { win->w_cursor.col = len; } else { @@ -394,7 +396,7 @@ void check_cursor_col_win(win_T *win) // line. if (oldcol == MAXCOL) { win->w_cursor.coladd = 0; - } else if (ve_flags == VE_ALL) { + } else if (cur_ve_flags == VE_ALL) { if (oldcoladd > win->w_cursor.col) { win->w_cursor.coladd = oldcoladd - win->w_cursor.col; diff --git a/src/nvim/debugger.c b/src/nvim/debugger.c index b6e35f3047..75ebf0084e 100644 --- a/src/nvim/debugger.c +++ b/src/nvim/debugger.c @@ -268,7 +268,7 @@ void do_debug(char_u *cmd) DOCMD_VERBOSE|DOCMD_EXCRESET); debug_break_level = n; } - lines_left = (int)(Rows - 1); + lines_left = Rows - 1; } xfree(cmdline); @@ -277,7 +277,7 @@ void do_debug(char_u *cmd) redraw_all_later(NOT_VALID); need_wait_return = false; msg_scroll = save_msg_scroll; - lines_left = (int)(Rows - 1); + lines_left = Rows - 1; State = save_State; debug_mode = false; did_emsg = save_did_emsg; diff --git a/src/nvim/decoration.c b/src/nvim/decoration.c index c0f3c32f93..935b233752 100644 --- a/src/nvim/decoration.c +++ b/src/nvim/decoration.c @@ -13,8 +13,6 @@ # include "decoration.c.generated.h" #endif -static PMap(uint64_t) hl_decors; - /// Add highlighting to a buffer, bounded by two cursor positions, /// with an offset. /// @@ -33,9 +31,9 @@ void bufhl_add_hl_pos_offset(buf_T *buf, int src_id, int hl_id, lpos_T pos_start { colnr_T hl_start = 0; colnr_T hl_end = 0; - Decoration *decor = decor_hl(hl_id); + Decoration decor = DECORATION_INIT; + decor.hl_id = hl_id; - decor->priority = DECOR_PRIORITY_BASE; // TODO(bfredl): if decoration had blocky mode, we could avoid this loop for (linenr_T lnum = pos_start.lnum; lnum <= pos_end.lnum; lnum++) { int end_off = 0; @@ -59,40 +57,23 @@ void bufhl_add_hl_pos_offset(buf_T *buf, int src_id, int hl_id, lpos_T pos_start hl_start = pos_start.col + offset; hl_end = pos_end.col + offset; } - (void)extmark_set(buf, (uint64_t)src_id, NULL, - (int)lnum-1, hl_start, (int)lnum-1+end_off, hl_end, - decor, true, false, kExtmarkNoUndo); - } -} - -Decoration *decor_hl(int hl_id) -{ - assert(hl_id > 0); - Decoration **dp = (Decoration **)pmap_ref(uint64_t)(&hl_decors, - (uint64_t)hl_id, true); - if (*dp) { - return *dp; + extmark_set(buf, (uint32_t)src_id, NULL, + (int)lnum-1, hl_start, (int)lnum-1+end_off, hl_end, + &decor, true, false, kExtmarkNoUndo); } - - Decoration *decor = xcalloc(1, sizeof(*decor)); - decor->hl_id = hl_id; - decor->shared = true; - decor->priority = DECOR_PRIORITY_BASE; - *dp = decor; - return decor; } void decor_redraw(buf_T *buf, int row1, int row2, Decoration *decor) { - if (decor->hl_id && row2 >= row1) { + if ((!decor || decor->hl_id) && row2 >= row1) { redraw_buf_range_later(buf, row1+1, row2+1); } - if (kv_size(decor->virt_text)) { + if (decor && kv_size(decor->virt_text)) { redraw_buf_line_later(buf, row1+1); } - if (kv_size(decor->virt_lines)) { + if (decor && kv_size(decor->virt_lines)) { redraw_buf_line_later(buf, MIN(buf->b_ml.ml_line_count, row1+1+(decor->virt_lines_above?0:1))); } @@ -100,17 +81,17 @@ void decor_redraw(buf_T *buf, int row1, int row2, Decoration *decor) void decor_remove(buf_T *buf, int row, int row2, Decoration *decor) { - if (kv_size(decor->virt_lines)) { + decor_redraw(buf, row, row2, decor); + if (decor && kv_size(decor->virt_lines)) { assert(buf->b_virt_line_blocks > 0); buf->b_virt_line_blocks--; } - decor_redraw(buf, row, row2, decor); decor_free(decor); } void decor_free(Decoration *decor) { - if (decor && !decor->shared) { + if (decor) { clear_virttext(&decor->virt_text); for (size_t i = 0; i < kv_size(decor->virt_lines); i++) { clear_virttext(&kv_A(decor->virt_lines, i).line); @@ -134,17 +115,16 @@ Decoration *decor_find_virttext(buf_T *buf, int row, uint64_t ns_id) MarkTreeIter itr[1] = { 0 }; marktree_itr_get(buf->b_marktree, row, 0, itr); while (true) { - mtmark_t mark = marktree_itr_current(itr); - if (mark.row < 0 || mark.row > row) { + mtkey_t mark = marktree_itr_current(itr); + if (mark.pos.row < 0 || mark.pos.row > row) { break; - } else if (marktree_decor_level(mark.id) < kDecorLevelVisible) { + } else if (marktree_decor_level(mark) < kDecorLevelVisible) { goto next_mark; } - ExtmarkItem *item = map_ref(uint64_t, ExtmarkItem)(buf->b_extmark_index, - mark.id, false); - if (item && (ns_id == 0 || ns_id == item->ns_id) - && item->decor && kv_size(item->decor->virt_text)) { - return item->decor; + Decoration *decor = mark.decor_full; + if ((ns_id == 0 || ns_id == mark.ns) + && decor && kv_size(decor->virt_text)) { + return decor; } next_mark: marktree_itr_next(buf->b_marktree, itr); @@ -163,7 +143,20 @@ bool decor_redraw_reset(buf_T *buf, DecorState *state) } } kv_size(state->active) = 0; - return map_size(buf->b_extmark_index); + return buf->b_marktree->n_keys; +} + +Decoration get_decor(mtkey_t mark) +{ + if (mark.decor_full) { + return *mark.decor_full; + } else { + Decoration fake = DECORATION_INIT; + fake.hl_id = mark.hl_id; + fake.priority = mark.priority; + fake.hl_eol = (mark.flags & MT_FLAG_HL_EOL); + return fake; + } } @@ -176,42 +169,35 @@ bool decor_redraw_start(buf_T *buf, int top_row, DecorState *state) } marktree_itr_rewind(buf->b_marktree, state->itr); while (true) { - mtmark_t mark = marktree_itr_current(state->itr); - if (mark.row < 0) { // || mark.row > end_row + mtkey_t mark = marktree_itr_current(state->itr); + if (mark.pos.row < 0) { // || mark.row > end_row break; } - if ((mark.row < top_row && mark.id&MARKTREE_END_FLAG) - || marktree_decor_level(mark.id) < kDecorLevelVisible) { + if ((mark.pos.row < top_row && mt_end(mark)) + || marktree_decor_level(mark) < kDecorLevelVisible) { goto next_mark; } - uint64_t start_id = mark.id & ~MARKTREE_END_FLAG; - ExtmarkItem *item = map_ref(uint64_t, ExtmarkItem)(buf->b_extmark_index, - start_id, false); - if (!item || !item->decor) { - goto next_mark; - } - Decoration *decor = item->decor; + Decoration decor = get_decor(mark); - mtpos_t altpos = marktree_lookup(buf->b_marktree, - mark.id^MARKTREE_END_FLAG, NULL); + mtpos_t altpos = marktree_get_altpos(buf->b_marktree, mark, NULL); - if ((!(mark.id&MARKTREE_END_FLAG) && altpos.row < top_row - && !kv_size(decor->virt_text)) - || ((mark.id&MARKTREE_END_FLAG) && altpos.row >= top_row)) { + if ((!mt_end(mark) && altpos.row < top_row + && !kv_size(decor.virt_text)) + || (mt_end(mark) && altpos.row >= top_row)) { goto next_mark; } - if (mark.id&MARKTREE_END_FLAG) { - decor_add(state, altpos.row, altpos.col, mark.row, mark.col, - decor, false); + if (mt_end(mark)) { + decor_add(state, altpos.row, altpos.col, mark.pos.row, mark.pos.col, + &decor, false); } else { if (altpos.row == -1) { - altpos.row = mark.row; - altpos.col = mark.col; + altpos.row = mark.pos.row; + altpos.col = mark.pos.col; } - decor_add(state, mark.row, mark.col, altpos.row, altpos.col, - decor, false); + decor_add(state, mark.pos.row, mark.pos.col, altpos.row, altpos.col, + &decor, false); } next_mark: @@ -266,43 +252,36 @@ int decor_redraw_col(buf_T *buf, int col, int win_col, bool hidden, DecorState * while (true) { // TODO(bfredl): check duplicate entry in "intersection" // branch - mtmark_t mark = marktree_itr_current(state->itr); - if (mark.row < 0 || mark.row > state->row) { + mtkey_t mark = marktree_itr_current(state->itr); + if (mark.pos.row < 0 || mark.pos.row > state->row) { break; - } else if (mark.row == state->row && mark.col > col) { - state->col_until = mark.col-1; + } else if (mark.pos.row == state->row && mark.pos.col > col) { + state->col_until = mark.pos.col-1; break; } - if ((mark.id&MARKTREE_END_FLAG) - || marktree_decor_level(mark.id) < kDecorLevelVisible) { + if (mt_end(mark) + || marktree_decor_level(mark) < kDecorLevelVisible) { goto next_mark; } - ExtmarkItem *item = map_ref(uint64_t, ExtmarkItem)(buf->b_extmark_index, - mark.id, false); - if (!item || !item->decor) { - goto next_mark; - } - Decoration *decor = item->decor; + Decoration decor = get_decor(mark); - mtpos_t endpos = marktree_lookup(buf->b_marktree, - mark.id|MARKTREE_END_FLAG, NULL); + mtpos_t endpos = marktree_get_altpos(buf->b_marktree, mark, NULL); if (endpos.row == -1) { - endpos.row = mark.row; - endpos.col = mark.col; + endpos = mark.pos; } - if (endpos.row < mark.row - || (endpos.row == mark.row && endpos.col <= mark.col)) { - if (!kv_size(decor->virt_text)) { + if (endpos.row < mark.pos.row + || (endpos.row == mark.pos.row && endpos.col <= mark.pos.col)) { + if (!kv_size(decor.virt_text)) { goto next_mark; } } - decor_add(state, mark.row, mark.col, endpos.row, endpos.col, - decor, false); + decor_add(state, mark.pos.row, mark.pos.col, endpos.row, endpos.col, + &decor, false); next_mark: marktree_itr_next(buf->b_marktree, state->itr); @@ -452,18 +431,18 @@ int decor_virt_lines(win_T *wp, linenr_T lnum, VirtLines *lines) MarkTreeIter itr[1] = { 0 }; marktree_itr_get(buf->b_marktree, row, 0, itr); while (true) { - mtmark_t mark = marktree_itr_current(itr); - if (mark.row < 0 || mark.row >= end_row) { + mtkey_t mark = marktree_itr_current(itr); + if (mark.pos.row < 0 || mark.pos.row >= end_row) { break; - } else if (marktree_decor_level(mark.id) < kDecorLevelVirtLine) { + } else if (marktree_decor_level(mark) < kDecorLevelVirtLine) { goto next_mark; } - bool above = mark.row > (int)(lnum - 2); - ExtmarkItem *item = map_ref(uint64_t, ExtmarkItem)(buf->b_extmark_index, mark.id, false); - if (item && item->decor && item->decor->virt_lines_above == above) { - virt_lines += (int)kv_size(item->decor->virt_lines); + bool above = mark.pos.row > (int)(lnum - 2); + Decoration *decor = mark.decor_full; + if (decor && decor->virt_lines_above == above) { + virt_lines += (int)kv_size(decor->virt_lines); if (lines) { - kv_splice(*lines, item->decor->virt_lines); + kv_splice(*lines, decor->virt_lines); } } next_mark: diff --git a/src/nvim/decoration.h b/src/nvim/decoration.h index 611b4223da..02472d09e4 100644 --- a/src/nvim/decoration.h +++ b/src/nvim/decoration.h @@ -17,6 +17,8 @@ typedef enum { kVTRightAlign, } VirtTextPos; +EXTERN const char *const virt_text_pos_str[] INIT(= { "eol", "overlay", "win_col", "right_align" }); + typedef enum { kHlModeUnknown, kHlModeReplace, @@ -24,6 +26,8 @@ typedef enum { kHlModeBlend, } HlMode; +EXTERN const char *const hl_mode_str[] INIT(= { "", "replace", "combine", "blend" }); + typedef kvec_t(VirtTextChunk) VirtText; #define VIRTTEXT_EMPTY ((VirtText)KV_INITIAL_VALUE) @@ -42,7 +46,6 @@ struct Decoration { // TODO(bfredl): at some point turn this into FLAGS bool virt_text_hide; bool hl_eol; - bool shared; // shared decoration, don't free bool virt_lines_above; // TODO(bfredl): style, signs, etc DecorPriority priority; @@ -50,7 +53,7 @@ struct Decoration { int virt_text_width; // width of virt_text }; #define DECORATION_INIT { KV_INITIAL_VALUE, KV_INITIAL_VALUE, 0, kVTEndOfLine, kHlModeUnknown, \ - false, false, false, false, DECOR_PRIORITY_BASE, 0, 0 } + false, false, false, DECOR_PRIORITY_BASE, 0, 0 } typedef struct { int start_row; diff --git a/src/nvim/diff.c b/src/nvim/diff.c index 0233b3a5ab..80bd3229c6 100644 --- a/src/nvim/diff.c +++ b/src/nvim/diff.c @@ -37,8 +37,8 @@ #include "nvim/path.h" #include "nvim/screen.h" #include "nvim/strings.h" -#include "nvim/undo.h" #include "nvim/ui.h" +#include "nvim/undo.h" #include "nvim/vim.h" #include "nvim/window.h" #include "xdiff/xdiff.h" @@ -82,6 +82,14 @@ typedef struct { garray_T dout_ga; // used for internal diff } diffout_T; +// used for recording hunks from xdiff +typedef struct { + linenr_T lnum_orig; + long count_orig; + linenr_T lnum_new; + long count_new; +} diffhunk_T; + // two diff inputs and one result typedef struct { diffin_T dio_orig; // original file input @@ -674,11 +682,11 @@ void diff_redraw(bool dofold) } if (wp_other != NULL && curwin->w_p_scb) { if (used_max_fill_curwin) { - // The current window was set to used the maximum number of filler + // The current window was set to use the maximum number of filler // lines, may need to reduce them. diff_set_topline(wp_other, curwin); } else if (used_max_fill_other) { - // The other window was set to used the maximum number of filler + // The other window was set to use the maximum number of filler // lines, may need to reduce them. diff_set_topline(curwin, wp_other); } @@ -782,9 +790,14 @@ static int diff_write(buf_T *buf, diffin_T *din) // Always use 'fileformat' set to "unix". char_u *save_ff = buf->b_p_ff; buf->b_p_ff = vim_strsave((char_u *)FF_UNIX); + const bool save_lockmarks = cmdmod.lockmarks; + // Writing the buffer is an implementation detail of performing the diff, + // so it shouldn't update the '[ and '] marks. + cmdmod.lockmarks = true; int r = buf_write(buf, din->din_fname, NULL, (linenr_T)1, buf->b_ml.ml_line_count, NULL, false, false, false, true); + cmdmod.lockmarks = save_lockmarks; free_string_option(buf->b_p_ff); buf->b_p_ff = save_ff; return r; @@ -852,7 +865,7 @@ static void diff_try_update(diffio_T *dio, int idx_orig, exarg_T *eap) } // Read the diff output and add each entry to the diff list. - diff_read(idx_orig, idx_new, &dio->dio_diff); + diff_read(idx_orig, idx_new, dio); clear_diffin(&dio->dio_new); clear_diffout(&dio->dio_diff); @@ -1078,7 +1091,7 @@ static int diff_file_internal(diffio_T *diffio) emit_cfg.ctxlen = 0; // don't need any diff_context here emit_cb.priv = &diffio->dio_diff; - emit_cb.out_line = xdiff_out; + emit_cfg.hunk_func = xdiff_out; if (xdl_diff(&diffio->dio_orig.din_mmfile, &diffio->dio_new.din_mmfile, ¶m, &emit_cfg, &emit_cb) < 0) { @@ -1272,7 +1285,7 @@ void ex_diffpatch(exarg_T *eap) ex_file(eap); // Do filetype detection with the new name. - if (au_has_group((char_u *)"filetypedetect")) { + if (augroup_exists("filetypedetect")) { do_cmdline_cmd(":doau filetypedetect BufRead"); } } @@ -1508,7 +1521,7 @@ void ex_diffoff(exarg_T *eap) diff_clear(curtab); } - // Remove "hor" from from 'scrollopt' if there are no diff windows left. + // Remove "hor" from 'scrollopt' if there are no diff windows left. if (!diffwin && (vim_strchr(p_sbo, 'h') != NULL)) { do_cmdline_cmd("set sbo-=hor"); } @@ -1519,20 +1532,20 @@ void ex_diffoff(exarg_T *eap) /// @param idx_orig idx of original file /// @param idx_new idx of new file /// @dout diff output -static void diff_read(int idx_orig, int idx_new, diffout_T *dout) +static void diff_read(int idx_orig, int idx_new, diffio_T *dio) { FILE *fd = NULL; int line_idx = 0; diff_T *dprev = NULL; diff_T *dp = curtab->tp_first_diff; diff_T *dn, *dpl; + diffout_T *dout = &dio->dio_diff; char_u linebuf[LBUFLEN]; // only need to hold the diff line char_u *line; long off; int i; - linenr_T lnum_orig, lnum_new; - long count_orig, count_new; int notset = true; // block "*dp" not set yet + diffhunk_T *hunk; enum { DIFF_ED, DIFF_UNIFIED, @@ -1549,70 +1562,79 @@ static void diff_read(int idx_orig, int idx_new, diffout_T *dout) } } + if (!dio->dio_internal) { + hunk = xmalloc(sizeof(*hunk)); + } + for (;;) { - if (fd == NULL) { + if (dio->dio_internal) { if (line_idx >= dout->dout_ga.ga_len) { break; // did last line } - line = ((char_u **)dout->dout_ga.ga_data)[line_idx++]; + hunk = ((diffhunk_T **)dout->dout_ga.ga_data)[line_idx++]; } else { - if (vim_fgets(linebuf, LBUFLEN, fd)) { - break; // end of file - } - line = linebuf; - } - - if (diffstyle == DIFF_NONE) { - // Determine diff style. - // ed like diff looks like this: - // {first}[,{last}]c{first}[,{last}] - // {first}a{first}[,{last}] - // {first}[,{last}]d{first} - // - // unified diff looks like this: - // --- file1 2018-03-20 13:23:35.783153140 +0100 - // +++ file2 2018-03-20 13:23:41.183156066 +0100 - // @@ -1,3 +1,5 @@ - if (isdigit(*line)) { - diffstyle = DIFF_ED; - } else if ((STRNCMP(line, "@@ ", 3) == 0)) { - diffstyle = DIFF_UNIFIED; - } else if ((STRNCMP(line, "--- ", 4) == 0) // -V501 - && (vim_fgets(linebuf, LBUFLEN, fd) == 0) // -V501 - && (STRNCMP(line, "+++ ", 4) == 0) - && (vim_fgets(linebuf, LBUFLEN, fd) == 0) // -V501 - && (STRNCMP(line, "@@ ", 3) == 0)) { - diffstyle = DIFF_UNIFIED; + if (fd == NULL) { + if (line_idx >= dout->dout_ga.ga_len) { + break; // did last line + } + line = ((char_u **)dout->dout_ga.ga_data)[line_idx++]; } else { - // Format not recognized yet, skip over this line. Cygwin diff - // may put a warning at the start of the file. - continue; + if (vim_fgets(linebuf, LBUFLEN, fd)) { + break; // end of file + } + line = linebuf; } - } - if (diffstyle == DIFF_ED) { - if (!isdigit(*line)) { - continue; // not the start of a diff block - } - if (parse_diff_ed(line, &lnum_orig, &count_orig, - &lnum_new, &count_new) == FAIL) { - continue; - } - } else { - assert(diffstyle == DIFF_UNIFIED); - if (STRNCMP(line, "@@ ", 3) != 0) { - continue; // not the start of a diff block + if (diffstyle == DIFF_NONE) { + // Determine diff style. + // ed like diff looks like this: + // {first}[,{last}]c{first}[,{last}] + // {first}a{first}[,{last}] + // {first}[,{last}]d{first} + // + // unified diff looks like this: + // --- file1 2018-03-20 13:23:35.783153140 +0100 + // +++ file2 2018-03-20 13:23:41.183156066 +0100 + // @@ -1,3 +1,5 @@ + if (isdigit(*line)) { + diffstyle = DIFF_ED; + } else if ((STRNCMP(line, "@@ ", 3) == 0)) { + diffstyle = DIFF_UNIFIED; + } else if ((STRNCMP(line, "--- ", 4) == 0) // -V501 + && (vim_fgets(linebuf, LBUFLEN, fd) == 0) // -V501 + && (STRNCMP(line, "+++ ", 4) == 0) + && (vim_fgets(linebuf, LBUFLEN, fd) == 0) // -V501 + && (STRNCMP(line, "@@ ", 3) == 0)) { + diffstyle = DIFF_UNIFIED; + } else { + // Format not recognized yet, skip over this line. Cygwin diff + // may put a warning at the start of the file. + continue; + } } - if (parse_diff_unified(line, &lnum_orig, &count_orig, - &lnum_new, &count_new) == FAIL) { - continue; + + if (diffstyle == DIFF_ED) { + if (!isdigit(*line)) { + continue; // not the start of a diff block + } + if (parse_diff_ed(line, hunk) == FAIL) { + continue; + } + } else { + assert(diffstyle == DIFF_UNIFIED); + if (STRNCMP(line, "@@ ", 3) != 0) { + continue; // not the start of a diff block + } + if (parse_diff_unified(line, hunk) == FAIL) { + continue; + } } } // Go over blocks before the change, for which orig and new are equal. // Copy blocks from orig to new. while (dp != NULL - && lnum_orig > dp->df_lnum[idx_orig] + dp->df_count[idx_orig]) { + && hunk->lnum_orig > dp->df_lnum[idx_orig] + dp->df_count[idx_orig]) { if (notset) { diff_copy_entry(dprev, dp, idx_orig, idx_new); } @@ -1622,19 +1644,19 @@ static void diff_read(int idx_orig, int idx_new, diffout_T *dout) } if ((dp != NULL) - && (lnum_orig <= dp->df_lnum[idx_orig] + dp->df_count[idx_orig]) - && (lnum_orig + count_orig >= dp->df_lnum[idx_orig])) { + && (hunk->lnum_orig <= dp->df_lnum[idx_orig] + dp->df_count[idx_orig]) + && (hunk->lnum_orig + hunk->count_orig >= dp->df_lnum[idx_orig])) { // New block overlaps with existing block(s). // First find last block that overlaps. for (dpl = dp; dpl->df_next != NULL; dpl = dpl->df_next) { - if (lnum_orig + count_orig < dpl->df_next->df_lnum[idx_orig]) { + if (hunk->lnum_orig + hunk->count_orig < dpl->df_next->df_lnum[idx_orig]) { break; } } // If the newly found block starts before the old one, set the // start back a number of lines. - off = dp->df_lnum[idx_orig] - lnum_orig; + off = dp->df_lnum[idx_orig] - hunk->lnum_orig; if (off > 0) { for (i = idx_orig; i < idx_new; ++i) { @@ -1642,15 +1664,15 @@ static void diff_read(int idx_orig, int idx_new, diffout_T *dout) dp->df_lnum[i] -= off; } } - dp->df_lnum[idx_new] = lnum_new; - dp->df_count[idx_new] = count_new; + dp->df_lnum[idx_new] = hunk->lnum_new; + dp->df_count[idx_new] = hunk->count_new; } else if (notset) { // new block inside existing one, adjust new block - dp->df_lnum[idx_new] = lnum_new + off; - dp->df_count[idx_new] = count_new - off; + dp->df_lnum[idx_new] = hunk->lnum_new + off; + dp->df_count[idx_new] = hunk->count_new - off; } else { // second overlap of new block with existing block - dp->df_count[idx_new] += count_new - count_orig + dp->df_count[idx_new] += hunk->count_new - hunk->count_orig + dpl->df_lnum[idx_orig] + dpl->df_count[idx_orig] - (dp->df_lnum[idx_orig] + @@ -1659,7 +1681,7 @@ static void diff_read(int idx_orig, int idx_new, diffout_T *dout) // Adjust the size of the block to include all the lines to the // end of the existing block or the new diff, whatever ends last. - off = (lnum_orig + count_orig) + off = (hunk->lnum_orig + hunk->count_orig) - (dpl->df_lnum[idx_orig] + dpl->df_count[idx_orig]); if (off < 0) { @@ -1691,10 +1713,10 @@ static void diff_read(int idx_orig, int idx_new, diffout_T *dout) // Allocate a new diffblock. dp = diff_alloc_new(curtab, dprev, dp); - dp->df_lnum[idx_orig] = lnum_orig; - dp->df_count[idx_orig] = count_orig; - dp->df_lnum[idx_new] = lnum_new; - dp->df_count[idx_new] = count_new; + dp->df_lnum[idx_orig] = hunk->lnum_orig; + dp->df_count[idx_orig] = hunk->count_orig; + dp->df_lnum[idx_new] = hunk->lnum_new; + dp->df_count[idx_new] = hunk->count_new; // Set values for other buffers, these must be equal to the // original buffer, otherwise there would have been a change @@ -1718,6 +1740,10 @@ static void diff_read(int idx_orig, int idx_new, diffout_T *dout) notset = true; } + if (!dio->dio_internal) { + xfree(hunk); + } + if (fd != NULL) { fclose(fd); } @@ -3026,8 +3052,7 @@ linenr_T diff_lnum_win(linenr_T lnum, win_T *wp) /// Handle an ED style diff line. /// Return FAIL if the line does not contain diff info. /// -static int parse_diff_ed(char_u *line, linenr_T *lnum_orig, long *count_orig, linenr_T *lnum_new, - long *count_new) +static int parse_diff_ed(char_u *line, diffhunk_T *hunk) { char_u *p; long f1, l1, f2, l2; @@ -3061,18 +3086,18 @@ static int parse_diff_ed(char_u *line, linenr_T *lnum_orig, long *count_orig, li } if (difftype == 'a') { - *lnum_orig = f1 + 1; - *count_orig = 0; + hunk->lnum_orig = f1 + 1; + hunk->count_orig = 0; } else { - *lnum_orig = f1; - *count_orig = l1 - f1 + 1; + hunk->lnum_orig = f1; + hunk->count_orig = l1 - f1 + 1; } if (difftype == 'd') { - *lnum_new = f2 + 1; - *count_new = 0; + hunk->lnum_new = f2 + 1; + hunk->count_new = 0; } else { - *lnum_new = f2; - *count_new = l2 - f2 + 1; + hunk->lnum_new = f2; + hunk->count_new = l2 - f2 + 1; } return OK; } @@ -3081,8 +3106,7 @@ static int parse_diff_ed(char_u *line, linenr_T *lnum_orig, long *count_orig, li /// Parses unified diff with zero(!) context lines. /// Return FAIL if there is no diff information in "line". /// -static int parse_diff_unified(char_u *line, linenr_T *lnum_orig, long *count_orig, - linenr_T *lnum_new, long *count_new) +static int parse_diff_unified(char_u *line, diffhunk_T *hunk) { char_u *p; long oldline, oldcount, newline, newcount; @@ -3120,10 +3144,10 @@ static int parse_diff_unified(char_u *line, linenr_T *lnum_orig, long *count_ori newline = 1; } - *lnum_orig = oldline; - *count_orig = oldcount; - *lnum_new = newline; - *count_new = newcount; + hunk->lnum_orig = oldline; + hunk->count_orig = oldcount; + hunk->lnum_new = newline; + hunk->count_new = newcount; return OK; } @@ -3135,25 +3159,16 @@ static int parse_diff_unified(char_u *line, linenr_T *lnum_orig, long *count_ori /// Callback function for the xdl_diff() function. /// Stores the diff output in a grow array. /// -static int xdiff_out(void *priv, mmbuffer_t *mb, int nbuf) +static int xdiff_out(long start_a, long count_a, long start_b, long count_b, void *priv) { diffout_T *dout = (diffout_T *)priv; - char_u *p; - - // The header line always comes by itself, text lines in at least two - // parts. We drop the text part. - if (nbuf > 1) { - return 0; - } - - // sanity check - if (STRNCMP(mb[0].ptr, "@@ ", 3) != 0) { - return 0; - } + diffhunk_T *p = xmalloc(sizeof(*p)); ga_grow(&dout->dout_ga, 1); - - p = vim_strnsave((char_u *)mb[0].ptr, mb[0].size); - ((char_u **)dout->dout_ga.ga_data)[dout->dout_ga.ga_len++] = p; + p->lnum_orig = start_a + 1; + p->count_orig = count_a; + p->lnum_new = start_b + 1; + p->count_new = count_b; + ((diffhunk_T **)dout->dout_ga.ga_data)[dout->dout_ga.ga_len++] = p; return 0; } diff --git a/src/nvim/edit.c b/src/nvim/edit.c index 2e3eec3642..00ffa7cba1 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -260,7 +260,7 @@ static colnr_T Insstart_blank_vcol; // vcol for first inserted blank static bool update_Insstart_orig = true; // set Insstart_orig to Insstart static char_u *last_insert = NULL; // the text of the previous insert, - // K_SPECIAL and CSI are escaped + // K_SPECIAL is escaped static int last_insert_skip; // nr of chars in front of previous insert static int new_insert_skip; // nr of chars in front of current insert static int did_restart_edit; // "restart_edit" when calling edit() @@ -663,8 +663,12 @@ static int insert_execute(VimState *state, int key) InsertState *const s = (InsertState *)state; if (stop_insert_mode) { // Insert mode ended, possibly from a callback. + if (key != K_IGNORE && key != K_NOP) { + vungetc(key); + } s->count = 0; s->nomove = true; + ins_compl_prep(ESC); return 0; } @@ -909,7 +913,7 @@ static int insert_handle_key(InsertState *s) ins_ctrl_o(); // don't move the cursor left when 'virtualedit' has "onemore". - if (ve_flags & VE_ONEMORE) { + if (get_ve_flags() & VE_ONEMORE) { ins_at_eol = false; s->nomove = true; } @@ -1076,13 +1080,21 @@ static int insert_handle_key(InsertState *s) case K_COMMAND: // some command do_cmdline(NULL, getcmdkeycmd, NULL, 0); + goto check_pum; + + case K_LUA: + map_execute_lua(); check_pum: + // nvim_select_popupmenu_item() can be called from the handling of + // K_EVENT, K_COMMAND, or K_LUA. // TODO(bfredl): Not entirely sure this indirection is necessary // but doing like this ensures using nvim_select_popupmenu_item is // equivalent to selecting the item with a typed key. if (pum_want.active) { if (pum_visible()) { + // Set this to NULL so that ins_complete() will update the message. + edit_submode_extra = NULL; insert_do_complete(s); if (pum_want.finish) { // accept the item and stop completion @@ -1600,8 +1612,8 @@ static void ins_ctrl_v(void) */ static int pc_status; #define PC_STATUS_UNSET 0 // pc_bytes was not set -#define PC_STATUS_RIGHT 1 // right halve of double-wide char -#define PC_STATUS_LEFT 2 // left halve of double-wide char +#define PC_STATUS_RIGHT 1 // right half of double-wide char +#define PC_STATUS_LEFT 2 // left half of double-wide char #define PC_STATUS_SET 3 // pc_bytes was filled static char_u pc_bytes[MB_MAXBYTES + 1]; // saved bytes static int pc_attr; @@ -3616,7 +3628,7 @@ static bool ins_compl_prep(int c) // Ignore end of Select mode mapping and mouse scroll buttons. if (c == K_SELECT || c == K_MOUSEDOWN || c == K_MOUSEUP || c == K_MOUSELEFT || c == K_MOUSERIGHT || c == K_EVENT - || c == K_COMMAND) { + || c == K_COMMAND || c == K_LUA) { return retval; } @@ -4982,7 +4994,7 @@ void ins_compl_check_keys(int frequency, int in_compl_func) */ static int ins_compl_key2dir(int c) { - if (c == K_EVENT || c == K_COMMAND) { + if (c == K_EVENT || c == K_COMMAND || c == K_LUA) { return pum_want.item < pum_selected_item ? BACKWARD : FORWARD; } if (c == Ctrl_P || c == Ctrl_L @@ -5012,7 +5024,7 @@ static int ins_compl_key2count(int c) { int h; - if (c == K_EVENT || c == K_COMMAND) { + if (c == K_EVENT || c == K_COMMAND || c == K_LUA) { int offset = pum_want.item - pum_selected_item; return abs(offset); } @@ -5046,6 +5058,7 @@ static bool ins_compl_use_match(int c) return false; case K_EVENT: case K_COMMAND: + case K_LUA: return pum_want.active && pum_want.insert; } return true; @@ -5624,8 +5637,12 @@ int get_literal(void) i = 0; for (;;) { nc = plain_vgetc(); - if (!(State & CMDLINE) - && MB_BYTE2LEN_CHECK(nc) == 1) { + if ((mod_mask & ~MOD_MASK_SHIFT) != 0) { + // A character with non-Shift modifiers should not be a valid + // character for i_CTRL-V_digit. + break; + } + if (!(State & CMDLINE) && MB_BYTE2LEN_CHECK(nc) == 1) { add_to_showcmd(nc); } if (nc == 'x' || nc == 'X') { @@ -5691,6 +5708,8 @@ int get_literal(void) --no_mapping; if (nc) { vungetc(nc); + // A character typed with i_CTRL-V_digit cannot have modifiers. + mod_mask = 0; } got_int = false; // CTRL-C typed after CTRL-V is not an interrupt return cc; @@ -6002,6 +6021,7 @@ static void internal_format(int textwidth, int second_indent, int flags, int for char_u *saved_text = NULL; colnr_T col; colnr_T end_col; + bool did_do_comment = false; virtcol = get_nolist_virtcol() + char2cells(c != NUL ? c : gchar_cursor()); @@ -6117,8 +6137,7 @@ static void internal_format(int textwidth, int second_indent, int flags, int for if (curwin->w_cursor.col <= (colnr_T)wantcol) { break; } - } else if ((cc >= 0x100 || !utf_allow_break_before(cc)) - && fo_multibyte) { + } else if ((cc >= 0x100 || !utf_allow_break_before(cc)) && fo_multibyte) { int ncc; bool allow_break; @@ -6275,11 +6294,18 @@ static void internal_format(int textwidth, int second_indent, int flags, int for + (fo_white_par ? OPENLINE_KEEPTRAIL : 0) + (do_comments ? OPENLINE_DO_COM : 0) + ((flags & INSCHAR_COM_LIST) ? OPENLINE_COM_LIST : 0), - ((flags & INSCHAR_COM_LIST) ? second_indent : old_indent)); + ((flags & INSCHAR_COM_LIST) ? second_indent : old_indent), + &did_do_comment); if (!(flags & INSCHAR_COM_LIST)) { old_indent = 0; } + // If a comment leader was inserted, may also do this on a following + // line. + if (did_do_comment) { + no_leader = false; + } + replace_offset = 0; if (first_line) { if (!(flags & INSCHAR_COM_LIST)) { @@ -6810,7 +6836,7 @@ void free_last_insert(void) /// Add character "c" to buffer "s" /// -/// Escapes the special meaning of K_SPECIAL and CSI, handles multi-byte +/// Escapes the special meaning of K_SPECIAL, handles multi-byte /// characters. /// /// @param[in] c Character to add. @@ -6824,7 +6850,7 @@ char_u *add_char2buf(int c, char_u *s) const int len = utf_char2bytes(c, temp); for (int i = 0; i < len; i++) { c = temp[i]; - // Need to escape K_SPECIAL and CSI like in the typeahead buffer. + // Need to escape K_SPECIAL like in the typeahead buffer. if (c == K_SPECIAL) { *s++ = K_SPECIAL; *s++ = KS_SPECIAL; @@ -6898,8 +6924,7 @@ int oneright(void) // move "l" bytes right, but don't end up on the NUL, unless 'virtualedit' // contains "onemore". - if (ptr[l] == NUL - && (ve_flags & VE_ONEMORE) == 0) { + if (ptr[l] == NUL && (get_ve_flags() & VE_ONEMORE) == 0) { return FAIL; } curwin->w_cursor.col += l; @@ -7106,9 +7131,7 @@ int stuff_inserted(int c, long count, int no_esc) stuffReadbuff((const char *)ptr); // A trailing "0" is inserted as "<C-V>048", "^" as "<C-V>^". if (last) { - stuffReadbuff((last == '0' - ? "\026\060\064\070" - : "\026^")); + stuffReadbuff(last == '0' ? "\026\060\064\070" : "\026^"); } } while (--count > 0); @@ -8021,7 +8044,7 @@ static bool ins_esc(long *count, int cmdchar, bool nomove) && !VIsual_active )) && !revins_on) { - if (curwin->w_cursor.coladd > 0 || ve_flags == VE_ALL) { + if (curwin->w_cursor.coladd > 0 || get_ve_flags() == VE_ALL) { oneleft(); if (restart_edit != NUL) { curwin->w_cursor.coladd++; @@ -8274,6 +8297,7 @@ static bool ins_bs(int c, int mode, int *inserted_space_p) int in_indent; int oldState; int cpc[MAX_MCO]; // composing characters + bool call_fix_indent = false; // can't delete anything in an empty file // can't backup past first character in buffer @@ -8417,6 +8441,8 @@ static bool ins_bs(int c, int mode, int *inserted_space_p) beginline(BL_WHITE); if (curwin->w_cursor.col < save_col) { mincol = curwin->w_cursor.col; + // should now fix the indent to match with the previous line + call_fix_indent = true; } curwin->w_cursor.col = save_col; } @@ -8551,6 +8577,11 @@ static bool ins_bs(int c, int mode, int *inserted_space_p) if (curwin->w_cursor.col <= 1) { did_ai = false; } + + if (call_fix_indent) { + fix_indent(); + } + // It's a little strange to put backspaces into the redo // buffer, but it makes auto-indent a lot easier to deal // with. @@ -9165,7 +9196,7 @@ static bool ins_eol(int c) AppendToRedobuff(NL_STR); bool i = open_line(FORWARD, has_format_option(FO_RET_COMS) ? OPENLINE_DO_COM : 0, - old_indent); + old_indent, NULL); old_indent = 0; can_cindent = true; // When inserting a line the cursor line must never be in a closed fold. diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 86384bc5b2..d95b9560c2 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -6,6 +6,7 @@ */ #include <math.h> +#include <stdlib.h> #include "auto/config.h" @@ -69,6 +70,7 @@ static char *e_nowhitespace static char *e_invalwindow = N_("E957: Invalid window number"); static char *e_lock_unlock = N_("E940: Cannot lock or unlock variable %s"); static char *e_write2 = N_("E80: Error while writing: %s"); +static char *e_string_list_or_blob_required = N_("E1098: String, List or Blob required"); // TODO(ZyX-I): move to eval/executor static char *e_letwrong = N_("E734: Wrong variable type for %s="); @@ -112,9 +114,11 @@ typedef struct { int fi_semicolon; // TRUE if ending in '; var]' int fi_varcount; // nr of variables in the list listwatch_T fi_lw; // keep an eye on the item used. - list_T *fi_list; // list being used + list_T *fi_list; // list being used int fi_bi; // index of blob blob_T *fi_blob; // blob being used + char_u *fi_string; // copy of string being used + int fi_byte_idx; // byte index in fi_string } forinfo_T; // values for vv_flags: @@ -763,6 +767,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) { @@ -2641,8 +2654,15 @@ void *eval_for_line(const char_u *arg, bool *errp, char_u **nextcmdp, int skip) fi->fi_blob = btv.vval.v_blob; } tv_clear(&tv); + } else if (tv.v_type == VAR_STRING) { + fi->fi_byte_idx = 0; + fi->fi_string = tv.vval.v_string; + tv.vval.v_string = NULL; + if (fi->fi_string == NULL) { + fi->fi_string = vim_strsave((char_u *)""); + } } else { - emsg(_(e_listblobreq)); + emsg(_(e_string_list_or_blob_required)); tv_clear(&tv); } } @@ -2679,6 +2699,22 @@ bool next_for_item(void *fi_void, char_u *arg) fi->fi_semicolon, fi->fi_varcount, false, NULL) == OK; } + if (fi->fi_string != NULL) { + const int len = utfc_ptr2len(fi->fi_string + fi->fi_byte_idx); + if (len == 0) { + return false; + } + typval_T tv; + tv.v_type = VAR_STRING; + tv.v_lock = VAR_FIXED; + tv.vval.v_string = vim_strnsave(fi->fi_string + fi->fi_byte_idx, len); + fi->fi_byte_idx += len; + const int result + = ex_let_vars(arg, &tv, true, fi->fi_semicolon, fi->fi_varcount, false, NULL) == OK; + xfree(tv.vval.v_string); + return result; + } + listitem_T *item = fi->fi_lw.lw_item; if (item == NULL) { return false; @@ -2698,12 +2734,16 @@ void free_for_info(void *fi_void) { forinfo_T *fi = (forinfo_T *)fi_void; - if (fi != NULL && fi->fi_list != NULL) { + if (fi == NULL) { + return; + } + if (fi->fi_list != NULL) { tv_list_watch_remove(fi->fi_list, &fi->fi_lw); tv_list_unref(fi->fi_list); - } - if (fi != NULL && fi->fi_blob != NULL) { + } else if (fi->fi_blob != NULL) { tv_blob_unref(fi->fi_blob); + } else { + xfree(fi->fi_string); } xfree(fi); } @@ -3218,9 +3258,8 @@ char_u *get_user_var_name(expand_T *xp, int idx) // b: variables // In cmdwin, the alternative buffer should be used. - hashtab_T *ht = (cmdwin_type != 0 && get_cmdline_type() == NUL) - ? &prevwin->w_buffer->b_vars->dv_hashtab - : &curbuf->b_vars->dv_hashtab; + hashtab_T *ht + = is_in_cmdwin() ? &prevwin->w_buffer->b_vars->dv_hashtab : &curbuf->b_vars->dv_hashtab; if (bdone < ht->ht_used) { if (bdone++ == 0) { hi = ht->ht_array; @@ -3235,9 +3274,7 @@ char_u *get_user_var_name(expand_T *xp, int idx) // w: variables // In cmdwin, the alternative window should be used. - ht = (cmdwin_type != 0 && get_cmdline_type() == NUL) - ? &prevwin->w_vars->dv_hashtab - : &curwin->w_vars->dv_hashtab; + ht = is_in_cmdwin() ? &prevwin->w_vars->dv_hashtab : &curwin->w_vars->dv_hashtab; if (wdone < ht->ht_used) { if (wdone++ == 0) { hi = ht->ht_array; @@ -4402,7 +4439,7 @@ static int eval_lambda(char_u **const arg, typval_T *const rettv, const bool eva rettv->v_type = VAR_UNKNOWN; int ret = get_lambda_tv(arg, rettv, evaluate); - if (ret == NOTDONE) { + if (ret != OK) { return FAIL; } else if (**arg != '(') { if (verbose) { @@ -6468,6 +6505,10 @@ void filter_map(typval_T *argvars, typval_T *rettv, int map) if (argvars[0].v_type == VAR_DICT) { vimvars[VV_KEY].vv_type = VAR_STRING; + const VarLockStatus prev_lock = d->dv_lock; + if (map && d->dv_lock == VAR_UNLOCKED) { + d->dv_lock = VAR_LOCKED; + } ht = &d->dv_hashtab; hash_lock(ht); todo = (int)ht->ht_used; @@ -6498,6 +6539,7 @@ void filter_map(typval_T *argvars, typval_T *rettv, int map) } } hash_unlock(ht); + d->dv_lock = prev_lock; } else if (argvars[0].v_type == VAR_BLOB) { vimvars[VV_KEY].vv_type = VAR_NUMBER; @@ -6530,6 +6572,10 @@ void filter_map(typval_T *argvars, typval_T *rettv, int map) assert(argvars[0].v_type == VAR_LIST); vimvars[VV_KEY].vv_type = VAR_NUMBER; + const VarLockStatus prev_lock = tv_list_locked(l); + if (map && tv_list_locked(l) == VAR_UNLOCKED) { + tv_list_set_lock(l, VAR_LOCKED); + } for (listitem_T *li = tv_list_first(l); li != NULL;) { if (map && var_check_lock(TV_LIST_ITEM_TV(li)->v_lock, arg_errmsg, @@ -6548,6 +6594,7 @@ void filter_map(typval_T *argvars, typval_T *rettv, int map) } idx++; } + tv_list_set_lock(l, prev_lock); } restore_vimvar(VV_KEY, &save_key); @@ -6956,10 +7003,9 @@ win_T *find_tabwin(typval_T *wvp, typval_T *tvp) /// @param off 1 for gettabwinvar() void getwinvar(typval_T *argvars, typval_T *rettv, int off) { - win_T *win, *oldcurwin; + win_T *win; dictitem_T *v; tabpage_T *tp = NULL; - tabpage_T *oldtabpage = NULL; bool done = false; if (off == 1) { @@ -6979,8 +7025,8 @@ void getwinvar(typval_T *argvars, typval_T *rettv, int off) // otherwise the window is not valid. Only do this when needed, // autocommands get blocked. bool need_switch_win = tp != curtab || win != curwin; - if (!need_switch_win - || switch_win(&oldcurwin, &oldtabpage, win, tp, true) == OK) { + switchwin_T switchwin; + if (!need_switch_win || switch_win(&switchwin, win, tp, true) == OK) { if (*varname == '&') { if (varname[1] == NUL) { // get all window-local options in a dict @@ -7008,7 +7054,7 @@ void getwinvar(typval_T *argvars, typval_T *rettv, int off) if (need_switch_win) { // restore previous notion of curwin - restore_win(oldcurwin, oldtabpage, true); + restore_win(&switchwin, true); } } emsg_off--; @@ -7299,12 +7345,19 @@ void mapblock_fill_dict(dict_T *const dict, const mapblock_T *const mp, long buf noremap_value = mp->m_noremap == REMAP_SCRIPT ? 2 : !!mp->m_noremap; } - if (compatible) { - tv_dict_add_str(dict, S_LEN("rhs"), (const char *)mp->m_orig_str); + if (mp->m_luaref != LUA_NOREF) { + tv_dict_add_nr(dict, S_LEN("callback"), mp->m_luaref); } else { - tv_dict_add_allocated_str(dict, S_LEN("rhs"), - str2special_save((const char *)mp->m_str, false, - true)); + if (compatible) { + tv_dict_add_str(dict, S_LEN("rhs"), (const char *)mp->m_orig_str); + } else { + tv_dict_add_allocated_str(dict, S_LEN("rhs"), + str2special_save((const char *)mp->m_str, false, + true)); + } + } + if (mp->m_desc != NULL) { + tv_dict_add_allocated_str(dict, S_LEN("desc"), xstrdup(mp->m_desc)); } tv_dict_add_allocated_str(dict, S_LEN("lhs"), lhs); tv_dict_add_nr(dict, S_LEN("noremap"), noremap_value); @@ -7503,11 +7556,9 @@ void setwinvar(typval_T *argvars, typval_T *rettv, int off) typval_T *varp = &argvars[off + 2]; if (win != NULL && varname != NULL && varp != NULL) { - win_T *save_curwin; - tabpage_T *save_curtab; bool need_switch_win = tp != curtab || win != curwin; - if (!need_switch_win - || switch_win(&save_curwin, &save_curtab, win, tp, true) == OK) { + switchwin_T switchwin; + if (!need_switch_win || switch_win(&switchwin, win, tp, true) == OK) { if (*varname == '&') { long numval; bool error = false; @@ -7529,7 +7580,7 @@ void setwinvar(typval_T *argvars, typval_T *rettv, int off) } } if (need_switch_win) { - restore_win(save_curwin, save_curtab, true); + restore_win(&switchwin, true); } } } @@ -7696,6 +7747,7 @@ bool callback_from_typval(Callback *const callback, typval_T *const arg) callback->type = kCallbackFuncref; } } else if (nlua_is_table_from_lua(arg)) { + // TODO(tjdvries): UnifiedCallback char_u *name = nlua_register_table_as_callable(arg); if (name != NULL) { @@ -7725,6 +7777,7 @@ bool callback_call(Callback *const callback, const int argcount_in, typval_T *co { partial_T *partial; char_u *name; + Array args = ARRAY_DICT_INIT; switch (callback->type) { case kCallbackFuncref: name = callback->data.funcref; @@ -7736,6 +7789,13 @@ bool callback_call(Callback *const callback, const int argcount_in, typval_T *co name = partial_name(partial); break; + case kCallbackLua: + ILOG(" We tryin to call dat dang lua ref "); + nlua_call_ref(callback->data.luaref, "aucmd", args, false, NULL); + + return false; + break; + case kCallbackNone: return false; break; @@ -8140,6 +8200,66 @@ char *save_tv_as_string(typval_T *tv, ptrdiff_t *const len, bool endnl) return ret; } +/// 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 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) { + return -1; + } + + if (lnum > buf->b_ml.ml_line_count) { + lnum = buf->b_ml.ml_line_count; + } + + char_u *str = ml_get_buf(buf, lnum, false); + + if (*str == NUL) { + 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 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 zero. +int buf_charidx_to_byteidx(buf_T *buf, int lnum, int charidx) +{ + if (buf == NULL || buf->b_ml.ml_mfp == NULL) { + return -1; + } + + if (lnum > buf->b_ml.ml_line_count) { + lnum = buf->b_ml.ml_line_count; + } + + char_u *str = ml_get_buf(buf, lnum, false); + + // Convert the character offset to a byte offset + char_u *t = str; + while (*t != NUL && --charidx > 0) { + t += utfc_ptr2len(t); + } + + return t - str; +} + /// Translate a VimL object into a position /// /// Accepts VAR_LIST and VAR_STRING objects. Does not give an error for invalid @@ -8148,9 +8268,11 @@ char *save_tv_as_string(typval_T *tv, ptrdiff_t *const len, bool endnl) /// @param[in] tv Object to translate. /// @param[in] dollar_lnum True when "$" is last line. /// @param[out] ret_fnum Set to fnum for marks. +/// @param[in] charcol True to return character column. /// /// @return Pointer to position or NULL in case of error (e.g. invalid type). -pos_T *var2fpos(const typval_T *const tv, const bool dollar_lnum, int *const ret_fnum) +pos_T *var2fpos(const typval_T *const tv, const bool dollar_lnum, int *const ret_fnum, + const bool charcol) FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL { static pos_T pos; @@ -8180,7 +8302,11 @@ pos_T *var2fpos(const typval_T *const tv, const bool dollar_lnum, int *const ret if (error) { return NULL; } - len = (long)STRLEN(ml_get(pos.lnum)); + if (charcol) { + len = mb_charlen(ml_get(pos.lnum)); + } else { + len = STRLEN(ml_get(pos.lnum)); + } // We accept "$" for the column number: last column. li = tv_list_find(l, 1L); @@ -8211,19 +8337,31 @@ pos_T *var2fpos(const typval_T *const tv, const bool dollar_lnum, int *const ret return NULL; } if (name[0] == '.') { // Cursor. - return &curwin->w_cursor; + pos = curwin->w_cursor; + if (charcol) { + pos.col = buf_byteidx_to_charidx(curbuf, pos.lnum, pos.col); + } + return &pos; } if (name[0] == 'v' && name[1] == NUL) { // Visual start. if (VIsual_active) { - return &VIsual; + pos = VIsual; + } else { + pos = curwin->w_cursor; + } + if (charcol) { + pos.col = buf_byteidx_to_charidx(curbuf, pos.lnum, pos.col); } - return &curwin->w_cursor; + return &pos; } if (name[0] == '\'') { // Mark. pp = getmark_buf_fnum(curbuf, (uint8_t)name[1], false, ret_fnum); if (pp == NULL || pp == (pos_T *)-1 || pp->lnum <= 0) { return NULL; } + if (charcol) { + pp->col = buf_byteidx_to_charidx(curbuf, pp->lnum, pp->col); + } return pp; } @@ -8249,22 +8387,24 @@ pos_T *var2fpos(const typval_T *const tv, const bool dollar_lnum, int *const ret pos.col = 0; } else { pos.lnum = curwin->w_cursor.lnum; - pos.col = (colnr_T)STRLEN(get_cursor_line_ptr()); + if (charcol) { + pos.col = (colnr_T)mb_charlen(get_cursor_line_ptr()); + } else { + pos.col = (colnr_T)STRLEN(get_cursor_line_ptr()); + } } return &pos; } return NULL; } -/* - * Convert list in "arg" into a position and optional file number. - * When "fnump" is NULL there is no file number, only 3 items. - * Note that the column is passed on as-is, the caller may want to decrement - * it to use 1 for the first column. - * Return FAIL when conversion is not possible, doesn't check the position for - * validity. - */ -int list2fpos(typval_T *arg, pos_T *posp, int *fnump, colnr_T *curswantp) +/// Convert list in "arg" into a position and optional file number. +/// When "fnump" is NULL there is no file number, only 3 items. +/// Note that the column is passed on as-is, the caller may want to decrement +/// it to use 1 for the first column. +/// Return FAIL when conversion is not possible, doesn't check the position for +/// validity. +int list2fpos(typval_T *arg, pos_T *posp, int *fnump, colnr_T *curswantp, bool charcol) { list_T *l; long i = 0; @@ -8300,6 +8440,15 @@ int list2fpos(typval_T *arg, pos_T *posp, int *fnump, colnr_T *curswantp) if (n < 0) { return FAIL; } + // If character position is specified, then convert to byte position + if (charcol) { + // Get the text for the specified line in a loaded buffer + buf_T *buf = buflist_findnr(fnump == NULL ? curbuf->b_fnum : *fnump); + if (buf == NULL || buf->b_ml.ml_mfp == NULL) { + return FAIL; + } + n = buf_charidx_to_byteidx(buf, posp->lnum, n) + 1; + } posp->col = n; n = tv_list_find_nr(l, i, NULL); // off @@ -8939,7 +9088,7 @@ static bool tv_is_luafunc(typval_T *tv) const char *skip_luafunc_name(const char *p) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { - while (ASCII_ISALNUM(*p) || *p == '_' || *p == '.' || *p == '\'') { + while (ASCII_ISALNUM(*p) || *p == '_' || *p == '-' || *p == '.' || *p == '\'') { p++; } return p; @@ -9220,10 +9369,31 @@ static hashtab_T *find_var_ht_dict(const char *name, const size_t name_len, cons } else if (*name == 'l' && funccal != NULL) { // local variable *d = &funccal->l_vars; } else if (*name == 's' // script variable - && (current_sctx.sc_sid > 0 || current_sctx.sc_sid == SID_STR) + && (current_sctx.sc_sid > 0 || current_sctx.sc_sid == SID_STR + || current_sctx.sc_sid == SID_LUA) && current_sctx.sc_sid <= ga_scripts.ga_len) { // For anonymous scripts without a script item, create one now so script vars can be used - if (current_sctx.sc_sid == SID_STR) { + if (current_sctx.sc_sid == SID_LUA) { + // try to resolve lua filename & line no so it can be shown in lastset messages. + nlua_set_sctx(¤t_sctx); + if (current_sctx.sc_sid != SID_LUA) { + // Great we have valid location. Now here this out we'll create a new + // script context with the name and lineno of this one. why ? + // for behavioral consistency. With this different anonymous exec from + // same file can't access each others script local stuff. We need to do + // this all other cases except this will act like that otherwise. + const LastSet last_set = (LastSet){ + .script_ctx = current_sctx, + .channel_id = LUA_INTERNAL_CALL, + }; + bool should_free; + // should_free is ignored as script_sctx will be resolved to a fnmae + // & new_script_item will consume it. + char_u *sc_name = get_scriptname(last_set, &should_free); + new_script_item(sc_name, ¤t_sctx.sc_sid); + } + } + if (current_sctx.sc_sid == SID_STR || current_sctx.sc_sid == SID_LUA) { new_script_item(NULL, ¤t_sctx.sc_sid); } *d = &SCRIPT_SV(current_sctx.sc_sid)->sv_dict; @@ -10508,12 +10678,13 @@ int modify_fname(char_u *src, bool tilde_file, size_t *usedlen, char_u **fnamep, char_u *s, *p, *pbuf; char_u dirname[MAXPATHL]; int c; - int has_fullname = 0; + bool has_fullname = false; + bool has_homerelative = false; repeat: // ":p" - full path/file_name if (src[*usedlen] == ':' && src[*usedlen + 1] == 'p') { - has_fullname = 1; + has_fullname = true; valid |= VALID_PATH; *usedlen += 2; @@ -10582,8 +10753,8 @@ repeat: } pbuf = NULL; // Need full path first (use expand_env() to remove a "~/") - if (!has_fullname) { - if (c == '.' && **fnamep == '~') { + if (!has_fullname && !has_homerelative) { + if (**fnamep == '~') { p = pbuf = expand_env_save(*fnamep); } else { p = pbuf = (char_u *)FullName_save((char *)*fnamep, FALSE); @@ -10592,18 +10763,33 @@ repeat: p = *fnamep; } - has_fullname = 0; + has_fullname = false; if (p != NULL) { if (c == '.') { os_dirname(dirname, MAXPATHL); - s = path_shorten_fname(p, dirname); - if (s != NULL) { - *fnamep = s; - if (pbuf != NULL) { - xfree(*bufp); // free any allocated file name - *bufp = pbuf; - pbuf = NULL; + if (has_homerelative) { + s = vim_strsave(dirname); + home_replace(NULL, s, dirname, MAXPATHL, true); + xfree(s); + } + size_t namelen = STRLEN(dirname); + + // Do not call shorten_fname() here since it removes the prefix + // even though the path does not have a prefix. + if (fnamencmp(p, dirname, namelen) == 0) { + p += namelen; + if (vim_ispathsep(*p)) { + while (*p && vim_ispathsep(*p)) { + p++; + } + *fnamep = p; + if (pbuf != NULL) { + // free any allocated file name + xfree(*bufp); + *bufp = pbuf; + pbuf = NULL; + } } } } else { @@ -10614,6 +10800,7 @@ repeat: *fnamep = s; xfree(*bufp); *bufp = s; + has_homerelative = true; } } xfree(pbuf); @@ -10990,10 +11177,7 @@ typval_T eval_call_provider(char *provider, char *method, list_T *arguments, boo bool eval_has_provider(const char *feat) { if (!strequal(feat, "clipboard") - && !strequal(feat, "python") && !strequal(feat, "python3") - && !strequal(feat, "python_compiled") - && !strequal(feat, "python_dynamic") && !strequal(feat, "python3_compiled") && !strequal(feat, "python3_dynamic") && !strequal(feat, "perl") diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua index e445a08227..05e91a658f 100644 --- a/src/nvim/eval.lua +++ b/src/nvim/eval.lua @@ -71,6 +71,7 @@ return { chanclose={args={1, 2}}, chansend={args=2}, char2nr={args={1, 2}, base=1}, + charcol={args=1, base=1}, charidx={args={2, 3}, base=1}, chdir={args=1, base=1}, cindent={args=1, base=1}, @@ -144,6 +145,7 @@ return { getchangelist={args={0, 1}, base=1}, getchar={args={0, 1}}, getcharmod={}, + getcharpos={args=1, base=1}, getcharsearch={}, getcharstr={args={0, 1}}, getcmdline={}, @@ -151,7 +153,8 @@ return { getcmdtype={}, getcmdwintype={}, getcompletion={args={2, 3}, base=1}, - getcurpos={}, + getcurpos={args={0, 1}, base=1}, + getcursorcharpos={args={0, 1}, base=1}, getcwd={args={0, 2}, base=1}, getenv={args=1, base=1}, getfontname={args={0, 1}}, @@ -246,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}, @@ -259,7 +264,7 @@ return { nextnonblank={args=1, base=1}, nr2char={args={1, 2}, base=1}, ['or']={args=2, base=1}, - pathshorten={args=1, base=1}, + pathshorten={args={1, 2}, base=1}, pow={args=2, base=1}, prevnonblank={args=1, base=1}, printf={args=varargs(1), base=2}, @@ -270,12 +275,14 @@ return { pum_getpos={}, pumvisible={}, py3eval={args=1, base=1}, - pyeval={args=1, base=1}, - pyxeval={args=1, base=1}, + pyeval={args=1, base=1, func="f_py3eval"}, + pyxeval={args=1, base=1, func="f_py3eval"}, perleval={args=1, base=1}, + rand={args={0, 1}, base=1}, 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={}, @@ -300,19 +307,21 @@ 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}, setbufline={args=3, base=3}, setbufvar={args=3, base=3}, + setcharpos={args=2, base=2}, setcharsearch={args=1, base=1}, setcmdpos={args=1, base=1}, + setcursorcharpos={args={1, 3}, base=1}, setenv={args=2, base=2}, setfperm={args=2, base=1}, setline={args=2, base=2}, @@ -348,6 +357,7 @@ return { spellsuggest={args={1, 3}, base=1}, split={args={1, 3}, base=1}, sqrt={args=1, base=1, func="float_op_wrapper", data="&sqrt"}, + srand={args={0, 1}, base=1}, stdpath={args=1}, str2float={args=1, base=1}, str2list={args={1, 2}, base=1}, @@ -413,6 +423,8 @@ return { win_gotoid={args=1, base=1}, win_id2tabwin={args=1, base=1}, win_id2win={args=1, base=1}, + win_move_separator={args=2, base=1}, + win_move_statusline={args=2, base=1}, win_screenpos={args=1, base=1}, win_splitmove={args={2, 3}, base=1}, winbufnr={args=1, base=1}, diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index 32026282cf..49dde537c3 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -22,6 +22,7 @@ #include "nvim/eval/encode.h" #include "nvim/eval/executor.h" #include "nvim/eval/funcs.h" +#include "nvim/eval/typval.h" #include "nvim/eval/userfunc.h" #include "nvim/ex_cmds2.h" #include "nvim/ex_docmd.h" @@ -97,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 /// @@ -893,6 +894,7 @@ static void f_call(typval_T *argvars, typval_T *rettv, FunPtr fptr) partial = argvars[0].vval.v_partial; func = partial_name(partial); } else if (nlua_is_table_from_lua(&argvars[0])) { + // TODO(tjdevries): UnifiedCallback func = nlua_register_table_as_callable(&argvars[0]); owned = true; } else { @@ -1019,6 +1021,49 @@ static void f_char2nr(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_number = utf_ptr2char((const char_u *)tv_get_string(&argvars[0])); } +/// Get the current cursor column and store it in 'rettv'. If 'charcol' is true, +/// returns the character index of the column. Otherwise, returns the byte index +/// of the column. +static void get_col(typval_T *argvars, typval_T *rettv, bool charcol) +{ + colnr_T col = 0; + pos_T *fp; + int fnum = curbuf->b_fnum; + + fp = var2fpos(&argvars[0], false, &fnum, charcol); + if (fp != NULL && fnum == curbuf->b_fnum) { + if (fp->col == MAXCOL) { + // '> can be MAXCOL, get the length of the line then + if (fp->lnum <= curbuf->b_ml.ml_line_count) { + col = (colnr_T)STRLEN(ml_get(fp->lnum)) + 1; + } else { + col = MAXCOL; + } + } else { + col = fp->col + 1; + // col(".") when the cursor is on the NUL at the end of the line + // because of "coladd" can be seen as an extra column. + if (virtual_active() && fp == &curwin->w_cursor) { + char_u *p = get_cursor_pos_ptr(); + if (curwin->w_cursor.coladd >= + (colnr_T)win_chartabsize(curwin, p, curwin->w_virtcol - curwin->w_cursor.coladd)) { + int l; + if (*p != NUL && p[(l = utfc_ptr2len(p))] == NUL) { + col += l; + } + } + } + } + } + rettv->vval.v_number = col; +} + +/// "charcol()" function +static void f_charcol(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + get_col(argvars, rettv, true); +} + // "charidx()" function static void f_charidx(typval_T *argvars, typval_T *rettv, FunPtr fptr) { @@ -1147,45 +1192,10 @@ static void f_clearmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/* - * "col(string)" function - */ +/// "col(string)" function static void f_col(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - colnr_T col = 0; - pos_T *fp; - int fnum = curbuf->b_fnum; - - fp = var2fpos(&argvars[0], FALSE, &fnum); - if (fp != NULL && fnum == curbuf->b_fnum) { - if (fp->col == MAXCOL) { - // '> can be MAXCOL, get the length of the line then - if (fp->lnum <= curbuf->b_ml.ml_line_count) { - col = (colnr_T)STRLEN(ml_get(fp->lnum)) + 1; - } else { - col = MAXCOL; - } - } else { - col = fp->col + 1; - // col(".") when the cursor is on the NUL at the end of the line - // because of "coladd" can be seen as an extra column. - if (virtual_active() && fp == &curwin->w_cursor) { - char_u *p = get_cursor_pos_ptr(); - - if (curwin->w_cursor.coladd - >= (colnr_T)win_chartabsize(curwin, p, - (curwin->w_virtcol - - curwin->w_cursor.coladd))) { - int l; - - if (*p != NUL && p[(l = utfc_ptr2len(p))] == NUL) { - col += l; - } - } - } - } - } - rettv->vval.v_number = col; + get_col(argvars, rettv, false); } /* @@ -1548,24 +1558,21 @@ static void f_ctxsize(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_number = ctx_size(); } -/// "cursor(lnum, col)" function, or -/// "cursor(list)" -/// -/// Moves the cursor to the specified line and column. -/// -/// @returns 0 when the position could be set, -1 otherwise. -static void f_cursor(typval_T *argvars, typval_T *rettv, FunPtr fptr) +/// Set the cursor position. +/// If 'charcol' is true, then use the column number as a character offet. +/// Otherwise use the column number as a byte offset. +static void set_cursorpos(typval_T *argvars, typval_T *rettv, bool charcol) { long line, col; long coladd = 0; bool set_curswant = true; rettv->vval.v_number = -1; - if (argvars[1].v_type == VAR_UNKNOWN) { + if (argvars[0].v_type == VAR_LIST) { pos_T pos; colnr_T curswant = -1; - if (list2fpos(argvars, &pos, NULL, &curswant) == FAIL) { + if (list2fpos(argvars, &pos, NULL, &curswant, charcol) == FAIL) { emsg(_(e_invarg)); return; } @@ -1577,16 +1584,22 @@ static void f_cursor(typval_T *argvars, typval_T *rettv, FunPtr fptr) curwin->w_curswant = curswant - 1; set_curswant = false; } - } else { + } else if ((argvars[0].v_type == VAR_NUMBER || argvars[0].v_type == VAR_STRING) + && (argvars[1].v_type == VAR_NUMBER || argvars[1].v_type == VAR_STRING)) { line = tv_get_lnum(argvars); col = (long)tv_get_number_chk(&argvars[1], NULL); + if (charcol) { + 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); } + } else { + emsg(_(e_invarg)); + return; } - if (line < 0 || col < 0 - || coladd < 0) { - return; // type error; errmsg already given + if (line < 0 || col < 0 || coladd < 0) { + return; // type error; errmsg already given } if (line > 0) { curwin->w_cursor.lnum = line; @@ -1605,6 +1618,16 @@ static void f_cursor(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_number = 0; } +/// "cursor(lnum, col)" function, or +/// "cursor(list)" +/// +/// Moves the cursor to the specified line and column. +/// Returns 0 when the position could be set, -1 otherwise. +static void f_cursor(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + set_cursorpos(argvars, rettv, false); +} + // "debugbreak()" function static void f_debugbreak(typval_T *argvars, typval_T *rettv, FunPtr fptr) { @@ -2176,25 +2199,12 @@ static void f_win_execute(typval_T *argvars, typval_T *rettv, FunPtr fptr) { tabpage_T *tp; win_T *wp = win_id2wp_tp(argvars, &tp); - win_T *save_curwin; - tabpage_T *save_curtab; // Return an empty string if something fails. rettv->v_type = VAR_STRING; rettv->vval.v_string = NULL; if (wp != NULL && tp != NULL) { - pos_T curpos = wp->w_cursor; - if (switch_win_noblock(&save_curwin, &save_curtab, wp, tp, true) == - OK) { - check_cursor(); - execute_common(argvars, rettv, fptr, 1); - } - restore_win_noblock(save_curwin, save_curtab, true); - - // Update the status line if the cursor moved. - if (win_valid(wp) && !equalpos(curpos, wp->w_cursor)) { - wp->w_redr_status = true; - } + WIN_EXECUTE(wp, tp, execute_common(argvars, rettv, fptr, 1)); } } @@ -3180,7 +3190,7 @@ static void getchar_common(typval_T *argvars, typval_T *rettv) if (argvars[0].v_type == VAR_UNKNOWN) { // getchar(): blocking wait. // TODO(bfredl): deduplicate shared logic with state_enter ? - if (!(char_avail() || using_script() || input_available())) { + if (!char_avail()) { (void)os_inchar(NULL, 0, -1, 0, main_loop.events); if (!multiqueue_empty(main_loop.events)) { state_handle_k_event(); @@ -3216,7 +3226,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; @@ -3300,6 +3310,67 @@ static void f_getcharmod(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_number = mod_mask; } +static void getpos_both(typval_T *argvars, typval_T *rettv, bool getcurpos, bool charcol) +{ + pos_T *fp = NULL; + pos_T pos; + win_T *wp = curwin; + int fnum = -1; + + if (getcurpos) { + if (argvars[0].v_type != VAR_UNKNOWN) { + wp = find_win_by_nr_or_id(&argvars[0]); + if (wp != NULL) { + fp = &wp->w_cursor; + } + } else { + fp = &curwin->w_cursor; + } + if (fp != NULL && charcol) { + pos = *fp; + pos.col = buf_byteidx_to_charidx(wp->w_buffer, pos.lnum, pos.col); + fp = &pos; + } + } else { + fp = var2fpos(&argvars[0], true, &fnum, charcol); + } + + list_T *const l = tv_list_alloc_ret(rettv, 4 + getcurpos); + tv_list_append_number(l, (fnum != -1) ? (varnumber_T)fnum : (varnumber_T)0); + tv_list_append_number(l, ((fp != NULL) ? (varnumber_T)fp->lnum : (varnumber_T)0)); + tv_list_append_number(l, ((fp != NULL) + ? (varnumber_T)(fp->col == MAXCOL ? MAXCOL : fp->col + 1) + : (varnumber_T)0)); + tv_list_append_number(l, (fp != NULL) ? (varnumber_T)fp->coladd : (varnumber_T)0); + if (getcurpos) { + const int save_set_curswant = curwin->w_set_curswant; + const colnr_T save_curswant = curwin->w_curswant; + const colnr_T save_virtcol = curwin->w_virtcol; + + if (wp == curwin) { + update_curswant(); + } + tv_list_append_number(l, (wp == NULL) ? 0 : ((wp->w_curswant == MAXCOL) + ? (varnumber_T)MAXCOL + : (varnumber_T)wp->w_curswant + 1)); + + // Do not change "curswant", as it is unexpected that a get + // function has a side effect. + if (wp == curwin && save_set_curswant) { + curwin->w_set_curswant = save_set_curswant; + curwin->w_curswant = save_curswant; + curwin->w_virtcol = save_virtcol; + curwin->w_valid &= ~VALID_VIRTCOL; + } + } +} + +/// "getcharpos()" function +static void f_getcharpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + getpos_both(argvars, rettv, false, true); +} + /* * "getcharsearch()" function */ @@ -3795,7 +3866,7 @@ static void f_getmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr) } // "getmousepos()" function -void f_getmousepos(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_getmousepos(typval_T *argvars, typval_T *rettv, FunPtr fptr) { dict_T *d; win_T *wp; @@ -3855,61 +3926,21 @@ static void f_getpid(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_number = os_get_pid(); } -static void getpos_both(typval_T *argvars, typval_T *rettv, bool getcurpos) +/// "getcurpos(string)" function +static void f_getcurpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - pos_T *fp; - int fnum = -1; - - if (getcurpos) { - fp = &curwin->w_cursor; - } else { - fp = var2fpos(&argvars[0], true, &fnum); - } - - list_T *const l = tv_list_alloc_ret(rettv, 4 + (!!getcurpos)); - tv_list_append_number(l, (fnum != -1) ? (varnumber_T)fnum : (varnumber_T)0); - tv_list_append_number(l, ((fp != NULL) - ? (varnumber_T)fp->lnum - : (varnumber_T)0)); - tv_list_append_number(l, ((fp != NULL) - ? (varnumber_T)(fp->col == MAXCOL ? MAXCOL : fp->col + 1) - : (varnumber_T)0)); - tv_list_append_number(l, (fp != NULL) ? (varnumber_T)fp->coladd : (varnumber_T)0); - if (getcurpos) { - const int save_set_curswant = curwin->w_set_curswant; - const colnr_T save_curswant = curwin->w_curswant; - const colnr_T save_virtcol = curwin->w_virtcol; - - update_curswant(); - tv_list_append_number(l, (curwin->w_curswant == MAXCOL - ? (varnumber_T)MAXCOL - : (varnumber_T)curwin->w_curswant + 1)); - - // Do not change "curswant", as it is unexpected that a get - // function has a side effect. - if (save_set_curswant) { - curwin->w_set_curswant = save_set_curswant; - curwin->w_curswant = save_curswant; - curwin->w_virtcol = save_virtcol; - curwin->w_valid &= ~VALID_VIRTCOL; - } - } + getpos_both(argvars, rettv, true, false); } -/* - * "getcurpos(string)" function - */ -static void f_getcurpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_getcursorcharpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - getpos_both(argvars, rettv, true); + getpos_both(argvars, rettv, true, true); } -/* - * "getpos(string)" function - */ +/// "getpos(string)" function static void f_getpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - getpos_both(argvars, rettv, false); + getpos_both(argvars, rettv, false, false); } /// "getqflist()" functions @@ -3991,7 +4022,6 @@ static void f_getregtype(typval_T *argvars, typval_T *rettv, FunPtr fptr) MotionType reg_type = get_reg_type(regname, ®len); format_reg_type(reg_type, reglen, buf, ARRAY_SIZE(buf)); - rettv->v_type = VAR_STRING; rettv->vval.v_string = (char_u *)xstrdup(buf); } @@ -4032,8 +4062,6 @@ static void f_gettabinfo(typval_T *argvars, typval_T *rettv, FunPtr fptr) */ static void f_gettabvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - win_T *oldcurwin; - tabpage_T *oldtabpage; bool done = false; rettv->v_type = VAR_STRING; @@ -4047,7 +4075,8 @@ static void f_gettabvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) win_T *const window = tp == curtab || tp->tp_firstwin == NULL ? firstwin : tp->tp_firstwin; - if (switch_win(&oldcurwin, &oldtabpage, window, tp, true) == OK) { + switchwin_T switchwin; + if (switch_win(&switchwin, window, tp, true) == OK) { // look up the variable // Let gettabvar({nr}, "") return the "t:" dictionary. const dictitem_T *const v = find_var_in_ht(&tp->tp_vars->dv_hashtab, 't', @@ -4060,7 +4089,7 @@ static void f_gettabvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) } // restore previous notion of curwin - restore_win(oldcurwin, oldtabpage, true); + restore_win(&switchwin, true); } if (!done && argvars[2].v_type != VAR_UNKNOWN) { @@ -4214,7 +4243,7 @@ static void win_move_into_split(win_T *wp, win_T *targetwin, int size, int flags int height = wp->w_height; win_T *oldwin = curwin; - if (wp == targetwin) { + if (wp == targetwin || wp == aucmd_win) { return; } @@ -4434,6 +4463,12 @@ static void f_has(typval_T *argvars, typval_T *rettv, FunPtr fptr) #if defined(BSD) && !defined(__APPLE__) "bsd", #endif +#ifdef __linux__ + "linux", +#endif +#ifdef SUN_SYSTEM + "sun", +#endif #ifdef UNIX "unix", #endif @@ -4505,6 +4540,7 @@ static void f_has(typval_T *argvars, typval_T *rettv, FunPtr fptr) "mouse", "multi_byte", "multi_lang", + "nanotime", "num64", "packages", "path_extra", @@ -5882,22 +5918,20 @@ static void f_line(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (argvars[1].v_type != VAR_UNKNOWN) { tabpage_T *tp; - win_T *save_curwin; - tabpage_T *save_curtab; // use window specified in the second argument win_T *wp = win_id2wp_tp(&argvars[1], &tp); if (wp != NULL && tp != NULL) { - if (switch_win_noblock(&save_curwin, &save_curtab, wp, tp, true) - == OK) { + switchwin_T switchwin; + if (switch_win_noblock(&switchwin, wp, tp, true) == OK) { check_cursor(); - fp = var2fpos(&argvars[0], true, &fnum); + fp = var2fpos(&argvars[0], true, &fnum, false); } - restore_win_noblock(save_curwin, save_curtab, true); + restore_win_noblock(&switchwin, true); } } else { // use current window - fp = var2fpos(&argvars[0], true, &fnum); + fp = var2fpos(&argvars[0], true, &fnum, false); } if (fp != NULL) { @@ -5980,6 +6014,7 @@ static void get_maparg(typval_T *argvars, typval_T *rettv, int exact) { char_u *keys_buf = NULL; char_u *rhs; + LuaRef rhs_lua; int mode; int abbr = FALSE; int get_dict = FALSE; @@ -6016,7 +6051,7 @@ static void get_maparg(typval_T *argvars, typval_T *rettv, int exact) keys = replace_termcodes(keys, STRLEN(keys), &keys_buf, true, true, true, CPO_TO_CPO_FLAGS); - rhs = check_map(keys, mode, exact, false, abbr, &mp, &buffer_local); + rhs = check_map(keys, mode, exact, false, abbr, &mp, &buffer_local, &rhs_lua); xfree(keys_buf); if (!get_dict) { @@ -6027,10 +6062,15 @@ static void get_maparg(typval_T *argvars, typval_T *rettv, int exact) } else { rettv->vval.v_string = (char_u *)str2special_save((char *)rhs, false, false); } + } else if (rhs_lua != LUA_NOREF) { + size_t msglen = 100; + char *msg = (char *)xmalloc(msglen); + snprintf(msg, msglen, "<Lua function %d>", mp->m_luaref); + rettv->vval.v_string = (char_u *)msg; } } else { tv_dict_alloc_ret(rettv); - if (rhs != NULL) { + if (mp != NULL && (rhs != NULL || rhs_lua != LUA_NOREF)) { // Return a dictionary. mapblock_fill_dict(rettv->vval.v_dict, mp, buffer_local, true); } @@ -6808,12 +6848,23 @@ static void f_or(typval_T *argvars, typval_T *rettv, FunPtr fptr) */ static void f_pathshorten(typval_T *argvars, typval_T *rettv, FunPtr fptr) { + int trim_len = 1; + + if (argvars[1].v_type != VAR_UNKNOWN) { + trim_len = (int)tv_get_number(&argvars[1]); + if (trim_len < 1) { + trim_len = 1; + } + } + rettv->v_type = VAR_STRING; - const char *const s = tv_get_string_chk(&argvars[0]); - if (!s) { - return; + const char_u *p = (char_u *)tv_get_string_chk(&argvars[0]); + if (p == NULL) { + rettv->vval.v_string = NULL; + } else { + rettv->vval.v_string = vim_strsave(p); + shorten_dir_len(rettv->vval.v_string, trim_len); } - rettv->vval.v_string = shorten_dir((char_u *)xstrdup(s)); } /* @@ -6922,7 +6973,7 @@ static void f_prompt_setinterrupt(typval_T *argvars, typval_T *rettv, FunPtr fpt } /// "prompt_getprompt({buffer})" function -void f_prompt_getprompt(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_prompt_getprompt(typval_T *argvars, typval_T *rettv, FunPtr fptr) FUNC_ATTR_NONNULL_ALL { // return an empty string by default, e.g. it's not a prompt buffer @@ -6977,36 +7028,169 @@ static void f_pumvisible(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/* - * "pyeval()" function - */ -static void f_pyeval(typval_T *argvars, typval_T *rettv, FunPtr fptr) +/// "py3eval()" and "pyxeval()" functions (always python3) +static void f_py3eval(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - script_host_eval("python", argvars, rettv); + script_host_eval("python3", argvars, rettv); } -/* - * "py3eval()" function - */ -static void f_py3eval(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void init_srand(uint32_t *const x) + FUNC_ATTR_NONNULL_ALL { - script_host_eval("python3", argvars, rettv); +#ifndef MSWIN + static int dev_urandom_state = NOTDONE; // FAIL or OK once tried + + if (dev_urandom_state != FAIL) { + const int fd = os_open("/dev/urandom", O_RDONLY, 0); + struct { + union { + uint32_t number; + char bytes[sizeof(uint32_t)]; + } contents; + } buf; + + // Attempt reading /dev/urandom. + if (fd == -1) { + dev_urandom_state = FAIL; + } else { + buf.contents.number = 0; + if (read(fd, buf.contents.bytes, sizeof(uint32_t)) != sizeof(uint32_t)) { + dev_urandom_state = FAIL; + } else { + dev_urandom_state = OK; + *x = buf.contents.number; + } + os_close(fd); + } + } + if (dev_urandom_state != OK) { + // Reading /dev/urandom doesn't work, fall back to time(). +#endif + *x = time(NULL); +#ifndef MSWIN + } +#endif +} + +static inline uint32_t splitmix32(uint32_t *const x) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_ALWAYS_INLINE +{ + uint32_t z = (*x += 0x9e3779b9); + z = (z ^ (z >> 16)) * 0x85ebca6b; + z = (z ^ (z >> 13)) * 0xc2b2ae35; + return z ^ (z >> 16); +} + +static inline uint32_t shuffle_xoshiro128starstar(uint32_t *const x, uint32_t *const y, + uint32_t *const z, uint32_t *const w) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_ALWAYS_INLINE +{ +#define ROTL(x, k) ((x << k) | (x >> (32 - k))) + const uint32_t result = ROTL(*y * 5, 7) * 9; + const uint32_t t = *y << 9; + *z ^= *x; + *w ^= *y; + *y ^= *z; + *x ^= *w; + *z ^= t; + *w = ROTL(*w, 11); +#undef ROTL + return result; +} + +/// "rand()" function +static void f_rand(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + uint32_t result; + + if (argvars[0].v_type == VAR_UNKNOWN) { + static uint32_t gx, gy, gz, gw; + static bool initialized = false; + + // When no argument is given use the global seed list. + if (!initialized) { + // Initialize the global seed list. + uint32_t x; + init_srand(&x); + + gx = splitmix32(&x); + gy = splitmix32(&x); + gz = splitmix32(&x); + gw = splitmix32(&x); + initialized = true; + } + + result = shuffle_xoshiro128starstar(&gx, &gy, &gz, &gw); + } else if (argvars[0].v_type == VAR_LIST) { + list_T *const l = argvars[0].vval.v_list; + if (tv_list_len(l) != 4) { + goto theend; + } + + typval_T *const tvx = TV_LIST_ITEM_TV(tv_list_find(l, 0L)); + typval_T *const tvy = TV_LIST_ITEM_TV(tv_list_find(l, 1L)); + typval_T *const tvz = TV_LIST_ITEM_TV(tv_list_find(l, 2L)); + typval_T *const tvw = TV_LIST_ITEM_TV(tv_list_find(l, 3L)); + if (tvx->v_type != VAR_NUMBER) { + goto theend; + } + if (tvy->v_type != VAR_NUMBER) { + goto theend; + } + if (tvz->v_type != VAR_NUMBER) { + goto theend; + } + if (tvw->v_type != VAR_NUMBER) { + goto theend; + } + uint32_t x = tvx->vval.v_number; + uint32_t y = tvy->vval.v_number; + uint32_t z = tvz->vval.v_number; + uint32_t w = tvw->vval.v_number; + + result = shuffle_xoshiro128starstar(&x, &y, &z, &w); + + tvx->vval.v_number = (varnumber_T)x; + tvy->vval.v_number = (varnumber_T)y; + tvz->vval.v_number = (varnumber_T)z; + tvw->vval.v_number = (varnumber_T)w; + } else { + goto theend; + } + + rettv->v_type = VAR_NUMBER; + rettv->vval.v_number = (varnumber_T)result; + return; + +theend: + semsg(_(e_invarg2), tv_get_string(&argvars[0])); + rettv->v_type = VAR_NUMBER; + rettv->vval.v_number = -1; } -// "pyxeval()" function -static void f_pyxeval(typval_T *argvars, typval_T *rettv, FunPtr fptr) +/// "srand()" function +static void f_srand(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - init_pyxversion(); - if (p_pyx == 2) { - f_pyeval(argvars, rettv, NULL); + uint32_t x = 0; + + tv_list_alloc_ret(rettv, 4); + if (argvars[0].v_type == VAR_UNKNOWN) { + init_srand(&x); } else { - f_py3eval(argvars, rettv, NULL); + bool error = false; + x = tv_get_number_chk(&argvars[0], &error); + if (error) { + return; + } } + + tv_list_append_number(rettv->vval.v_list, (varnumber_T)splitmix32(&x)); + tv_list_append_number(rettv->vval.v_list, (varnumber_T)splitmix32(&x)); + tv_list_append_number(rettv->vval.v_list, (varnumber_T)splitmix32(&x)); + tv_list_append_number(rettv->vval.v_list, (varnumber_T)splitmix32(&x)); } -/// /// "perleval()" function -/// static void f_perleval(typval_T *argvars, typval_T *rettv, FunPtr fptr) { script_host_eval("perl", argvars, rettv); @@ -7873,6 +8057,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 @@ -7959,6 +8239,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. @@ -7976,7 +8257,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) { @@ -7987,6 +8268,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]); } } @@ -8006,11 +8288,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; @@ -8470,13 +8787,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) { @@ -8587,10 +8900,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; @@ -8863,6 +9173,49 @@ static void f_setbufvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } +/// Set the cursor or mark position. +/// If 'charpos' is TRUE, then use the column number as a character offet. +/// Otherwise use the column number as a byte offset. +static void set_position(typval_T *argvars, typval_T *rettv, bool charpos) +{ + pos_T pos; + int fnum; + colnr_T curswant = -1; + + rettv->vval.v_number = -1; + const char *const name = tv_get_string_chk(argvars); + if (name != NULL) { + if (list2fpos(&argvars[1], &pos, &fnum, &curswant, charpos) == OK) { + if (pos.col != MAXCOL && --pos.col < 0) { + pos.col = 0; + } + if (name[0] == '.' && name[1] == NUL) { + // set cursor; "fnum" is ignored + curwin->w_cursor = pos; + if (curswant >= 0) { + curwin->w_curswant = curswant - 1; + curwin->w_set_curswant = false; + } + check_cursor(); + rettv->vval.v_number = 0; + } else if (name[0] == '\'' && name[1] != NUL && name[2] == NUL) { + // set mark + if (setmark_pos((uint8_t)name[1], &pos, fnum) == OK) { + rettv->vval.v_number = 0; + } + } else { + emsg(_(e_invarg)); + } + } + } +} + +/// "setcharpos()" function +static void f_setcharpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + set_position(argvars, rettv, true); +} + static void f_setcharsearch(typval_T *argvars, typval_T *rettv, FunPtr fptr) { dict_T *d; @@ -8905,6 +9258,12 @@ static void f_setcmdpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } +/// "setcursorcharpos" function +static void f_setcursorcharpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + set_cursorpos(argvars, rettv, true); +} + /// "setenv()" function static void f_setenv(typval_T *argvars, typval_T *rettv, FunPtr fptr) { @@ -9161,41 +9520,10 @@ static void f_setmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/* - * "setpos()" function - */ +/// "setpos()" function static void f_setpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - pos_T pos; - int fnum; - colnr_T curswant = -1; - - rettv->vval.v_number = -1; - const char *const name = tv_get_string_chk(argvars); - if (name != NULL) { - if (list2fpos(&argvars[1], &pos, &fnum, &curswant) == OK) { - if (pos.col != MAXCOL && --pos.col < 0) { - pos.col = 0; - } - if (name[0] == '.' && name[1] == NUL) { - // set cursor; "fnum" is ignored - curwin->w_cursor = pos; - if (curswant >= 0) { - curwin->w_curswant = curswant - 1; - curwin->w_set_curswant = false; - } - check_cursor(); - rettv->vval.v_number = 0; - } else if (name[0] == '\'' && name[1] != NUL && name[2] == NUL) { - // set mark - if (setmark_pos((uint8_t)name[1], &pos, fnum) == OK) { - rettv->vval.v_number = 0; - } - } else { - emsg(_(e_invarg)); - } - } - } + set_position(argvars, rettv, false); } /* @@ -10210,6 +10538,10 @@ static void f_stdioopen(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (!tv_dict_get_callback(opts, S_LEN("on_stdin"), &on_stdin.cb)) { return; } + if (!tv_dict_get_callback(opts, S_LEN("on_print"), &on_print)) { + return; + } + on_stdin.buffered = tv_dict_get_number(opts, "stdin_buffered"); if (on_stdin.buffered && on_stdin.cb.type == kCallbackNone) { on_stdin.self = opts; @@ -10664,7 +10996,7 @@ static void f_stridx(typval_T *argvars, typval_T *rettv, FunPtr fptr) /* * "string()" function */ -void f_string(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void f_string(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->v_type = VAR_STRING; rettv->vval.v_string = (char_u *)encode_tv2string(&argvars[0], NULL); @@ -11874,7 +12206,7 @@ static void f_virtcol(typval_T *argvars, typval_T *rettv, FunPtr fptr) pos_T *fp; int fnum = curbuf->b_fnum; - fp = var2fpos(&argvars[0], FALSE, &fnum); + fp = var2fpos(&argvars[0], false, &fnum, false); if (fp != NULL && fp->lnum <= curbuf->b_ml.ml_line_count && fnum == curbuf->b_fnum) { // Limit the column to a valid value, getvvcol() doesn't check. @@ -11980,6 +12312,42 @@ static void f_win_id2win(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_number = win_id2win(argvars); } +/// "win_move_separator()" function +static void f_win_move_separator(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + win_T *wp; + int offset; + + rettv->vval.v_number = false; + + wp = find_win_by_nr_or_id(&argvars[0]); + if (wp == NULL || wp->w_floating) { + return; + } + + offset = (int)tv_get_number(&argvars[1]); + win_drag_vsep_line(wp, offset); + rettv->vval.v_number = true; +} + +/// "win_move_statusline()" function +static void f_win_move_statusline(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + win_T *wp; + int offset; + + rettv->vval.v_number = false; + + wp = find_win_by_nr_or_id(&argvars[0]); + if (wp == NULL || wp->w_floating) { + return; + } + + offset = (int)tv_get_number(&argvars[1]); + win_drag_status_line(wp, offset); + rettv->vval.v_number = true; +} + /// "winbufnr(nr)" function static void f_winbufnr(typval_T *argvars, typval_T *rettv, FunPtr fptr) { diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index 11bbaaed9c..44b003d106 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -8,6 +8,7 @@ #include <stdlib.h> #include <string.h> +#include "lauxlib.h" #include "nvim/ascii.h" #include "nvim/assert.h" #include "nvim/charset.h" @@ -28,11 +29,11 @@ #include "nvim/mbyte.h" #include "nvim/memory.h" #include "nvim/message.h" +#include "nvim/os/fileio.h" #include "nvim/os/input.h" #include "nvim/pos.h" #include "nvim/types.h" #include "nvim/vim.h" -#include "nvim/os/fileio.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "eval/typval.c.generated.h" @@ -1123,6 +1124,8 @@ bool tv_callback_equal(const Callback *cb1, const Callback *cb2) // FIXME: this is inconsistent with tv_equal but is needed for precision // maybe change dictwatcheradd to return a watcher id instead? return cb1->data.partial == cb2->data.partial; + case kCallbackLua: + return cb1->data.luaref == cb2->data.luaref; case kCallbackNone: return true; } @@ -1142,6 +1145,9 @@ void callback_free(Callback *callback) case kCallbackPartial: partial_unref(callback->data.partial); break; + case kCallbackLua: + NLUA_CLEAR_REF(callback->data.luaref); + break; case kCallbackNone: break; } @@ -1149,6 +1155,27 @@ void callback_free(Callback *callback) callback->data.funcref = NULL; } +/// Check if callback is freed +bool callback_is_freed(Callback callback) +{ + switch (callback.type) { + case kCallbackFuncref: + return false; + break; + case kCallbackPartial: + return false; + break; + case kCallbackLua: + return callback.data.luaref == LUA_NOREF; + break; + case kCallbackNone: + return true; + break; + } + + return true; +} + /// Copy a callback into a typval_T. void callback_put(Callback *cb, typval_T *tv) FUNC_ATTR_NONNULL_ALL @@ -1164,6 +1191,11 @@ void callback_put(Callback *cb, typval_T *tv) tv->vval.v_string = vim_strsave(cb->data.funcref); func_ref(cb->data.funcref); break; + case kCallbackLua: + // TODO(tjdevries): Unified Callback. + // At this point this isn't possible, but it'd be nice to put + // these handled more neatly in one place. + // So instead, we just do the default and put nil default: tv->v_type = VAR_SPECIAL; tv->vval.v_special = kSpecialVarNull; @@ -1185,6 +1217,9 @@ void callback_copy(Callback *dest, Callback *src) dest->data.funcref = vim_strsave(src->data.funcref); func_ref(src->data.funcref); break; + case kCallbackLua: + dest->data.luaref = api_new_luaref(src->data.luaref); + break; default: dest->data.funcref = NULL; break; @@ -2455,13 +2490,11 @@ static inline void _nothing_conv_dict_end(typval_T *const tv, dict_T **const dic #define TYPVAL_ENCODE_NAME nothing #define TYPVAL_ENCODE_FIRST_ARG_TYPE const void *const #define TYPVAL_ENCODE_FIRST_ARG_NAME ignored -#define TYPVAL_ENCODE_TRANSLATE_OBJECT_NAME #include "nvim/eval/typval_encode.c.h" #undef TYPVAL_ENCODE_SCOPE #undef TYPVAL_ENCODE_NAME #undef TYPVAL_ENCODE_FIRST_ARG_TYPE #undef TYPVAL_ENCODE_FIRST_ARG_NAME -#undef TYPVAL_ENCODE_TRANSLATE_OBJECT_NAME #undef TYPVAL_ENCODE_ALLOW_SPECIALS #undef TYPVAL_ENCODE_CONV_NIL @@ -3093,7 +3126,7 @@ linenr_T tv_get_lnum(const typval_T *const tv) linenr_T lnum = (linenr_T)tv_get_number_chk(tv, NULL); if (lnum == 0) { // No valid number, try using same function as line() does. int fnum; - pos_T *const fp = var2fpos(tv, true, &fnum); + pos_T *const fp = var2fpos(tv, true, &fnum, false); if (fp != NULL) { lnum = fp->lnum; } @@ -3207,8 +3240,9 @@ const char *tv_get_string_buf_chk(const typval_T *const tv, char *const buf) case VAR_BLOB: case VAR_UNKNOWN: emsg(_(str_errors[tv->v_type])); - return false; + return NULL; } + abort(); return NULL; } diff --git a/src/nvim/eval/typval.h b/src/nvim/eval/typval.h index d1275d6512..40dc819754 100644 --- a/src/nvim/eval/typval.h +++ b/src/nvim/eval/typval.h @@ -72,16 +72,20 @@ typedef enum { kCallbackNone = 0, kCallbackFuncref, kCallbackPartial, + kCallbackLua, } CallbackType; typedef struct { union { char_u *funcref; partial_T *partial; + LuaRef luaref; } data; CallbackType type; } Callback; -#define CALLBACK_NONE ((Callback){ .type = kCallbackNone }) + +#define CALLBACK_INIT { .type = kCallbackNone } +#define CALLBACK_NONE ((Callback)CALLBACK_INIT) /// Structure holding dictionary watcher typedef struct dict_watcher { diff --git a/src/nvim/eval/userfunc.c b/src/nvim/eval/userfunc.c index eb241eb8ae..5764f9fbd4 100644 --- a/src/nvim/eval/userfunc.c +++ b/src/nvim/eval/userfunc.c @@ -2573,6 +2573,7 @@ void ex_function(exarg_T *eap) fp->uf_calls = 0; fp->uf_script_ctx = current_sctx; fp->uf_script_ctx.sc_lnum += sourcing_lnum_top; + nlua_set_sctx(&fp->uf_script_ctx); goto ret_free; diff --git a/src/nvim/event/loop.c b/src/nvim/event/loop.c index 892c46dd04..89fced59c5 100644 --- a/src/nvim/event/loop.c +++ b/src/nvim/event/loop.c @@ -75,7 +75,7 @@ bool loop_poll_events(Loop *loop, int ms) /// @note Event is queued into `fast_events`, which is processed outside of the /// primary `events` queue by loop_poll_events(). For `main_loop`, that /// means `fast_events` is NOT processed in an "editor mode" -/// (VimState.execute), so redraw and other side-effects are likely to be +/// (VimState.execute), so redraw and other side effects are likely to be /// skipped. /// @see loop_schedule_deferred void loop_schedule_fast(Loop *loop, Event event) diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c index 4965eb9c20..c7910e148d 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 @@ -980,8 +988,10 @@ int do_move(linenr_T line1, linenr_T line2, linenr_T dest) foldMoveRange(win, &win->w_folds, line1, line2, dest); } } - curbuf->b_op_start.lnum = dest - num_lines + 1; - curbuf->b_op_end.lnum = dest; + if (!cmdmod.lockmarks) { + curbuf->b_op_start.lnum = dest - num_lines + 1; + curbuf->b_op_end.lnum = dest; + } line_off = -num_lines; byte_off = -extent_byte; } else { @@ -991,10 +1001,14 @@ int do_move(linenr_T line1, linenr_T line2, linenr_T dest) foldMoveRange(win, &win->w_folds, dest + 1, line1 - 1, line2); } } - curbuf->b_op_start.lnum = dest + 1; - curbuf->b_op_end.lnum = dest + num_lines; + if (!cmdmod.lockmarks) { + curbuf->b_op_start.lnum = dest + 1; + curbuf->b_op_end.lnum = dest + num_lines; + } + } + if (!cmdmod.lockmarks) { + curbuf->b_op_start.col = curbuf->b_op_end.col = 0; } - curbuf->b_op_start.col = curbuf->b_op_end.col = 0; mark_adjust_nofold(last_line - num_lines + 1, last_line, -(last_line - dest - extra), 0L, kExtmarkNOOP); @@ -1057,9 +1071,11 @@ void ex_copy(linenr_T line1, linenr_T line2, linenr_T n) char_u *p; count = line2 - line1 + 1; - curbuf->b_op_start.lnum = n + 1; - curbuf->b_op_end.lnum = n + count; - curbuf->b_op_start.col = curbuf->b_op_end.col = 0; + if (!cmdmod.lockmarks) { + curbuf->b_op_start.lnum = n + 1; + curbuf->b_op_end.lnum = n + count; + curbuf->b_op_start.col = curbuf->b_op_end.col = 0; + } /* * there are three situations: @@ -1099,6 +1115,9 @@ void ex_copy(linenr_T line1, linenr_T line2, linenr_T n) } appended_lines_mark(n, count); + if (VIsual_active) { + check_pos(curbuf, &VIsual); + } msgmore((long)count); } @@ -1269,12 +1288,18 @@ static void do_filter(linenr_T line1, linenr_T line2, exarg_T *eap, char_u *cmd, char_u *cmd_buf; buf_T *old_curbuf = curbuf; int shell_flags = 0; + const pos_T orig_start = curbuf->b_op_start; + const pos_T orig_end = curbuf->b_op_end; const int stmp = p_stmp; if (*cmd == NUL) { // no filter command return; } + const bool save_lockmarks = cmdmod.lockmarks; + // Temporarily disable lockmarks since that's needed to propagate changed + // regions of the buffer for foldUpdate(), linecount, etc. + cmdmod.lockmarks = false; cursor_save = curwin->w_cursor; linecount = line2 - line1 + 1; @@ -1349,7 +1374,7 @@ static void do_filter(linenr_T line1, linenr_T line2, exarg_T *eap, char_u *cmd, ui_cursor_goto(Rows - 1, 0); if (do_out) { - if (u_save((line2), (linenr_T)(line2 + 1)) == FAIL) { + if (u_save(line2, (linenr_T)(line2 + 1)) == FAIL) { xfree(cmd_buf); goto error; } @@ -1455,10 +1480,15 @@ error: filterend: + cmdmod.lockmarks = save_lockmarks; if (curbuf != old_curbuf) { no_wait_return--; emsg(_("E135: *Filter* Autocommands must not change current buffer")); + } else if (cmdmod.lockmarks) { + curbuf->b_op_start = orig_start; + curbuf->b_op_end = orig_end; } + if (itmp != NULL) { os_remove((char *)itmp); } @@ -1913,7 +1943,7 @@ int do_write(exarg_T *eap) // If 'filetype' was empty try detecting it now. if (*curbuf->b_p_ft == NUL) { - if (au_has_group((char_u *)"filetypedetect")) { + if (augroup_exists("filetypedetect")) { (void)do_doautocmd((char_u *)"filetypedetect BufRead", true, NULL); } do_modelines(0); @@ -3006,7 +3036,12 @@ void ex_append(exarg_T *eap) did_undo = true; ml_append(lnum, theline, (colnr_T)0, false); - appended_lines_mark(lnum + (empty ? 1 : 0), 1L); + if (empty) { + // there are no marks below the inserted lines + appended_lines(lnum, 1L); + } else { + appended_lines_mark(lnum, 1L); + } xfree(theline); ++lnum; @@ -3025,15 +3060,16 @@ void ex_append(exarg_T *eap) // "start" is set to eap->line2+1 unless that position is invalid (when // eap->line2 pointed to the end of the buffer and nothing was appended) // "end" is set to lnum when something has been appended, otherwise - // it is the same than "start" -- Acevedo - curbuf->b_op_start.lnum = (eap->line2 < curbuf->b_ml.ml_line_count) ? - eap->line2 + 1 : curbuf->b_ml.ml_line_count; - if (eap->cmdidx != CMD_append) { - --curbuf->b_op_start.lnum; + // it is the same as "start" -- Acevedo + if (!cmdmod.lockmarks) { + curbuf->b_op_start.lnum + = (eap->line2 < curbuf->b_ml.ml_line_count) ? eap->line2 + 1 : curbuf->b_ml.ml_line_count; + if (eap->cmdidx != CMD_append) { + curbuf->b_op_start.lnum--; + } + curbuf->b_op_end.lnum = (eap->line2 < lnum) ? lnum : curbuf->b_op_start.lnum; + curbuf->b_op_start.col = curbuf->b_op_end.col = 0; } - curbuf->b_op_end.lnum = (eap->line2 < lnum) - ? lnum : curbuf->b_op_start.lnum; - curbuf->b_op_start.col = curbuf->b_op_end.col = 0; curwin->w_cursor.lnum = lnum; check_cursor_lnum(); beginline(BL_SOL | BL_FIX); @@ -3624,8 +3660,14 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout, bool do_buf_event, handle // We do it here once to avoid it to be replaced over and over again. // But don't do it when it starts with "\=", then it's an expression. assert(sub != NULL); + + bool sub_needs_free = false; if (!(sub[0] == '\\' && sub[1] == '=')) { + char_u *source = sub; sub = regtilde(sub, p_magic); + // When previewing, the new pattern allocated by regtilde() needs to be freed + // in this function because it will not be used or freed by regtilde() later. + sub_needs_free = preview && sub != source; } // Check for a match on each line. @@ -4346,10 +4388,12 @@ skip: } if (sub_nsubs > start_nsubs) { - // Set the '[ and '] marks. - curbuf->b_op_start.lnum = eap->line1; - curbuf->b_op_end.lnum = line2; - curbuf->b_op_start.col = curbuf->b_op_end.col = 0; + if (!cmdmod.lockmarks) { + // Set the '[ and '] marks. + curbuf->b_op_start.lnum = eap->line1; + curbuf->b_op_end.lnum = line2; + curbuf->b_op_start.col = curbuf->b_op_end.col = 0; + } if (!global_busy) { // when interactive leave cursor on the match @@ -4420,6 +4464,10 @@ skip: kv_destroy(preview_lines.subresults); + if (sub_needs_free) { + xfree(sub); + } + return preview_buf; #undef ADJUST_SUB_FIRSTLNUM #undef PUSH_PREVIEW_LINES @@ -5063,8 +5111,7 @@ int find_help_tags(const char_u *arg, int *num_matches, char_u ***matches, bool && ((arg[1] != NUL && arg[2] == NUL) || (vim_strchr((char_u *)"%_z@", arg[1]) != NULL && arg[2] != NUL))) { - STRCPY(d, "/\\\\"); - STRCPY(d + 3, arg + 1); + vim_snprintf((char *)d, IOSIZE, "/\\\\%s", arg + 1); // Check for "/\\_$", should be "/\\_\$" if (d[3] == '_' && d[4] == '$') { STRCPY(d + 4, "\\$"); @@ -5817,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; } } @@ -6102,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_cmds.lua b/src/nvim/ex_cmds.lua index c388373ac1..c2d40c8bb7 100644 --- a/src/nvim/ex_cmds.lua +++ b/src/nvim/ex_cmds.lua @@ -2097,19 +2097,19 @@ module.cmds = { command='python', flags=bit.bor(RANGE, EXTRA, NEEDARG, CMDWIN), addr_type='ADDR_LINES', - func='ex_python', + func='ex_python3', }, { command='pydo', flags=bit.bor(RANGE, DFLALL, EXTRA, NEEDARG, CMDWIN), addr_type='ADDR_LINES', - func='ex_pydo', + func='ex_pydo3', }, { command='pyfile', flags=bit.bor(RANGE, FILE1, NEEDARG, CMDWIN), addr_type='ADDR_LINES', - func='ex_pyfile', + func='ex_py3file', }, { command='py3', @@ -2139,25 +2139,25 @@ module.cmds = { command='pyx', flags=bit.bor(RANGE, EXTRA, NEEDARG, CMDWIN), addr_type='ADDR_LINES', - func='ex_pyx', + func='ex_python3', }, { command='pyxdo', flags=bit.bor(RANGE, DFLALL, EXTRA, NEEDARG, CMDWIN), addr_type='ADDR_LINES', - func='ex_pyxdo', + func='ex_pydo3', }, { command='pythonx', flags=bit.bor(RANGE, EXTRA, NEEDARG, CMDWIN), addr_type='ADDR_LINES', - func='ex_pyx', + func='ex_python3', }, { command='pyxfile', flags=bit.bor(RANGE, FILE1, NEEDARG, CMDWIN), addr_type='ADDR_LINES', - func='ex_pyxfile', + func='ex_py3file', }, { command='quit', @@ -2413,7 +2413,7 @@ module.cmds = { }, { command='set', - flags=bit.bor(TRLBAR, EXTRA, CMDWIN, SBOXOK), + flags=bit.bor(BANG, TRLBAR, EXTRA, CMDWIN, SBOXOK), addr_type='ADDR_NONE', func='ex_set', }, @@ -2425,13 +2425,13 @@ module.cmds = { }, { command='setglobal', - flags=bit.bor(TRLBAR, EXTRA, CMDWIN, SBOXOK), + flags=bit.bor(BANG, TRLBAR, EXTRA, CMDWIN, SBOXOK), addr_type='ADDR_NONE', func='ex_set', }, { command='setlocal', - flags=bit.bor(TRLBAR, EXTRA, CMDWIN, SBOXOK), + flags=bit.bor(BANG, TRLBAR, EXTRA, CMDWIN, SBOXOK), addr_type='ADDR_NONE', func='ex_set', }, diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c index 33f9477608..8666c7e33a 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> @@ -135,21 +136,6 @@ void ex_profile(exarg_T *eap) } } -void ex_python(exarg_T *eap) -{ - script_host_execute("python", eap); -} - -void ex_pyfile(exarg_T *eap) -{ - script_host_execute_file("python", eap); -} - -void ex_pydo(exarg_T *eap) -{ - script_host_do_range("python", eap); -} - void ex_ruby(exarg_T *eap) { script_host_execute("ruby", eap); @@ -1613,7 +1599,7 @@ void ex_compiler(exarg_T *eap) if (old_cur_comp != NULL) { old_cur_comp = vim_strsave(old_cur_comp); } - do_cmdline_cmd("command -nargs=* CompilerSet setlocal <args>"); + do_cmdline_cmd("command -nargs=* -keepscript CompilerSet setlocal <args>"); } do_unlet(S_LEN("g:current_compiler"), true); do_unlet(S_LEN("b:current_compiler"), true); @@ -1660,126 +1646,6 @@ void ex_options(exarg_T *eap) cmd_source((char_u *)SYS_OPTWIN_FILE, NULL); } -// Detect Python 3 or 2, and initialize 'pyxversion'. -void init_pyxversion(void) -{ - if (p_pyx == 0) { - if (eval_has_provider("python3")) { - p_pyx = 3; - } else if (eval_has_provider("python")) { - p_pyx = 2; - } - } -} - -// Does a file contain one of the following strings at the beginning of any -// line? -// "#!(any string)python2" => returns 2 -// "#!(any string)python3" => returns 3 -// "# requires python 2.x" => returns 2 -// "# requires python 3.x" => returns 3 -// otherwise return 0. -static int requires_py_version(char_u *filename) -{ - FILE *file; - int requires_py_version = 0; - int i, lines; - - lines = (int)p_mls; - if (lines < 0) { - lines = 5; - } - - file = os_fopen((char *)filename, "r"); - if (file != NULL) { - for (i = 0; i < lines; i++) { - if (vim_fgets(IObuff, IOSIZE, file)) { - break; - } - if (i == 0 && IObuff[0] == '#' && IObuff[1] == '!') { - // Check shebang. - if (strstr((char *)IObuff + 2, "python2") != NULL) { - requires_py_version = 2; - break; - } - if (strstr((char *)IObuff + 2, "python3") != NULL) { - requires_py_version = 3; - break; - } - } - IObuff[21] = '\0'; - if (STRCMP("# requires python 2.x", IObuff) == 0) { - requires_py_version = 2; - break; - } - if (STRCMP("# requires python 3.x", IObuff) == 0) { - requires_py_version = 3; - break; - } - } - fclose(file); - } - return requires_py_version; -} - - -// Source a python file using the requested python version. -static void source_pyx_file(exarg_T *eap, char_u *fname) -{ - exarg_T ex; - long int v = requires_py_version(fname); - - init_pyxversion(); - if (v == 0) { - // user didn't choose a preference, 'pyx' is used - v = p_pyx; - } - - // now source, if required python version is not supported show - // unobtrusive message. - if (eap == NULL) { - memset(&ex, 0, sizeof(ex)); - } else { - ex = *eap; - } - ex.arg = fname; - ex.cmd = (char_u *)(v == 2 ? "pyfile" : "pyfile3"); - - if (v == 2) { - ex_pyfile(&ex); - } else { - ex_py3file(&ex); - } -} - -// ":pyxfile {fname}" -void ex_pyxfile(exarg_T *eap) -{ - source_pyx_file(eap, eap->arg); -} - -// ":pyx" -void ex_pyx(exarg_T *eap) -{ - init_pyxversion(); - if (p_pyx == 2) { - ex_python(eap); - } else { - ex_python3(eap); - } -} - -// ":pyxdo" -void ex_pyxdo(exarg_T *eap) -{ - init_pyxversion(); - if (p_pyx == 2) { - ex_pydo(eap); - } else { - ex_pydo3(eap); - } -} - /// ":source [{fname}]" void ex_source(exarg_T *eap) { @@ -1957,7 +1823,9 @@ static int source_using_linegetter(void *cookie, LineGetter fgetline, const char sourcing_lnum = 0; const sctx_T save_current_sctx = current_sctx; - current_sctx.sc_sid = SID_STR; + if (current_sctx.sc_sid != SID_LUA) { + current_sctx.sc_sid = SID_STR; + } current_sctx.sc_seq = 0; current_sctx.sc_lnum = save_sourcing_lnum; funccal_entry_T entry; @@ -2038,7 +1906,6 @@ int do_source(char *fname, int check_other, int is_vimrc) char_u *fname_exp; char_u *firstline = NULL; int retval = FAIL; - static int last_current_SID_seq = 0; int save_debug_break_level = debug_break_level; scriptitem_T *si = NULL; proftime_T wait_start; @@ -2086,7 +1953,7 @@ int do_source(char *fname, int check_other, int is_vimrc) } if (cookie.fp == NULL) { - if (p_verbose > 0) { + if (p_verbose > 1) { verbose_enter(); if (sourcing_name == NULL) { smsg(_("could not source \"%s\""), fname); @@ -2162,37 +2029,8 @@ int do_source(char *fname, int check_other, int is_vimrc) funccal_entry_T funccalp_entry; save_funccal(&funccalp_entry); - // Check if this script was sourced before to finds its SID. - // If it's new, generate a new SID. - // Always use a new sequence number. const sctx_T save_current_sctx = current_sctx; - current_sctx.sc_seq = ++last_current_SID_seq; - current_sctx.sc_lnum = 0; - FileID file_id; - bool file_id_ok = os_fileid((char *)fname_exp, &file_id); - assert(script_items.ga_len >= 0); - for (current_sctx.sc_sid = script_items.ga_len; current_sctx.sc_sid > 0; - current_sctx.sc_sid--) { - si = &SCRIPT_ITEM(current_sctx.sc_sid); - // Compare dev/ino when possible, it catches symbolic links. - // Also compare file names, the inode may change when the file was edited. - bool file_id_equal = file_id_ok && si->file_id_valid - && os_fileid_equal(&(si->file_id), &file_id); - if (si->sn_name != NULL - && (file_id_equal || fnamecmp(si->sn_name, fname_exp) == 0)) { - break; - } - } - if (current_sctx.sc_sid == 0) { - si = new_script_item(fname_exp, ¤t_sctx.sc_sid); - fname_exp = vim_strsave(si->sn_name); // used for autocmd - if (file_id_ok) { - si->file_id_valid = true; - si->file_id = file_id; - } else { - si->file_id_valid = false; - } - } + si = get_current_script_id(fname_exp, ¤t_sctx); if (l_do_profiling == PROF_YES) { bool forceit = false; @@ -2225,16 +2063,14 @@ int do_source(char *fname, int check_other, int is_vimrc) firstline = p; } - if (path_with_extension((const char *)fname, "lua")) { - // TODO(shadmansaleh): Properly handle :verbose for lua - // For now change currennt_sctx before sourcing lua files - // So verbose doesn't say everything was done in line 1 since we don't know + if (path_with_extension((const char *)fname_exp, "lua")) { const sctx_T current_sctx_backup = current_sctx; const linenr_T sourcing_lnum_backup = sourcing_lnum; + current_sctx.sc_sid = SID_LUA; current_sctx.sc_lnum = 0; sourcing_lnum = 0; // Source the file as lua - nlua_exec_file((const char *)fname); + nlua_exec_file((const char *)fname_exp); current_sctx = current_sctx_backup; sourcing_lnum = sourcing_lnum_backup; } else { @@ -2307,6 +2143,52 @@ theend: } +/// Check if fname was sourced before to finds its SID. +/// If it's new, generate a new SID. +/// @param[in] fname file path of script +/// @param[out] ret_sctx sctx of this script +scriptitem_T *get_current_script_id(char_u *fname, sctx_T *ret_sctx) +{ + static int last_current_SID_seq = 0; + + sctx_T script_sctx = { .sc_seq = ++last_current_SID_seq, + .sc_lnum = 0, + .sc_sid = 0 }; + FileID file_id; + scriptitem_T *si = NULL; + + bool file_id_ok = os_fileid((char *)fname, &file_id); + assert(script_items.ga_len >= 0); + for (script_sctx.sc_sid = script_items.ga_len; script_sctx.sc_sid > 0; + script_sctx.sc_sid--) { + si = &SCRIPT_ITEM(script_sctx.sc_sid); + // Compare dev/ino when possible, it catches symbolic links. + // Also compare file names, the inode may change when the file was edited. + bool file_id_equal = file_id_ok && si->file_id_valid + && os_fileid_equal(&(si->file_id), &file_id); + if (si->sn_name != NULL + && (file_id_equal || fnamecmp(si->sn_name, fname) == 0)) { + break; + } + } + if (script_sctx.sc_sid == 0) { + si = new_script_item(vim_strsave(fname), &script_sctx.sc_sid); + if (file_id_ok) { + si->file_id_valid = true; + si->file_id = file_id; + } else { + si->file_id_valid = false; + } + } + if (ret_sctx != NULL) { + *ret_sctx = script_sctx; + } + + return si; +} + + + /// ":scriptnames" void ex_scriptnames(exarg_T *eap) { @@ -2323,9 +2205,13 @@ void ex_scriptnames(exarg_T *eap) for (int i = 1; i <= script_items.ga_len && !got_int; i++) { if (SCRIPT_ITEM(i).sn_name != NULL) { - home_replace(NULL, SCRIPT_ITEM(i).sn_name, - NameBuff, MAXPATHL, true); - smsg("%3d: %s", i, NameBuff); + home_replace(NULL, SCRIPT_ITEM(i).sn_name, NameBuff, MAXPATHL, true); + vim_snprintf((char *)IObuff, IOSIZE, "%3d: %s", i, NameBuff); + if (!message_filtered(IObuff)) { + msg_putchar('\n'); + msg_outtrans(IObuff); + line_breakcheck(); + } } } } diff --git a/src/nvim/ex_cmds_defs.h b/src/nvim/ex_cmds_defs.h index ea899b660b..92675499be 100644 --- a/src/nvim/ex_cmds_defs.h +++ b/src/nvim/ex_cmds_defs.h @@ -4,6 +4,7 @@ #include <stdbool.h> #include <stdint.h> +#include "nvim/eval/typval.h" #include "nvim/normal.h" #include "nvim/pos.h" // for linenr_T #include "nvim/regexp_defs.h" @@ -61,6 +62,7 @@ // current buffer is locked #define EX_MODIFY 0x100000 // forbidden in non-'modifiable' buffer #define EX_FLAGS 0x200000 // allow flags after count in argument +#define EX_KEEPSCRIPT 0x4000000 // keep sctx of where command was invoked #define EX_FILES (EX_XFILE | EX_EXTRA) // multiple extra files allowed #define EX_FILE1 (EX_FILES | EX_NOSPC) // 1 file, defaults to current file #define EX_WORD1 (EX_EXTRA | EX_NOSPC) // one extra word allowed @@ -90,6 +92,35 @@ typedef struct exarg exarg_T; typedef void (*ex_func_T)(exarg_T *eap); +// NOTE: These possible could be removed and changed so that +// Callback could take a "command" style string, and simply +// execute that (instead of it being a function). +// +// But it's still a bit weird to do that. +// +// Another option would be that we just make a callback reference to +// "execute($INPUT)" or something like that, so whatever the user +// sends in via autocmds is just executed via this. +// +// However, that would probably have some performance cost (probably +// very marginal, but still some cost either way). +typedef enum { + CALLABLE_NONE, + CALLABLE_EX, + CALLABLE_CB, +} AucmdExecutableType; + +typedef struct aucmd_executable_t AucmdExecutable; +struct aucmd_executable_t { + AucmdExecutableType type; + union { + char_u *cmd; + Callback cb; + } callable; +}; + +#define AUCMD_EXECUTABLE_INIT { .type = CALLABLE_NONE } + typedef char_u *(*LineGetter)(int, void *, int, bool); /// Structure for command definition. @@ -192,6 +223,7 @@ struct expand { int xp_context; // type of expansion size_t xp_pattern_len; // bytes in xp_pattern before cursor char_u *xp_arg; // completion function + LuaRef xp_luaref; // Ref to Lua completion function sctx_T xp_script_ctx; // SCTX for completion function int xp_backslash; // one of the XP_BS_ values #ifndef BACKSLASH_IN_FILENAME diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 230cbd4b60..d3203bcee8 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -67,8 +67,8 @@ #include "nvim/sign.h" #include "nvim/spell.h" #include "nvim/spellfile.h" -#include "nvim/strings.h" #include "nvim/state.h" +#include "nvim/strings.h" #include "nvim/syntax.h" #include "nvim/tag.h" #include "nvim/terminal.h" @@ -78,26 +78,14 @@ #include "nvim/vim.h" #include "nvim/window.h" +static char *e_no_such_user_defined_command_str = N_("E184: No such user-defined command: %s"); +static char *e_no_such_user_defined_command_in_current_buffer_str + = N_("E1237: No such user-defined command in current buffer: %s"); + static int quitmore = 0; static bool ex_pressedreturn = false; -typedef struct ucmd { - char_u *uc_name; // The command name - uint32_t uc_argt; // The argument type - char_u *uc_rep; // The command's replacement string - long uc_def; // The default value for a range/count - int uc_compl; // completion type - cmd_addr_T uc_addr_type; // The command's address type - sctx_T uc_script_ctx; // SCTX where the command was defined - char_u *uc_compl_arg; // completion argument if any -} ucmd_T; - -#define UC_BUFFER 1 // -buffer: local to current buffer - -static garray_T ucmds = { 0, 0, sizeof(ucmd_T), 4, NULL }; - -#define USER_CMD(i) (&((ucmd_T *)(ucmds.ga_data))[i]) -#define USER_CMD_GA(gap, i) (&((ucmd_T *)((gap)->ga_data))[i]) +garray_T ucmds = { 0, 0, sizeof(ucmd_T), 4, NULL }; // Whether a command index indicates a user command. #define IS_USER_CMDIDX(idx) ((int)(idx) < 0) @@ -1774,7 +1762,9 @@ static char_u *do_one_cmd(char_u **cmdlinep, int flags, cstack_T *cstack, LineGe ea.regname = *ea.arg++; // for '=' register: accept the rest of the line as an expression if (ea.arg[-1] == '=' && ea.arg[0] != NUL) { - set_expr_line(vim_strsave(ea.arg)); + if (!ea.skip) { + set_expr_line(vim_strsave(ea.arg)); + } ea.arg += STRLEN(ea.arg); } ea.arg = skipwhite(ea.arg); @@ -2713,10 +2703,8 @@ static char_u *find_ucmd(exarg_T *eap, char_u *p, int *full, expand_T *xp, int * bool amb_local = false; // Found ambiguous buffer-local command, // only full match global is accepted. - /* - * Look for buffer-local user commands first, then global ones. - */ - gap = &curbuf->b_ucmds; + // Look for buffer-local user commands first, then global ones. + gap = is_in_cmdwin() ? &prevwin->w_buffer->b_ucmds : &curbuf->b_ucmds; for (;;) { for (j = 0; j < gap->ga_len; j++) { uc = USER_CMD_GA(gap, j); @@ -2761,6 +2749,7 @@ static char_u *find_ucmd(exarg_T *eap, char_u *p, int *full, expand_T *xp, int * *complp = uc->uc_compl; } if (xp != NULL) { + xp->xp_luaref = uc->uc_compl_luaref; xp->xp_arg = uc->uc_compl_arg; xp->xp_script_ctx = uc->uc_script_ctx; xp->xp_script_ctx.sc_lnum += sourcing_lnum; @@ -2906,13 +2895,17 @@ void f_fullcommand(typval_T *argvars, typval_T *rettv, FunPtr fptr) exarg_T ea; char_u *name = argvars[0].vval.v_string; - while (name[0] != NUL && name[0] == ':') { + rettv->v_type = VAR_STRING; + rettv->vval.v_string = NULL; + if (name == NULL) { + return; + } + + while (*name == ':') { name++; } name = skip_range(name, NULL); - rettv->v_type = VAR_STRING; - ea.cmd = (*name == '2' || *name == '3') ? name + 1 : name; ea.cmdidx = (cmdidx_T)0; char_u *p = find_command(&ea, NULL); @@ -2921,7 +2914,7 @@ void f_fullcommand(typval_T *argvars, typval_T *rettv, FunPtr fptr) } rettv->vval.v_string = vim_strsave(IS_USER_CMDIDX(ea.cmdidx) - ? get_user_commands(NULL, ea.useridx) + ? get_user_command_name(ea.useridx, ea.cmdidx) : cmdnames[ea.cmdidx].cmd_name); } @@ -4148,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) { @@ -4167,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; } } @@ -4385,7 +4386,7 @@ static char_u *replace_makeprg(exarg_T *eap, char_u *p, char_u **cmdlinep) ++i; } len = (int)STRLEN(p); - new_cmdline = xmalloc(STRLEN(program) + i * (len - 2) + 1); + new_cmdline = xmalloc(STRLEN(program) + (size_t)i * (len - 2) + 1); ptr = new_cmdline; while ((pos = (char_u *)strstr((char *)program, "$*")) != NULL) { i = (int)(pos - program); @@ -5166,13 +5167,32 @@ static int check_more(int message, bool forceit) char_u *get_command_name(expand_T *xp, int idx) { if (idx >= CMD_SIZE) { - return get_user_command_name(idx); + return expand_user_command_name(idx); } return cmdnames[idx].cmd_name; } -static 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, cmd_addr_T addr_type, bool force) +/// 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) FUNC_ATTR_NONNULL_ARG(1, 3) { ucmd_T *cmd = NULL; @@ -5226,6 +5246,8 @@ static int uc_add_command(char_u *name, size_t name_len, char_u *rep, uint32_t a XFREE_CLEAR(cmd->uc_rep); XFREE_CLEAR(cmd->uc_compl_arg); + NLUA_CLEAR_REF(cmd->uc_luaref); + NLUA_CLEAR_REF(cmd->uc_compl_luaref); break; } @@ -5255,14 +5277,19 @@ static int uc_add_command(char_u *name, size_t name_len, char_u *rep, uint32_t a cmd->uc_compl = compl; cmd->uc_script_ctx = current_sctx; cmd->uc_script_ctx.sc_lnum += sourcing_lnum; + nlua_set_sctx(&cmd->uc_script_ctx); cmd->uc_compl_arg = compl_arg; + cmd->uc_compl_luaref = compl_luaref; cmd->uc_addr_type = addr_type; + cmd->uc_luaref = luaref; return OK; fail: xfree(rep_buf); xfree(compl_arg); + NLUA_CLEAR_REF(luaref); + NLUA_CLEAR_REF(compl_luaref); return FAIL; } @@ -5301,6 +5328,7 @@ static const char *command_complete[] = [EXPAND_CSCOPE] = "cscope", [EXPAND_USER_DEFINED] = "custom", [EXPAND_USER_LIST] = "customlist", + [EXPAND_USER_LUA] = "<Lua function>", [EXPAND_DIFF_BUFFERS] = "diff_buffer", [EXPAND_DIRECTORIES] = "dir", [EXPAND_ENV_VARS] = "environment", @@ -5350,9 +5378,7 @@ static void uc_list(char_u *name, size_t name_len) uint32_t a; // In cmdwin, the alternative buffer should be used. - garray_T *gap = (cmdwin_type != 0 && get_cmdline_type() == NUL) - ? &prevwin->w_buffer->b_ucmds - : &curbuf->b_ucmds; + garray_T *gap = is_in_cmdwin() ? &prevwin->w_buffer->b_ucmds : &curbuf->b_ucmds; for (;;) { for (i = 0; i < gap->ga_len; i++) { cmd = USER_CMD_GA(gap, i); @@ -5524,6 +5550,8 @@ static int uc_scan_attr(char_u *attr, size_t len, uint32_t *argt, long *def, int *flags |= UC_BUFFER; } else if (STRNICMP(attr, "register", len) == 0) { *argt |= EX_REGSTR; + } else if (STRNICMP(attr, "keepscript", len) == 0) { + *argt |= EX_KEEPSCRIPT; } else if (STRNICMP(attr, "bar", len) == 0) { *argt |= EX_TRLBAR; } else { @@ -5678,23 +5706,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) { @@ -5702,8 +5725,8 @@ 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, - addr_type_arg, eap->forceit); + uc_add_command(name, name_len, p, argt, def, flags, compl, compl_arg, LUA_NOREF, + addr_type_arg, LUA_NOREF, eap->forceit); } } @@ -5717,11 +5740,13 @@ void ex_comclear(exarg_T *eap) uc_clear(&curbuf->b_ucmds); } -static void free_ucmd(ucmd_T *cmd) +void free_ucmd(ucmd_T *cmd) { xfree(cmd->uc_name); xfree(cmd->uc_rep); xfree(cmd->uc_compl_arg); + NLUA_CLEAR_REF(cmd->uc_compl_luaref); + NLUA_CLEAR_REF(cmd->uc_luaref); } /* @@ -5736,32 +5761,40 @@ static void ex_delcommand(exarg_T *eap) { int i = 0; ucmd_T *cmd = NULL; - int cmp = -1; + int res = -1; garray_T *gap; + const char_u *arg = eap->arg; + bool buffer_only = false; + + if (STRNCMP(arg, "-buffer", 7) == 0 && ascii_iswhite(arg[7])) { + buffer_only = true; + arg = skipwhite(arg + 7); + } gap = &curbuf->b_ucmds; for (;;) { for (i = 0; i < gap->ga_len; i++) { cmd = USER_CMD_GA(gap, i); - cmp = STRCMP(eap->arg, cmd->uc_name); - if (cmp <= 0) { + res = STRCMP(arg, cmd->uc_name); + if (res <= 0) { break; } } - if (gap == &ucmds || cmp == 0) { + if (gap == &ucmds || res == 0 || buffer_only) { break; } gap = &ucmds; } - if (cmp != 0) { - semsg(_("E184: No such user-defined command: %s"), eap->arg); + if (res != 0) { + semsg(_(buffer_only + ? e_no_such_user_defined_command_in_current_buffer_str + : e_no_such_user_defined_command_str), + arg); return; } - xfree(cmd->uc_name); - xfree(cmd->uc_rep); - xfree(cmd->uc_compl_arg); + free_ucmd(cmd); --gap->ga_len; @@ -5770,6 +5803,30 @@ static void ex_delcommand(exarg_T *eap) } } +/// Split a string by unescaped whitespace (space & tab), used for f-args on Lua commands callback. +/// Similar to uc_split_args(), but does not allocate, add quotes, add commas and is an iterator. +/// +/// @note If no separator is found start = 0 and end = length - 1 +/// @param[in] arg String to split +/// @param[in] iter Iteration counter +/// @param[out] start Start of the split +/// @param[out] end End of the split +/// @param[in] length Length of the string +/// @return false if it's the last split (don't call again), true otherwise (call again). +bool uc_split_args_iter(const char_u *arg, int iter, int *start, int *end, int length) +{ + int pos; + *start = *end + (iter > 1 ? 2 : 0); // Skip whitespace after the first split + for (pos = *start; pos < length - 2; pos++) { + if (arg[pos] != '\\' && ascii_iswhite(arg[pos + 1])) { + *end = pos; + return true; + } + } + *end = length - 1; + return false; +} + /* * split and quote args for <f-args> */ @@ -5843,7 +5900,7 @@ static char_u *uc_split_args(char_u *arg, size_t *lenp) return buf; } -static size_t add_cmd_modifier(char_u *buf, char *mod_str, bool *multi_mods) +static size_t add_cmd_modifier(char *buf, char *mod_str, bool *multi_mods) { size_t result = STRLEN(mod_str); if (*multi_mods) { @@ -6035,7 +6092,7 @@ static size_t uc_check_code(char_u *code, size_t len, char_u *buf, ucmd_T *cmd, break; } - case ct_MODS: { + case ct_MODS: result = quote ? 2 : 0; if (buf != NULL) { if (quote) { @@ -6044,76 +6101,13 @@ static size_t uc_check_code(char_u *code, size_t len, char_u *buf, ucmd_T *cmd, *buf = '\0'; } - bool multi_mods = false; - - // :aboveleft and :leftabove - if (cmdmod.split & WSP_ABOVE) { - result += add_cmd_modifier(buf, "aboveleft", &multi_mods); - } - // :belowright and :rightbelow - if (cmdmod.split & WSP_BELOW) { - result += add_cmd_modifier(buf, "belowright", &multi_mods); - } - // :botright - if (cmdmod.split & WSP_BOT) { - result += add_cmd_modifier(buf, "botright", &multi_mods); - } - - typedef struct { - bool *set; - char *name; - } mod_entry_T; - static mod_entry_T mod_entries[] = { - { &cmdmod.browse, "browse" }, - { &cmdmod.confirm, "confirm" }, - { &cmdmod.hide, "hide" }, - { &cmdmod.keepalt, "keepalt" }, - { &cmdmod.keepjumps, "keepjumps" }, - { &cmdmod.keepmarks, "keepmarks" }, - { &cmdmod.keeppatterns, "keeppatterns" }, - { &cmdmod.lockmarks, "lockmarks" }, - { &cmdmod.noswapfile, "noswapfile" } - }; - // the modifiers that are simple flags - for (size_t i = 0; i < ARRAY_SIZE(mod_entries); i++) { - if (*mod_entries[i].set) { - result += add_cmd_modifier(buf, mod_entries[i].name, &multi_mods); - } - } - - // TODO(vim): How to support :noautocmd? - // TODO(vim): How to support :sandbox? + result += uc_mods((char *)buf); - // :silent - if (msg_silent > 0) { - result += add_cmd_modifier(buf, emsg_silent > 0 ? "silent!" : "silent", - &multi_mods); - } - // :tab - if (cmdmod.tab > 0) { - result += add_cmd_modifier(buf, "tab", &multi_mods); - } - // :topleft - if (cmdmod.split & WSP_TOP) { - result += add_cmd_modifier(buf, "topleft", &multi_mods); - } - - // TODO(vim): How to support :unsilent? - - // :verbose - if (p_verbose > 0) { - result += add_cmd_modifier(buf, "verbose", &multi_mods); - } - // :vertical - if (cmdmod.split & WSP_VERT) { - result += add_cmd_modifier(buf, "vertical", &multi_mods); - } if (quote && buf != NULL) { buf += result - 2; *buf = '"'; } break; - } case ct_REGISTER: result = eap->regname ? 1 : 0; @@ -6152,6 +6146,76 @@ static size_t uc_check_code(char_u *code, size_t len, char_u *buf, ucmd_T *cmd, return result; } +size_t uc_mods(char *buf) +{ + size_t result = 0; + bool multi_mods = false; + + // :aboveleft and :leftabove + if (cmdmod.split & WSP_ABOVE) { + result += add_cmd_modifier(buf, "aboveleft", &multi_mods); + } + // :belowright and :rightbelow + if (cmdmod.split & WSP_BELOW) { + result += add_cmd_modifier(buf, "belowright", &multi_mods); + } + // :botright + if (cmdmod.split & WSP_BOT) { + result += add_cmd_modifier(buf, "botright", &multi_mods); + } + + typedef struct { + bool *set; + char *name; + } mod_entry_T; + static mod_entry_T mod_entries[] = { + { &cmdmod.browse, "browse" }, + { &cmdmod.confirm, "confirm" }, + { &cmdmod.hide, "hide" }, + { &cmdmod.keepalt, "keepalt" }, + { &cmdmod.keepjumps, "keepjumps" }, + { &cmdmod.keepmarks, "keepmarks" }, + { &cmdmod.keeppatterns, "keeppatterns" }, + { &cmdmod.lockmarks, "lockmarks" }, + { &cmdmod.noswapfile, "noswapfile" } + }; + // the modifiers that are simple flags + for (size_t i = 0; i < ARRAY_SIZE(mod_entries); i++) { + if (*mod_entries[i].set) { + result += add_cmd_modifier(buf, mod_entries[i].name, &multi_mods); + } + } + + // TODO(vim): How to support :noautocmd? + // TODO(vim): How to support :sandbox? + + // :silent + if (msg_silent > 0) { + result += add_cmd_modifier(buf, emsg_silent > 0 ? "silent!" : "silent", &multi_mods); + } + // :tab + if (cmdmod.tab > 0) { + result += add_cmd_modifier(buf, "tab", &multi_mods); + } + // :topleft + if (cmdmod.split & WSP_TOP) { + result += add_cmd_modifier(buf, "topleft", &multi_mods); + } + + // TODO(vim): How to support :unsilent? + + // :verbose + if (p_verbose > 0) { + result += add_cmd_modifier(buf, "verbose", &multi_mods); + } + // :vertical + if (cmdmod.split & WSP_VERT) { + result += add_cmd_modifier(buf, "vertical", &multi_mods); + } + + return result; +} + static void do_ucmd(exarg_T *eap) { char_u *buf; @@ -6174,6 +6238,11 @@ static void do_ucmd(exarg_T *eap) cmd = USER_CMD_GA(&curbuf->b_ucmds, eap->useridx); } + if (cmd->uc_luaref > 0) { + nlua_do_ucmd(cmd, eap); + return; + } + /* * Replace <> in the command by the arguments. * First round: "buf" is NULL, compute length, allocate "buf". @@ -6199,7 +6268,6 @@ static void do_ucmd(exarg_T *eap) // K_SPECIAL has been put in the buffer as K_SPECIAL // KS_SPECIAL KE_FILLER, like for mappings, but // do_cmdline() doesn't handle that, so convert it back. - // Also change K_SPECIAL KS_EXTRA KE_CSI into CSI. len = ksp - p; if (len > 0) { memmove(q, p, len); @@ -6252,15 +6320,19 @@ static void do_ucmd(exarg_T *eap) buf = xmalloc(totlen + 1); } - current_sctx.sc_sid = cmd->uc_script_ctx.sc_sid; + if ((cmd->uc_argt & EX_KEEPSCRIPT) == 0) { + current_sctx.sc_sid = cmd->uc_script_ctx.sc_sid; + } (void)do_cmdline(buf, eap->getline, eap->cookie, DOCMD_VERBOSE|DOCMD_NOWAIT|DOCMD_KEYTYPED); - current_sctx = save_current_sctx; + if ((cmd->uc_argt & EX_KEEPSCRIPT) == 0) { + current_sctx = save_current_sctx; + } xfree(buf); xfree(split_buf); } -static char_u *get_user_command_name(int idx) +static char_u *expand_user_command_name(int idx) { return get_user_commands(NULL, idx - CMD_SIZE); } @@ -6279,9 +6351,7 @@ char_u *get_user_commands(expand_T *xp FUNC_ATTR_UNUSED, int idx) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { // In cmdwin, the alternative buffer should be used. - const buf_T *const buf = (cmdwin_type != 0 && get_cmdline_type() == NUL) - ? prevwin->w_buffer - : curbuf; + const buf_T *const buf = is_in_cmdwin() ? prevwin->w_buffer : curbuf; if (idx < buf->b_ucmds.ga_len) { return USER_CMD_GA(&buf->b_ucmds, idx)->uc_name; @@ -6293,6 +6363,24 @@ char_u *get_user_commands(expand_T *xp FUNC_ATTR_UNUSED, int idx) return NULL; } +// Get the name of user command "idx". "cmdidx" can be CMD_USER or +// CMD_USER_BUF. +// Returns NULL if the command is not found. +static char_u *get_user_command_name(int idx, int cmdidx) +{ + if (cmdidx == CMD_USER && idx < ucmds.ga_len) { + return USER_CMD(idx)->uc_name; + } + if (cmdidx == CMD_USER_BUF) { + // In cmdwin, the alternative buffer should be used. + buf_T *buf = is_in_cmdwin() ? prevwin->w_buffer : curbuf; + if (idx < buf->b_ucmds.ga_len) { + return USER_CMD_GA(&buf->b_ucmds, idx)->uc_name; + } + } + return NULL; +} + /* * Function given to ExpandGeneric() to obtain the list of user command * attributes. @@ -6301,7 +6389,7 @@ char_u *get_user_cmd_flags(expand_T *xp, int idx) { static char *user_cmd_flags[] = { "addr", "bang", "bar", "buffer", "complete", "count", - "nargs", "range", "register" }; + "nargs", "range", "register", "keepscript" }; if (idx >= (int)ARRAY_SIZE(user_cmd_flags)) { return NULL; @@ -6577,7 +6665,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); } } @@ -6692,7 +6780,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); } @@ -6856,7 +6944,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; @@ -6871,7 +6959,7 @@ static void ex_hide(exarg_T *eap) if (win == NULL) { win = lastwin; } - win_close(win, false); + win_close(win, false, eap->forceit); } } } @@ -6929,7 +7017,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); } } @@ -7443,7 +7531,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) @@ -7489,9 +7577,8 @@ void do_exedit(exarg_T *eap, win_T *old_curwin) if ((eap->cmdidx == CMD_new || eap->cmdidx == CMD_tabnew || eap->cmdidx == CMD_tabedit - || eap->cmdidx == CMD_vnew - ) && *eap->arg == NUL) { - // ":new" or ":tabnew" without argument: edit an new empty buffer + || eap->cmdidx == CMD_vnew) && *eap->arg == NUL) { + // ":new" or ":tabnew" without argument: edit a new empty buffer setpcmark(); (void)do_ecmd(0, NULL, NULL, eap, ECMD_ONE, ECMD_HIDE + (eap->forceit ? ECMD_FORCEIT : 0), @@ -7531,7 +7618,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 @@ -7743,7 +7830,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); @@ -7784,7 +7871,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); } } @@ -7794,14 +7881,11 @@ void post_chdir(CdScope scope, bool trigger_dirchanged) /// @return true if the directory is successfully changed. bool changedir_func(char_u *new_dir, CdScope scope) { - char_u *tofree; - char_u *pdir = NULL; - bool retval = false; - if (new_dir == NULL || allbuf_locked()) { return false; } + char_u *pdir = NULL; // ":cd -": Change to previous directory if (STRCMP(new_dir, "-") == 0) { pdir = get_prevdir(scope); @@ -7812,26 +7896,12 @@ bool changedir_func(char_u *new_dir, CdScope scope) new_dir = pdir; } - // Free the previous directory - tofree = get_prevdir(scope); - if (os_dirname(NameBuff, MAXPATHL) == OK) { pdir = vim_strsave(NameBuff); } else { pdir = NULL; } - switch (scope) { - case kCdScopeTabpage: - curtab->tp_prevdir = pdir; - break; - case kCdScopeWindow: - curwin->w_prevdir = pdir; - break; - default: - prev_dir = pdir; - } - // For UNIX ":cd" means: go to home directory. // On other systems too if 'cdhome' is set. #if defined(UNIX) @@ -7844,17 +7914,33 @@ bool changedir_func(char_u *new_dir, CdScope scope) new_dir = NameBuff; } - bool dir_differs = new_dir == NULL || pdir == NULL - || pathcmp((char *)pdir, (char *)new_dir, -1) != 0; - if (new_dir != NULL && (!dir_differs || vim_chdir(new_dir) == 0)) { - post_chdir(scope, dir_differs); - retval = true; - } else { - emsg(_(e_failed)); + bool dir_differs = pdir == NULL || pathcmp((char *)pdir, (char *)new_dir, -1) != 0; + 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; + } } - xfree(tofree); - return retval; + char_u **pp; + switch (scope) { + case kCdScopeTabpage: + pp = &curtab->tp_prevdir; + break; + case kCdScopeWindow: + pp = &curwin->w_prevdir; + break; + default: + pp = &prev_dir; + } + xfree(*pp); + *pp = pdir; + + post_chdir(scope, dir_differs); + + return true; } /// ":cd", ":tcd", ":lcd", ":chdir", "tchdir" and ":lchdir". @@ -8102,6 +8188,7 @@ static void ex_put(exarg_T *eap) eap->forceit = TRUE; } curwin->w_cursor.lnum = eap->line2; + check_cursor_col(); do_put(eap->regname, NULL, eap->forceit ? BACKWARD : FORWARD, 1, PUT_LINE|PUT_CURSLINE); } @@ -8623,7 +8710,7 @@ static void ex_normal(exarg_T *eap) return; } - // vgetc() expects a CSI and K_SPECIAL to have been escaped. Don't do + // vgetc() expects K_SPECIAL to have been escaped. Don't do // this for the K_SPECIAL leading byte, otherwise special keys will not // work. { @@ -8632,8 +8719,7 @@ static void ex_normal(exarg_T *eap) // Count the number of characters to be escaped. for (p = eap->arg; *p != NUL; p++) { for (l = utfc_ptr2len(p) - 1; l > 0; l--) { - if (*++p == K_SPECIAL // trailbyte K_SPECIAL or CSI - ) { + if (*++p == K_SPECIAL) { // trailbyte K_SPECIAL len += 2; } } @@ -9542,23 +9628,34 @@ static void ex_filetype(exarg_T *eap) } } -/// Set all :filetype options ON if user did not explicitly set any to OFF. -void filetype_maybe_enable(void) +/// Source ftplugin.vim and indent.vim to create the necessary FileType +/// autocommands. We do this separately from filetype.vim so that these +/// autocommands will always fire first (and thus can be overriden) while still +/// allowing general filetype detection to be disabled in the user's init file. +void filetype_plugin_enable(void) { - if (filetype_detect == kNone) { - source_runtime(FILETYPE_FILE, true); - filetype_detect = kTrue; - } if (filetype_plugin == kNone) { - source_runtime(FTPLUGIN_FILE, true); + source_runtime(FTPLUGIN_FILE, DIP_ALL); filetype_plugin = kTrue; } if (filetype_indent == kNone) { - source_runtime(INDENT_FILE, true); + source_runtime(INDENT_FILE, DIP_ALL); filetype_indent = kTrue; } } +/// Enable filetype detection if the user did not explicitly disable it. +void filetype_maybe_enable(void) +{ + if (filetype_detect == kNone) { + // Normally .vim files are sourced before .lua files when both are + // supported, but we reverse the order here because we want the Lua + // autocommand to be defined first so that it runs first + source_runtime(FILETYPE_FILE, DIP_ALL); + filetype_detect = kTrue; + } +} + /// ":setfiletype [FALLBACK] {name}" static void ex_setfiletype(exarg_T *eap) { @@ -9585,18 +9682,6 @@ static void ex_digraphs(exarg_T *eap) } } -static void ex_set(exarg_T *eap) -{ - int flags = 0; - - if (eap->cmdidx == CMD_setlocal) { - flags = OPT_LOCAL; - } else if (eap->cmdidx == CMD_setglobal) { - flags = OPT_GLOBAL; - } - (void)do_set(eap->arg, flags); -} - void set_no_hlsearch(bool flag) { no_hlsearch = flag; @@ -9831,6 +9916,7 @@ Dictionary commands_array(buf_T *buf) PUT(d, "bang", BOOLEAN_OBJ(!!(cmd->uc_argt & EX_BANG))); PUT(d, "bar", BOOLEAN_OBJ(!!(cmd->uc_argt & EX_TRLBAR))); PUT(d, "register", BOOLEAN_OBJ(!!(cmd->uc_argt & EX_REGSTR))); + PUT(d, "keepscript", BOOLEAN_OBJ(!!(cmd->uc_argt & EX_KEEPSCRIPT))); switch (cmd->uc_argt & (EX_EXTRA | EX_NOSPC | EX_NEEDARG)) { case 0: diff --git a/src/nvim/ex_docmd.h b/src/nvim/ex_docmd.h index a302d4a3c5..abf6ec347b 100644 --- a/src/nvim/ex_docmd.h +++ b/src/nvim/ex_docmd.h @@ -32,6 +32,26 @@ typedef struct { tasave_T tabuf; } save_state_T; +typedef struct ucmd { + char_u *uc_name; // The command name + uint32_t uc_argt; // The argument type + char_u *uc_rep; // The command's replacement string + long uc_def; // The default value for a range/count + int uc_compl; // completion type + cmd_addr_T uc_addr_type; // The command's address type + sctx_T uc_script_ctx; // SCTX where the command was defined + char_u *uc_compl_arg; // completion argument if any + LuaRef uc_compl_luaref; // Reference to Lua completion function + LuaRef uc_luaref; // Reference to Lua function +} ucmd_T; + +#define UC_BUFFER 1 // -buffer: local to current buffer + +extern garray_T ucmds; + +#define USER_CMD(i) (&((ucmd_T *)(ucmds.ga_data))[i]) +#define USER_CMD_GA(gap, i) (&((ucmd_T *)((gap)->ga_data))[i]) + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "ex_docmd.h.generated.h" #endif diff --git a/src/nvim/ex_eval.c b/src/nvim/ex_eval.c index b1c59a607c..851828afcf 100644 --- a/src/nvim/ex_eval.c +++ b/src/nvim/ex_eval.c @@ -599,7 +599,7 @@ static void catch_exception(except_T *excp) { excp->caught = caught_stack; caught_stack = excp; - set_vim_var_string(VV_EXCEPTION, (char *)excp->value, -1); + set_vim_var_string(VV_EXCEPTION, excp->value, -1); if (*excp->throw_name != NUL) { if (excp->throw_lnum != 0) { vim_snprintf((char *)IObuff, IOSIZE, _("%s, line %" PRId64), @@ -650,7 +650,7 @@ static void finish_exception(except_T *excp) } caught_stack = caught_stack->caught; if (caught_stack != NULL) { - set_vim_var_string(VV_EXCEPTION, (char *)caught_stack->value, -1); + set_vim_var_string(VV_EXCEPTION, caught_stack->value, -1); if (*caught_stack->throw_name != NUL) { if (caught_stack->throw_lnum != 0) { vim_snprintf((char *)IObuff, IOSIZE, @@ -733,7 +733,7 @@ static void report_pending(int action, int pending, void *value) vim_snprintf((char *)IObuff, IOSIZE, mesg, _("Exception")); mesg = (char *)concat_str(IObuff, (char_u *)": %s"); - s = (char *)((except_T *)value)->value; + s = ((except_T *)value)->value; } else if ((pending & CSTP_ERROR) && (pending & CSTP_INTERRUPT)) { s = _("Error and interrupt"); } else if (pending & CSTP_ERROR) { @@ -1620,7 +1620,7 @@ void ex_endtry(exarg_T *eap) // the finally clause. The latter case need not be tested since then // anything pending has already been discarded. bool skip = did_emsg || got_int || current_exception - || !(cstack->cs_flags[cstack->cs_idx] & CSF_TRUE); + || !(cstack->cs_flags[cstack->cs_idx] & CSF_TRUE); if (!(cstack->cs_flags[cstack->cs_idx] & CSF_TRY)) { eap->errmsg = get_end_emsg(cstack); diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index ba2238ace2..30287cd6f2 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -311,7 +311,7 @@ static char_u *get_healthcheck_names(expand_T *xp, int idx) healthchecks.last_gen = last_prompt_id; } return idx < - (int)healthchecks.names.ga_len ? ((char_u **)(healthchecks.names.ga_data))[idx] : NULL; + healthchecks.names.ga_len ? ((char_u **)(healthchecks.names.ga_data))[idx] : NULL; } /// Transform healthcheck file path into it's name. @@ -772,7 +772,7 @@ static uint8_t *command_line_enter(int firstc, long count, int indent) ccline.cmdindent = (s->firstc > 0 ? s->indent : 0); // alloc initial ccline.cmdbuff - alloc_cmdbuff(exmode_active ? 250 : s->indent + 1); + alloc_cmdbuff(indent + 50); ccline.cmdlen = ccline.cmdpos = 0; ccline.cmdbuff[0] = NUL; @@ -1024,11 +1024,13 @@ static int command_line_execute(VimState *state, int key) CommandLineState *s = (CommandLineState *)state; s->c = key; - if (s->c == K_EVENT || s->c == K_COMMAND) { + if (s->c == K_EVENT || s->c == K_COMMAND || s->c == K_LUA) { if (s->c == K_EVENT) { state_handle_k_event(); - } else { + } else if (s->c == K_COMMAND) { do_cmdline(NULL, getcmdkeycmd, NULL, DOCMD_NOWAIT); + } else { + map_execute_lua(); } if (!cmdline_was_last_drawn) { @@ -4983,6 +4985,9 @@ static int ExpandFromContext(expand_T *xp, char_u *pat, int *num_file, char_u ** if (xp->xp_context == EXPAND_USER_LIST) { return ExpandUserList(xp, num_file, file); } + if (xp->xp_context == EXPAND_USER_LUA) { + return ExpandUserLua(xp, num_file, file); + } if (xp->xp_context == EXPAND_PACKADD) { return ExpandPackAddDir(pat, num_file, file); } @@ -5044,8 +5049,8 @@ static int ExpandFromContext(expand_T *xp, char_u *pat, int *num_file, char_u ** { EXPAND_SYNTAX, get_syntax_name, true, true }, { EXPAND_SYNTIME, get_syntime_arg, true, true }, { EXPAND_HIGHLIGHT, (ExpandFunc)get_highlight_name, true, true }, - { EXPAND_EVENTS, get_event_name, true, true }, - { EXPAND_AUGROUP, get_augroup_name, true, true }, + { EXPAND_EVENTS, expand_get_event_name, true, true }, + { EXPAND_AUGROUP, expand_get_augroup_name, true, true }, { EXPAND_CSCOPE, get_cscope_name, true, true }, { EXPAND_SIGN, get_sign_name, true, true }, { EXPAND_PROFILE, get_profile_name, true, true }, @@ -5411,6 +5416,35 @@ static int ExpandUserList(expand_T *xp, int *num_file, char_u ***file) return OK; } +static int ExpandUserLua(expand_T *xp, int *num_file, char_u ***file) +{ + typval_T rettv; + nlua_call_user_expand_func(xp, &rettv); + if (rettv.v_type != VAR_LIST) { + tv_clear(&rettv); + return FAIL; + } + + list_T *const retlist = rettv.vval.v_list; + + garray_T ga; + ga_init(&ga, (int)sizeof(char *), 3); + // Loop over the items in the list. + TV_LIST_ITER_CONST(retlist, li, { + if (TV_LIST_ITEM_TV(li)->v_type != VAR_STRING + || TV_LIST_ITEM_TV(li)->vval.v_string == NULL) { + continue; // Skip non-string items and empty strings. + } + + GA_APPEND(char *, &ga, xstrdup((const char *)TV_LIST_ITEM_TV(li)->vval.v_string)); + }); + tv_list_unref(retlist); + + *file = ga.ga_data; + *num_file = ga.ga_len; + return OK; +} + /// Expand color scheme, compiler or filetype names. /// Search from 'runtimepath': /// 'runtimepath'/{dirnames}/{pat}.vim @@ -6529,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 @@ -6553,6 +6587,13 @@ static int open_cmdwin(void) return cmdwin_result; } +/// @return true if in the cmdwin, not editing the command line. +bool is_in_cmdwin(void) + FUNC_ATTR_PURE +{ + return cmdwin_type != 0 && get_cmdline_type() == NUL; +} + /// Get script string /// /// Used for commands which accept either `:command script` or diff --git a/src/nvim/ex_session.c b/src/nvim/ex_session.c index a37cad9f2d..ca07174543 100644 --- a/src/nvim/ex_session.c +++ b/src/nvim/ex_session.c @@ -338,14 +338,26 @@ static int put_view(FILE *fd, win_T *wp, int add_edit, unsigned *flagp, int curr // Edit the file. Skip this when ":next" already did it. if (add_edit && (!did_next || wp->w_arg_idx_invalid)) { - char *fname_esc = - ses_escape_fname(ses_get_fname(wp->w_buffer, flagp), flagp); - // - // Load the file. - // - if (wp->w_buffer->b_ffname != NULL - && (!bt_nofile(wp->w_buffer) - || wp->w_buffer->terminal)) { + char *fname_esc = ses_escape_fname(ses_get_fname(wp->w_buffer, flagp), flagp); + if (bt_help(wp->w_buffer)) { + char *curtag = ""; + + // A help buffer needs some options to be set. + // First, create a new empty buffer with "buftype=help". + // Then ":help" will re-use both the buffer and the window and set + // the options, even when "options" is not in 'sessionoptions'. + if (0 < wp->w_tagstackidx && wp->w_tagstackidx <= wp->w_tagstacklen) { + curtag = (char *)wp->w_tagstack[wp->w_tagstackidx - 1].tagname; + } + + if (put_line(fd, "enew | setl bt=help") == FAIL + || fprintf(fd, "help %s", curtag) < 0 || put_eol(fd) == FAIL) { + return FAIL; + } + } else if (wp->w_buffer->b_ffname != NULL + && (!bt_nofile(wp->w_buffer) || wp->w_buffer->terminal)) { + // Load the file. + // Editing a file in this buffer: use ":edit file". // This may have side effects! (e.g., compressed or network file). // @@ -579,6 +591,24 @@ static int makeopens(FILE *fd, char_u *dirnow) return FAIL; } + // Put all buffers into the buffer list. + // Do it very early to preserve buffer order after loading session (which + // can be disrupted by prior `edit` or `tabedit` calls). + FOR_ALL_BUFFERS(buf) { + if (!(only_save_windows && buf->b_nwindows == 0) + && !(buf->b_help && !(ssop_flags & SSOP_HELP)) + && buf->b_fname != NULL + && buf->b_p_bl) { + if (fprintf(fd, "badd +%" PRId64 " ", + buf->b_wininfo == NULL + ? (int64_t)1L + : (int64_t)buf->b_wininfo->wi_fpos.lnum) < 0 + || ses_fname(fd, buf, &ssop_flags, true) == FAIL) { + return FAIL; + } + } + } + // the global argument list if (ses_arglist(fd, "argglobal", &global_alist.al_ga, !(ssop_flags & SSOP_CURDIR), &ssop_flags) == FAIL) { @@ -614,7 +644,10 @@ static int makeopens(FILE *fd, char_u *dirnow) // Similar to ses_win_rec() below, populate the tab pages first so // later local options won't be copied to the new tabs. FOR_ALL_TABS(tp) { - if (tp->tp_next != NULL && put_line(fd, "tabnew") == FAIL) { + // Use `bufhidden=wipe` to remove empty "placeholder" buffers once + // they are not needed. This prevents creating extra buffers (see + // cause of Vim patch 8.1.0829) + if (tp->tp_next != NULL && put_line(fd, "tabnew +setlocal\\ bufhidden=wipe") == FAIL) { return FAIL; } } @@ -792,25 +825,6 @@ static int makeopens(FILE *fd, char_u *dirnow) return FAIL; } - // Now put the remaining buffers into the buffer list. - // This is near the end, so that when 'hidden' is set we don't create extra - // buffers. If the buffer was already created with another command the - // ":badd" will have no effect. - FOR_ALL_BUFFERS(buf) { - if (!(only_save_windows && buf->b_nwindows == 0) - && !(buf->b_help && !(ssop_flags & SSOP_HELP)) - && buf->b_fname != NULL - && buf->b_p_bl) { - if (fprintf(fd, "badd +%" PRId64 " ", - buf->b_wininfo == NULL - ? (int64_t)1L - : (int64_t)buf->b_wininfo->wi_fpos.lnum) < 0 - || ses_fname(fd, buf, &ssop_flags, true) == FAIL) { - return FAIL; - } - } - } - // // Wipe out an empty unnamed buffer we started in. // diff --git a/src/nvim/extmark.c b/src/nvim/extmark.c index c4d8f75a21..48e57e20e1 100644 --- a/src/nvim/extmark.c +++ b/src/nvim/extmark.c @@ -48,28 +48,29 @@ # include "extmark.c.generated.h" #endif -static ExtmarkNs *buf_ns_ref(buf_T *buf, uint64_t ns_id, bool put) +static uint32_t *buf_ns_ref(buf_T *buf, uint32_t ns_id, bool put) { - return map_ref(uint64_t, ExtmarkNs)(buf->b_extmark_ns, ns_id, put); + return map_ref(uint32_t, uint32_t)(buf->b_extmark_ns, ns_id, put); } /// Create or update an extmark /// /// must not be used during iteration! -/// @returns the internal mark id -uint64_t extmark_set(buf_T *buf, uint64_t ns_id, uint64_t *idp, int row, colnr_T col, int end_row, - colnr_T end_col, Decoration *decor, bool right_gravity, bool end_right_gravity, - ExtmarkOp op) +void extmark_set(buf_T *buf, uint32_t ns_id, uint32_t *idp, int row, colnr_T col, int end_row, + colnr_T end_col, Decoration *decor, bool right_gravity, bool end_right_gravity, + ExtmarkOp op) { - ExtmarkNs *ns = buf_ns_ref(buf, ns_id, true); - assert(ns != NULL); - mtpos_t old_pos; - uint64_t mark = 0; - uint64_t id = idp ? *idp : 0; + uint32_t *ns = buf_ns_ref(buf, ns_id, true); + uint32_t id = idp ? *idp : 0; + bool decor_full = false; uint8_t decor_level = kDecorLevelNone; // no decor if (decor) { + if (kv_size(decor->virt_text) || kv_size(decor->virt_lines)) { + decor_full = true; + decor = xmemdup(decor, sizeof *decor); + } decor_level = kDecorLevelVisible; // decor affects redraw if (kv_size(decor->virt_lines)) { decor_level = kDecorLevelVirtLine; // decor affects horizontal size @@ -77,50 +78,64 @@ uint64_t extmark_set(buf_T *buf, uint64_t ns_id, uint64_t *idp, int row, colnr_T } if (id == 0) { - id = ns->free_id++; + id = ++*ns; } else { - uint64_t old_mark = map_get(uint64_t, uint64_t)(ns->map, id); - if (old_mark) { - if (old_mark & MARKTREE_PAIRED_FLAG || end_row > -1) { + MarkTreeIter itr[1] = { 0 }; + mtkey_t old_mark = marktree_lookup_ns(buf->b_marktree, ns_id, id, false, itr); + if (old_mark.id) { + if (mt_paired(old_mark) || end_row > -1) { extmark_del(buf, ns_id, id); } else { - MarkTreeIter itr[1] = { 0 }; - old_pos = marktree_lookup(buf->b_marktree, old_mark, itr); + // TODO(bfredl): we need to do more if "revising" a decoration mark. assert(itr->node); - if (old_pos.row == row && old_pos.col == col) { - ExtmarkItem it = map_del(uint64_t, ExtmarkItem)(buf->b_extmark_index, - old_mark); - if (it.decor) { - decor_remove(buf, row, row, it.decor); + if (old_mark.pos.row == row && old_mark.pos.col == col) { + if (marktree_decor_level(old_mark) > kDecorLevelNone) { + decor_remove(buf, row, row, old_mark.decor_full); + old_mark.decor_full = NULL; + } + old_mark.flags = 0; + if (decor_full) { + old_mark.decor_full = decor; + } else if (decor) { + old_mark.hl_id = decor->hl_id; + // Workaround: the gcc compiler of functionaltest-lua build + // apparently incapable of handling basic integer constants. + // This can be underanged as soon as we bump minimal gcc version. + old_mark.flags = (uint16_t)(old_mark.flags + | (decor->hl_eol ? (uint16_t)MT_FLAG_HL_EOL : (uint16_t)0)); + old_mark.priority = decor->priority; } - mark = marktree_revise(buf->b_marktree, itr, decor_level); + marktree_revise(buf->b_marktree, itr, decor_level, old_mark); goto revised; } marktree_del_itr(buf->b_marktree, itr, false); } } else { - ns->free_id = MAX(ns->free_id, id+1); + *ns = MAX(*ns, id); } } - if (end_row > -1) { - mark = marktree_put_pair(buf->b_marktree, - row, col, right_gravity, - end_row, end_col, end_right_gravity, decor_level); - } else { - mark = marktree_put(buf->b_marktree, row, col, right_gravity, decor_level); + mtkey_t mark = { { row, col }, ns_id, id, 0, + mt_flags(right_gravity, decor_level), 0, NULL }; + if (decor_full) { + mark.decor_full = decor; + } else if (decor) { + mark.hl_id = decor->hl_id; + // workaround: see above + mark.flags = (uint16_t)(mark.flags | (decor->hl_eol ? (uint16_t)MT_FLAG_HL_EOL : (uint16_t)0)); + mark.priority = decor->priority; } -revised: - map_put(uint64_t, ExtmarkItem)(buf->b_extmark_index, mark, - (ExtmarkItem){ ns_id, id, decor }); - map_put(uint64_t, uint64_t)(ns->map, id, mark); + marktree_put(buf->b_marktree, mark, end_row, end_col, end_right_gravity); +revised: if (op != kExtmarkNoUndo) { // TODO(bfredl): this doesn't cover all the cases and probably shouldn't // be done "prematurely". Any movement in undo history might necessitate - // adding new marks to old undo headers. - u_extmark_set(buf, mark, row, col); + // adding new marks to old undo headers. add a test case for this (doesn't + // fail extmark_spec.lua, and it should) + uint64_t mark_id = mt_lookup_id(ns_id, id, false); + u_extmark_set(buf, mark_id, row, col); } if (decor) { @@ -133,18 +148,17 @@ revised: if (idp) { *idp = id; } - return mark; } static bool extmark_setraw(buf_T *buf, uint64_t mark, int row, colnr_T col) { MarkTreeIter itr[1] = { 0 }; - mtpos_t pos = marktree_lookup(buf->b_marktree, mark, itr); - if (pos.row == -1) { + mtkey_t key = marktree_lookup(buf->b_marktree, mark, itr); + if (key.pos.row == -1) { return false; } - if (pos.row == row && pos.col == col) { + if (key.pos.row == row && key.pos.col == col) { return true; } @@ -154,45 +168,35 @@ static bool extmark_setraw(buf_T *buf, uint64_t mark, int row, colnr_T col) // Remove an extmark // Returns 0 on missing id -bool extmark_del(buf_T *buf, uint64_t ns_id, uint64_t id) +bool extmark_del(buf_T *buf, uint32_t ns_id, uint32_t id) { - ExtmarkNs *ns = buf_ns_ref(buf, ns_id, false); - if (!ns) { - return false; - } - - uint64_t mark = map_get(uint64_t, uint64_t)(ns->map, id); - if (!mark) { + MarkTreeIter itr[1] = { 0 }; + mtkey_t key = marktree_lookup_ns(buf->b_marktree, ns_id, id, false, itr); + if (!key.id) { return false; } - - MarkTreeIter itr[1] = { 0 }; - mtpos_t pos = marktree_lookup(buf->b_marktree, mark, itr); - assert(pos.row >= 0); + assert(key.pos.row >= 0); marktree_del_itr(buf->b_marktree, itr, false); - ExtmarkItem item = map_get(uint64_t, ExtmarkItem)(buf->b_extmark_index, mark); - mtpos_t pos2 = pos; - if (mark & MARKTREE_PAIRED_FLAG) { - pos2 = marktree_lookup(buf->b_marktree, mark|MARKTREE_END_FLAG, itr); - assert(pos2.row >= 0); + mtkey_t key2 = key; + + if (mt_paired(key)) { + key2 = marktree_lookup_ns(buf->b_marktree, ns_id, id, true, itr); + assert(key2.pos.row >= 0); marktree_del_itr(buf->b_marktree, itr, false); } - if (item.decor) { - decor_remove(buf, pos.row, pos2.row, item.decor); + if (marktree_decor_level(key) > kDecorLevelNone) { + decor_remove(buf, key.pos.row, key2.pos.row, key.decor_full); } - map_del(uint64_t, uint64_t)(ns->map, id); - map_del(uint64_t, ExtmarkItem)(buf->b_extmark_index, mark); - // TODO(bfredl): delete it from current undo header, opportunistically? return true; } // Free extmarks in a ns between lines // if ns = 0, it means clear all namespaces -bool extmark_clear(buf_T *buf, uint64_t ns_id, int l_row, colnr_T l_col, int u_row, colnr_T u_col) +bool extmark_clear(buf_T *buf, uint32_t ns_id, int l_row, colnr_T l_col, int u_row, colnr_T u_col) { if (!map_size(buf->b_extmark_ns)) { return false; @@ -201,68 +205,58 @@ bool extmark_clear(buf_T *buf, uint64_t ns_id, int l_row, colnr_T l_col, int u_r bool marks_cleared = false; bool all_ns = (ns_id == 0); - ExtmarkNs *ns = NULL; + uint32_t *ns = NULL; if (!all_ns) { ns = buf_ns_ref(buf, ns_id, false); if (!ns) { // nothing to do return false; } - - // TODO(bfredl): if map_size(ns->map) << buf->b_marktree.n_nodes - // it could be faster to iterate over the map instead } // the value is either zero or the lnum (row+1) if highlight was present. static Map(uint64_t, ssize_t) delete_set = MAP_INIT; - typedef struct { Decoration *decor; int row1; } DecorItem; + typedef struct { int row1; } DecorItem; static kvec_t(DecorItem) decors; MarkTreeIter itr[1] = { 0 }; marktree_itr_get(buf->b_marktree, l_row, l_col, itr); while (true) { - mtmark_t mark = marktree_itr_current(itr); - if (mark.row < 0 - || mark.row > u_row - || (mark.row == u_row && mark.col > u_col)) { + mtkey_t mark = marktree_itr_current(itr); + if (mark.pos.row < 0 + || mark.pos.row > u_row + || (mark.pos.row == u_row && mark.pos.col > u_col)) { break; } - ssize_t *del_status = map_ref(uint64_t, ssize_t)(&delete_set, mark.id, + ssize_t *del_status = map_ref(uint64_t, ssize_t)(&delete_set, mt_lookup_key(mark), false); if (del_status) { marktree_del_itr(buf->b_marktree, itr, false); if (*del_status >= 0) { // we had a decor_id DecorItem it = kv_A(decors, *del_status); - decor_remove(buf, it.row1, mark.row, it.decor); + decor_remove(buf, it.row1, mark.pos.row, mark.decor_full); } - map_del(uint64_t, ssize_t)(&delete_set, mark.id); + map_del(uint64_t, ssize_t)(&delete_set, mt_lookup_key(mark)); continue; } - uint64_t start_id = mark.id & ~MARKTREE_END_FLAG; - ExtmarkItem item = map_get(uint64_t, ExtmarkItem)(buf->b_extmark_index, - start_id); - - assert(item.ns_id > 0 && item.mark_id > 0); - if (item.mark_id > 0 && (item.ns_id == ns_id || all_ns)) { + assert(mark.ns > 0 && mark.id > 0); + if (mark.ns == ns_id || all_ns) { marks_cleared = true; - if (mark.id & MARKTREE_PAIRED_FLAG) { - uint64_t other = mark.id ^ MARKTREE_END_FLAG; + if (mt_paired(mark)) { + uint64_t other = mt_lookup_id(mark.ns, mark.id, !mt_end(mark)); ssize_t decor_id = -1; - if (item.decor) { + if (marktree_decor_level(mark) > kDecorLevelNone) { // Save the decoration and the first pos. Clear the decoration // later when we know the full range. decor_id = (ssize_t)kv_size(decors); kv_push(decors, - ((DecorItem) { .decor = item.decor, .row1 = mark.row })); + ((DecorItem) { .row1 = mark.pos.row })); } map_put(uint64_t, ssize_t)(&delete_set, other, decor_id); - } else if (item.decor) { - decor_remove(buf, mark.row, mark.row, item.decor); + } else if (mark.decor_full) { + decor_remove(buf, mark.pos.row, mark.pos.row, mark.decor_full); } - ExtmarkNs *my_ns = all_ns ? buf_ns_ref(buf, item.ns_id, false) : ns; - map_del(uint64_t, uint64_t)(my_ns->map, item.mark_id); - map_del(uint64_t, ExtmarkItem)(buf->b_extmark_index, start_id); marktree_del_itr(buf->b_marktree, itr, false); } else { marktree_itr_next(buf->b_marktree, itr); @@ -271,12 +265,12 @@ bool extmark_clear(buf_T *buf, uint64_t ns_id, int l_row, colnr_T l_col, int u_r uint64_t id; ssize_t decor_id; map_foreach(&delete_set, id, decor_id, { - mtpos_t pos = marktree_lookup(buf->b_marktree, id, itr); + mtkey_t mark = marktree_lookup(buf->b_marktree, id, itr); assert(itr->node); marktree_del_itr(buf->b_marktree, itr, false); if (decor_id >= 0) { DecorItem it = kv_A(decors, decor_id); - decor_remove(buf, it.row1, pos.row, it.decor); + decor_remove(buf, it.row1, mark.pos.row, mark.decor_full); } }); map_clear(uint64_t, ssize_t)(&delete_set); @@ -290,7 +284,7 @@ bool extmark_clear(buf_T *buf, uint64_t ns_id, int l_row, colnr_T l_col, int u_r // will be searched to the start, or end // dir can be set to control the order of the array // amount = amount of marks to find or -1 for all -ExtmarkInfoArray extmark_get(buf_T *buf, uint64_t ns_id, int l_row, colnr_T l_col, int u_row, +ExtmarkInfoArray extmark_get(buf_T *buf, uint32_t ns_id, int l_row, colnr_T l_col, int u_row, colnr_T u_col, int64_t amount, bool reverse) { ExtmarkInfoArray array = KV_INITIAL_VALUE; @@ -300,30 +294,26 @@ ExtmarkInfoArray extmark_get(buf_T *buf, uint64_t ns_id, int l_row, colnr_T l_co itr, reverse, false, NULL); int order = reverse ? -1 : 1; while ((int64_t)kv_size(array) < amount) { - mtmark_t mark = marktree_itr_current(itr); - mtpos_t endpos = { -1, -1 }; - if (mark.row < 0 - || (mark.row - u_row) * order > 0 - || (mark.row == u_row && (mark.col - u_col) * order > 0)) { + mtkey_t mark = marktree_itr_current(itr); + if (mark.pos.row < 0 + || (mark.pos.row - u_row) * order > 0 + || (mark.pos.row == u_row && (mark.pos.col - u_col) * order > 0)) { break; } - if (mark.id & MARKTREE_END_FLAG) { + if (mt_end(mark)) { goto next_mark; - } else if (mark.id & MARKTREE_PAIRED_FLAG) { - endpos = marktree_lookup(buf->b_marktree, mark.id | MARKTREE_END_FLAG, - NULL); } - - ExtmarkItem item = map_get(uint64_t, ExtmarkItem)(buf->b_extmark_index, - mark.id); - if (item.ns_id == ns_id) { - kv_push(array, ((ExtmarkInfo) { .ns_id = item.ns_id, - .mark_id = item.mark_id, - .row = mark.row, .col = mark.col, - .end_row = endpos.row, - .end_col = endpos.col, - .decor = item.decor })); + if (mark.ns == ns_id) { + mtkey_t end = marktree_get_alt(buf->b_marktree, mark, NULL); + kv_push(array, ((ExtmarkInfo) { .ns_id = mark.ns, + .mark_id = mark.id, + .row = mark.pos.row, .col = mark.pos.col, + .end_row = end.pos.row, + .end_col = end.pos.col, + .right_gravity = mt_right(mark), + .end_right_gravity = mt_right(end), + .decor = get_decor(mark) })); } next_mark: if (reverse) { @@ -336,36 +326,25 @@ next_mark: } // Lookup an extmark by id -ExtmarkInfo extmark_from_id(buf_T *buf, uint64_t ns_id, uint64_t id) +ExtmarkInfo extmark_from_id(buf_T *buf, uint32_t ns_id, uint32_t id) { - ExtmarkNs *ns = buf_ns_ref(buf, ns_id, false); - ExtmarkInfo ret = { 0, 0, -1, -1, -1, -1, NULL }; - if (!ns) { + ExtmarkInfo ret = { 0, 0, -1, -1, -1, -1, false, false, DECORATION_INIT }; + mtkey_t mark = marktree_lookup_ns(buf->b_marktree, ns_id, id, false, NULL); + if (!mark.id) { return ret; } - - uint64_t mark = map_get(uint64_t, uint64_t)(ns->map, id); - if (!mark) { - return ret; - } - - mtpos_t pos = marktree_lookup(buf->b_marktree, mark, NULL); - mtpos_t endpos = { -1, -1 }; - if (mark & MARKTREE_PAIRED_FLAG) { - endpos = marktree_lookup(buf->b_marktree, mark | MARKTREE_END_FLAG, NULL); - } - assert(pos.row >= 0); - - ExtmarkItem item = map_get(uint64_t, ExtmarkItem)(buf->b_extmark_index, - mark); + assert(mark.pos.row >= 0); + mtkey_t end = marktree_get_alt(buf->b_marktree, mark, NULL); ret.ns_id = ns_id; ret.mark_id = id; - ret.row = pos.row; - ret.col = pos.col; - ret.end_row = endpos.row; - ret.end_col = endpos.col; - ret.decor = item.decor; + ret.row = mark.pos.row; + ret.col = mark.pos.col; + ret.end_row = end.pos.row; + ret.end_col = end.pos.col; + ret.right_gravity = mt_right(mark); + ret.end_right_gravity = mt_right(end); + ret.decor = get_decor(mark); return ret; } @@ -378,25 +357,26 @@ void extmark_free_all(buf_T *buf) return; } - uint64_t id; - ExtmarkNs ns; - ExtmarkItem item; + MarkTreeIter itr[1] = { 0 }; + marktree_itr_get(buf->b_marktree, 0, 0, itr); + while (true) { + mtkey_t mark = marktree_itr_current(itr); + if (mark.pos.row < 0) { + break; + } - marktree_clear(buf->b_marktree); + // don't free mark.decor_full twice for a paired mark. + if (!(mt_paired(mark) && mt_end(mark))) { + decor_free(mark.decor_full); + } - map_foreach(buf->b_extmark_ns, id, ns, { - (void)id; - map_destroy(uint64_t, uint64_t)(ns.map); - }); - map_destroy(uint64_t, ExtmarkNs)(buf->b_extmark_ns); - map_init(uint64_t, ExtmarkNs, buf->b_extmark_ns); + marktree_itr_next(buf->b_marktree, itr); + } - map_foreach(buf->b_extmark_index, id, item, { - (void)id; - decor_free(item.decor); - }); - map_destroy(uint64_t, ExtmarkItem)(buf->b_extmark_index); - map_init(uint64_t, ExtmarkItem, buf->b_extmark_index); + marktree_clear(buf->b_marktree); + + map_destroy(uint32_t, uint32_t)(buf->b_extmark_ns); + map_init(uint32_t, uint32_t, buf->b_extmark_ns); } @@ -437,16 +417,16 @@ void u_extmark_copy(buf_T *buf, int l_row, colnr_T l_col, int u_row, colnr_T u_c MarkTreeIter itr[1] = { 0 }; marktree_itr_get(buf->b_marktree, l_row, l_col, itr); while (true) { - mtmark_t mark = marktree_itr_current(itr); - if (mark.row < 0 - || mark.row > u_row - || (mark.row == u_row && mark.col > u_col)) { + mtkey_t mark = marktree_itr_current(itr); + if (mark.pos.row < 0 + || mark.pos.row > u_row + || (mark.pos.row == u_row && mark.pos.col > u_col)) { break; } ExtmarkSavePos pos; - pos.mark = mark.id; - pos.old_row = mark.row; - pos.old_col = mark.col; + pos.mark = mt_lookup_key(mark); + pos.old_row = mark.pos.row; + pos.old_col = mark.pos.col; pos.row = -1; pos.col = -1; diff --git a/src/nvim/extmark.h b/src/nvim/extmark.h index c70db9f7aa..af9526cd43 100644 --- a/src/nvim/extmark.h +++ b/src/nvim/extmark.h @@ -3,6 +3,7 @@ #include "nvim/buffer_defs.h" #include "nvim/extmark_defs.h" +#include "nvim/decoration.h" #include "nvim/marktree.h" #include "nvim/pos.h" @@ -15,7 +16,9 @@ typedef struct { colnr_T col; int end_row; colnr_T end_col; - Decoration *decor; + bool right_gravity; + bool end_right_gravity; + Decoration decor; // TODO(bfredl): CHONKY } ExtmarkInfo; typedef kvec_t(ExtmarkInfo) ExtmarkInfoArray; diff --git a/src/nvim/extmark_defs.h b/src/nvim/extmark_defs.h index bbe8504ebf..5570b5c71e 100644 --- a/src/nvim/extmark_defs.h +++ b/src/nvim/extmark_defs.h @@ -4,23 +4,11 @@ #include "nvim/lib/kvec.h" #include "nvim/types.h" -typedef struct Decoration Decoration; - typedef struct { char *text; int hl_id; } VirtTextChunk; - -typedef struct { - uint64_t ns_id; - uint64_t mark_id; - // TODO(bfredl): a lot of small allocations. Should probably use - // kvec_t(Decoration) as an arena. Alternatively, store ns_id/mark_id - // _inline_ in MarkTree and use the map only for decorations. - Decoration *decor; -} ExtmarkItem; - typedef struct undo_object ExtmarkUndoObject; typedef kvec_t(ExtmarkUndoObject) extmark_undo_vec_t; diff --git a/src/nvim/file_search.c b/src/nvim/file_search.c index 1884dd49c5..d0f7a91d6c 100644 --- a/src/nvim/file_search.c +++ b/src/nvim/file_search.c @@ -483,8 +483,14 @@ void *vim_findfile_init(char_u *path, char_u *filename, char_u *stopdirs, int le int len = 0; if (p > search_ctx->ffsc_fix_path) { + // do not add '..' to the path and start upwards searching len = (int)(p - search_ctx->ffsc_fix_path) - 1; - STRNCAT(ff_expand_buffer, search_ctx->ffsc_fix_path, len); + if ((len >= 2 && STRNCMP(search_ctx->ffsc_fix_path, "..", 2) == 0) + && (len == 2 || search_ctx->ffsc_fix_path[2] == PATHSEP)) { + xfree(buf); + goto error_return; + } + STRLCAT(ff_expand_buffer, search_ctx->ffsc_fix_path, eb_len + (size_t)len + 1); add_pathsep((char *)ff_expand_buffer); } else { len = (int)STRLEN(search_ctx->ffsc_fix_path); @@ -607,7 +613,7 @@ char_u *vim_findfile(void *search_ctx_arg) for (;;) { // downward search loop for (;;) { - // check if user user wants to stop the search + // check if user wants to stop the search os_breakcheck(); if (got_int) { break; @@ -1139,7 +1145,7 @@ static int ff_check_visited(ff_visited_T **visited_list, char_u *fname, char_u * bool url = false; FileID file_id; - // For an URL we only compare the name, otherwise we compare the + // For a URL we only compare the name, otherwise we compare the // device/inode. if (path_with_url((char *)fname)) { STRLCPY(ff_expand_buffer, fname, MAXPATHL); @@ -1433,7 +1439,11 @@ char_u *find_file_in_path_option(char_u *ptr, size_t len, int options, int first rel_fname = NULL; } - if (first == TRUE) { + if (first == true) { + if (len == 0) { + return NULL; + } + // copy file name into NameBuff, expanding environment variables save_char = ptr[len]; ptr[len] = NUL; @@ -1590,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; @@ -1628,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); @@ -1645,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); @@ -1672,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 f8cf341836..965aa8749d 100644 --- a/src/nvim/fileio.c +++ b/src/nvim/fileio.c @@ -242,6 +242,7 @@ int readfile(char_u *fname, char_u *sfname, linenr_T from, linenr_T lines_to_ski bool notconverted = false; // true if conversion wanted but it wasn't possible char_u conv_rest[CONV_RESTLEN]; int conv_restlen = 0; // nr of bytes in conv_rest[] + pos_T orig_start; buf_T *old_curbuf; char_u *old_b_ffname; char_u *old_b_fname; @@ -298,14 +299,10 @@ int readfile(char_u *fname, char_u *sfname, linenr_T from, linenr_T lines_to_ski fname = sfname; #endif - /* - * The BufReadCmd and FileReadCmd events intercept the reading process by - * executing the associated commands instead. - */ + // The BufReadCmd and FileReadCmd events intercept the reading process by + // executing the associated commands instead. if (!filtering && !read_stdin && !read_buffer) { - pos_T pos; - - pos = curbuf->b_op_start; + orig_start = curbuf->b_op_start; // Set '[ mark to the line above where the lines go (line 1 if zero). curbuf->b_op_start.lnum = ((from == 0) ? 1 : from); @@ -335,7 +332,7 @@ int readfile(char_u *fname, char_u *sfname, linenr_T from, linenr_T lines_to_ski return aborting() ? FAIL : OK; } - curbuf->b_op_start = pos; + curbuf->b_op_start = orig_start; } if ((shortmess(SHM_OVER) || curbuf->b_help) && p_verbose == 0) { @@ -408,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. @@ -424,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; } @@ -576,9 +576,8 @@ int readfile(char_u *fname, char_u *sfname, linenr_T from, linenr_T lines_to_ski ++no_wait_return; // don't wait for return yet - /* - * Set '[ mark to the line above where the lines go (line 1 if zero). - */ + // Set '[ mark to the line above where the lines go (line 1 if zero). + orig_start = curbuf->b_op_start; curbuf->b_op_start.lnum = ((from == 0) ? 1 : from); curbuf->b_op_start.col = 0; @@ -618,6 +617,7 @@ int readfile(char_u *fname, char_u *sfname, linenr_T from, linenr_T lines_to_ski try_mac = (vim_strchr(p_ffs, 'm') != NULL); try_dos = (vim_strchr(p_ffs, 'd') != NULL); try_unix = (vim_strchr(p_ffs, 'x') != NULL); + curbuf->b_op_start = orig_start; if (msg_scrolled == n) { msg_scroll = m; @@ -1888,13 +1888,13 @@ failed: check_cursor_lnum(); beginline(BL_WHITE | BL_FIX); // on first non-blank - /* - * Set '[ and '] marks to the newly read lines. - */ - curbuf->b_op_start.lnum = from + 1; - curbuf->b_op_start.col = 0; - curbuf->b_op_end.lnum = from + linecnt; - curbuf->b_op_end.col = 0; + if (!cmdmod.lockmarks) { + // Set '[ and '] marks to the newly read lines. + curbuf->b_op_start.lnum = from + 1; + curbuf->b_op_start.col = 0; + curbuf->b_op_end.lnum = from + linecnt; + curbuf->b_op_end.col = 0; + } } msg_scroll = msg_save; @@ -2013,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 { @@ -2252,6 +2250,8 @@ int buf_write(buf_T *buf, char_u *fname, char_u *sfname, linenr_T start, linenr_ int write_undo_file = FALSE; context_sha256_T sha_ctx; unsigned int bkc = get_bkc_value(buf); + const pos_T orig_start = buf->b_op_start; + const pos_T orig_end = buf->b_op_end; if (fname == NULL || *fname == NUL) { // safety check return FAIL; @@ -2432,7 +2432,13 @@ int buf_write(buf_T *buf, char_u *fname, char_u *sfname, linenr_T start, linenr_ if (buf == NULL || (buf->b_ml.ml_mfp == NULL && !empty_memline) || did_cmd || nofile_err || aborting()) { - --no_wait_return; + if (buf != NULL && cmdmod.lockmarks) { + // restore the original '[ and '] positions + buf->b_op_start = orig_start; + buf->b_op_end = orig_end; + } + + no_wait_return--; msg_scroll = msg_save; if (nofile_err) { emsg(_("E676: No matching autocommands for acwrite buffer")); @@ -2513,6 +2519,11 @@ int buf_write(buf_T *buf, char_u *fname, char_u *sfname, linenr_T start, linenr_ } } + if (cmdmod.lockmarks) { + // restore the original '[ and '] positions + buf->b_op_start = orig_start; + buf->b_op_end = orig_end; + } if (shortmess(SHM_OVER) && !exiting) { msg_scroll = FALSE; // overwrite previous file message @@ -3308,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; @@ -3685,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; } } } @@ -3784,7 +3796,7 @@ static int set_rw_fname(char_u *fname, char_u *sfname) // Do filetype detection now if 'filetype' is empty. if (*curbuf->b_p_ft == NUL) { - if (au_has_group((char_u *)"filetypedetect")) { + if (augroup_exists("filetypedetect")) { (void)do_doautocmd((char_u *)"filetypedetect BufRead", false, NULL); } do_modelines(0); @@ -3853,7 +3865,7 @@ void msg_add_lines(int insert_space, long lnum, off_T nchars) *p++ = ' '; } if (shortmess(SHM_LINES)) { - vim_snprintf((char *)p, IOSIZE - (p - IObuff), "%" PRId64 "L, %" PRId64 "C", + vim_snprintf((char *)p, IOSIZE - (p - IObuff), "%" PRId64 "L, %" PRId64 "B", (int64_t)lnum, (int64_t)nchars); } else { vim_snprintf((char *)p, IOSIZE - (p - IObuff), @@ -3861,7 +3873,7 @@ void msg_add_lines(int insert_space, long lnum, off_T nchars) (int64_t)lnum); p += STRLEN(p); vim_snprintf((char *)p, IOSIZE - (p - IObuff), - NGETTEXT("%" PRId64 " character", "%" PRId64 " characters", nchars), + NGETTEXT("%" PRId64 " byte", "%" PRId64 " bytes", nchars), (int64_t)nchars); } } @@ -3883,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. @@ -3898,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 } @@ -4890,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 { @@ -4905,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; @@ -4933,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; @@ -4958,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"; @@ -4989,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 { @@ -5024,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; } } } @@ -5052,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) { @@ -5088,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]; @@ -5108,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; @@ -5129,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; @@ -5252,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/fold.c b/src/nvim/fold.c index b1d4321d4c..546345eeac 100644 --- a/src/nvim/fold.c +++ b/src/nvim/fold.c @@ -2332,7 +2332,7 @@ static linenr_T foldUpdateIEMSRecurse(garray_T *const gap, const int level, * firstlnum. */ while (!got_int) { - // set concat to 1 if it's allowed to concatenated this fold + // set concat to 1 if it's allowed to concatenate this fold // with a previous one that touches it. if (flp->start != 0 || flp->had_end <= MAX_LEVEL) { concat = 0; diff --git a/src/nvim/generators/gen_api_dispatch.lua b/src/nvim/generators/gen_api_dispatch.lua index 21f8c3855e..c6dd25154b 100644 --- a/src/nvim/generators/gen_api_dispatch.lua +++ b/src/nvim/generators/gen_api_dispatch.lua @@ -441,8 +441,8 @@ local function process_function(fn) local cparam = string.format('arg%u', j) local param_type = real_type(param[1]) local lc_param_type = real_type(param[1]):lower() - local extra = ((param_type == "Object" or param_type == "Dictionary") and "false, ") or "" - if param[1] == "DictionaryOf(LuaRef)" then + local extra = param_type == "Dictionary" and "false, " or "" + if param[1] == "Object" or param[1] == "DictionaryOf(LuaRef)" then extra = "true, " end local errshift = 0 diff --git a/src/nvim/generators/gen_keysets.lua b/src/nvim/generators/gen_keysets.lua index 63ef202fe1..633c5da184 100644 --- a/src/nvim/generators/gen_keysets.lua +++ b/src/nvim/generators/gen_keysets.lua @@ -26,6 +26,18 @@ local defspipe = io.open(defs_file, 'wb') local keysets = require'api.keysets' +local keywords = { + register = true; + default = true; +} + +local function sanitize(key) + if keywords[key] then + return key .. "_" + end + return key +end + for name, keys in pairs(keysets) do local neworder, hashfun = hashy.hashy_hash(name, keys, function (idx) return name.."_table["..idx.."].str" @@ -33,7 +45,7 @@ for name, keys in pairs(keysets) do defspipe:write("typedef struct {\n") for _, key in ipairs(neworder) do - defspipe:write(" Object "..key..";\n") + defspipe:write(" Object "..sanitize(key)..";\n") end defspipe:write("} KeyDict_"..name..";\n\n") @@ -41,7 +53,7 @@ for name, keys in pairs(keysets) do funcspipe:write("KeySetLink "..name.."_table[] = {\n") for _, key in ipairs(neworder) do - funcspipe:write(' {"'..key..'", offsetof(KeyDict_'..name..", "..key..")},\n") + funcspipe:write(' {"'..key..'", offsetof(KeyDict_'..name..", "..sanitize(key)..")},\n") end funcspipe:write(' {NULL, 0},\n') funcspipe:write("};\n\n") diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c index 6b1150cefa..85a5c176bb 100644 --- a/src/nvim/getchar.c +++ b/src/nvim/getchar.c @@ -15,6 +15,7 @@ #include <stdbool.h> #include <string.h> +#include "nvim/api/private/helpers.h" #include "nvim/ascii.h" #include "nvim/assert.h" #include "nvim/buffer_defs.h" @@ -58,25 +59,18 @@ static int curscript = 0; FileDescriptor *scriptin[NSCRIPT] = { NULL }; -/* - * These buffers are used for storing: - * - stuffed characters: A command that is translated into another command. - * - redo characters: will redo the last change. - * - recorded characters: for the "q" command. - * - * The bytes are stored like in the typeahead buffer: - * - K_SPECIAL introduces a special key (two more bytes follow). A literal - * K_SPECIAL is stored as K_SPECIAL KS_SPECIAL KE_FILLER. - * - CSI introduces a GUI termcap code (also when gui.in_use is FALSE, - * otherwise switching the GUI on would make mappings invalid). - * A literal CSI is stored as CSI KS_EXTRA KE_CSI. - * These translations are also done on multi-byte characters! - * - * Escaping CSI bytes is done by the system-specific input functions, called - * by ui_inchar(). - * Escaping K_SPECIAL is done by inchar(). - * Un-escaping is done by vgetc(). - */ +// These buffers are used for storing: +// - stuffed characters: A command that is translated into another command. +// - redo characters: will redo the last change. +// - recorded characters: for the "q" command. +// +// The bytes are stored like in the typeahead buffer: +// - K_SPECIAL introduces a special key (two more bytes follow). A literal +// K_SPECIAL is stored as K_SPECIAL KS_SPECIAL KE_FILLER. +// These translations are also done on multi-byte characters! +// +// Escaping K_SPECIAL is done by inchar(). +// Un-escaping is done by vgetc(). #define MINIMAL_SIZE 20 // minimal size for b_str @@ -146,7 +140,7 @@ static int KeyNoremap = 0; // remapping flags // typebuf.tb_buf has three parts: room in front (for result of mappings), the // middle for typeahead and room for new characters (which needs to be 3 * -// MAXMAPLEN) for the Amiga). +// MAXMAPLEN for the Amiga). #define TYPELEN_INIT (5 * (MAXMAPLEN + 3)) static char_u typebuf_init[TYPELEN_INIT]; // initial typebuf.tb_buf static char_u noremapbuf_init[TYPELEN_INIT]; // initial typebuf.tb_noremap @@ -169,10 +163,11 @@ void free_buff(buffheader_T *buf) xfree(p); } buf->bh_first.b_next = NULL; + buf->bh_curr = NULL; } /// Return the contents of a buffer as a single string. -/// K_SPECIAL and CSI in the returned string are escaped. +/// K_SPECIAL in the returned string is escaped. /// /// @param dozero count == zero is not an error static char_u *get_buffcont(buffheader_T *buffer, int dozero) @@ -201,11 +196,9 @@ static char_u *get_buffcont(buffheader_T *buffer, int dozero) return p; } -/* - * Return the contents of the record buffer as a single string - * and clear the record buffer. - * K_SPECIAL and CSI in the returned string are escaped. - */ +/// Return the contents of the record buffer as a single string +/// and clear the record buffer. +/// K_SPECIAL in the returned string is escaped. char_u *get_recorded(void) { char_u *p; @@ -235,10 +228,8 @@ char_u *get_recorded(void) return p; } -/* - * Return the contents of the redo buffer as a single string. - * K_SPECIAL and CSI in the returned string are escaped. - */ +/// Return the contents of the redo buffer as a single string. +/// K_SPECIAL in the returned string is escaped. char_u *get_inserted(void) { return get_buffcont(&redobuff, FALSE); @@ -246,7 +237,7 @@ char_u *get_inserted(void) /// Add string after the current block of the given buffer /// -/// K_SPECIAL and CSI should have been escaped already. +/// K_SPECIAL should have been escaped already. /// /// @param[out] buf Buffer to add to. /// @param[in] s String to add. @@ -294,9 +285,23 @@ static void add_buff(buffheader_T *const buf, const char *const s, ptrdiff_t sle } } -/* - * Add number "n" to buffer "buf". - */ +/// Delete "slen" bytes from the end of "buf". +/// Only works when it was just added. +static void delete_buff_tail(buffheader_T *buf, int slen) +{ + int len; + + if (buf->bh_curr == NULL) { + return; // nothing to delete + } + len = (int)STRLEN(buf->bh_curr->b_str); + if (len >= slen) { + buf->bh_curr->b_str[len - slen] = NUL; + buf->bh_space += (size_t)slen; + } +} + +/// Add number "n" to buffer "buf". static void add_num_buff(buffheader_T *buf, long n) { char number[32]; @@ -304,10 +309,8 @@ static void add_num_buff(buffheader_T *buf, long n) add_buff(buf, number, -1L); } -/* - * Add character 'c' to buffer "buf". - * Translates special keys, NUL, CSI, K_SPECIAL and multibyte characters. - */ +/// Add character 'c' to buffer "buf". +/// Translates special keys, NUL, K_SPECIAL and multibyte characters. static void add_char_buff(buffheader_T *buf, int c) { uint8_t bytes[MB_MAXBYTES + 1]; @@ -339,12 +342,10 @@ static void add_char_buff(buffheader_T *buf, int c) } } -/* - * Get one byte from the read buffers. Use readbuf1 one first, use readbuf2 - * if that one is empty. - * If advance == TRUE go to the next char. - * No translation is done K_SPECIAL and CSI are escaped. - */ +/// Get one byte from the read buffers. Use readbuf1 one first, use readbuf2 +/// if that one is empty. +/// If advance == TRUE go to the next char. +/// No translation is done K_SPECIAL is escaped. static int read_readbuffers(int advance) { int c; @@ -523,10 +524,8 @@ void restoreRedobuff(save_redo_T *save_redo) old_redobuff = save_redo->sr_old_redobuff; } -/* - * Append "s" to the redo buffer. - * K_SPECIAL and CSI should already have been escaped. - */ +/// Append "s" to the redo buffer. +/// K_SPECIAL should already have been escaped. void AppendToRedobuff(const char *s) { if (!block_redo) { @@ -535,7 +534,7 @@ void AppendToRedobuff(const char *s) } /// Append to Redo buffer literally, escaping special characters with CTRL-V. -/// K_SPECIAL and CSI are escaped as well. +/// K_SPECIAL is escaped as well. /// /// @param str String to append /// @param len Length of `str` or -1 for up to the NUL. @@ -583,10 +582,8 @@ void AppendToRedobuffLit(const char_u *str, int len) } } -/* - * Append a character to the redo buffer. - * Translates special keys, NUL, CSI, K_SPECIAL and multibyte characters. - */ +/// Append a character to the redo buffer. +/// Translates special keys, NUL, K_SPECIAL and multibyte characters. void AppendCharToRedobuff(int c) { if (!block_redo) { @@ -604,17 +601,15 @@ void AppendNumberToRedobuff(long n) } } -/* - * Append string "s" to the stuff buffer. - * CSI and K_SPECIAL must already have been escaped. - */ +/// Append string "s" to the stuff buffer. +/// K_SPECIAL must already have been escaped. void stuffReadbuff(const char *s) { add_buff(&readbuf1, s, -1L); } /// Append string "s" to the redo stuff buffer. -/// @remark CSI and K_SPECIAL must already have been escaped. +/// @remark K_SPECIAL must already have been escaped. void stuffRedoReadbuff(const char *s) { add_buff(&readbuf2, s, -1L); @@ -625,11 +620,9 @@ void stuffReadbuffLen(const char *s, long len) add_buff(&readbuf1, s, len); } -/* - * Stuff "s" into the stuff buffer, leaving special key codes unmodified and - * escaping other K_SPECIAL and CSI bytes. - * Change CR, LF and ESC into a space. - */ +/// Stuff "s" into the stuff buffer, leaving special key codes unmodified and +/// escaping other K_SPECIAL bytes. +/// Change CR, LF and ESC into a space. void stuffReadbuffSpec(const char *s) { while (*s != NUL) { @@ -647,10 +640,8 @@ void stuffReadbuffSpec(const char *s) } } -/* - * Append a character to the stuff buffer. - * Translates special keys, NUL, CSI, K_SPECIAL and multibyte characters. - */ +/// Append a character to the stuff buffer. +/// Translates special keys, NUL, K_SPECIAL and multibyte characters. void stuffcharReadbuff(int c) { add_char_buff(&readbuf1, c); @@ -664,12 +655,12 @@ void stuffnumReadbuff(long n) add_num_buff(&readbuf1, n); } -// Read a character from the redo buffer. Translates K_SPECIAL, CSI and -// multibyte characters. -// The redo buffer is left as it is. -// If init is true, prepare for redo, return FAIL if nothing to redo, OK -// otherwise. -// If old_redo is true, use old_redobuff instead of redobuff. +/// Read a character from the redo buffer. Translates K_SPECIAL and +/// multibyte characters. +/// The redo buffer is left as it is. +/// If init is true, prepare for redo, return FAIL if nothing to redo, OK +/// otherwise. +/// If old_redo is true, use old_redobuff instead of redobuff. static int read_redo(bool init, bool old_redo) { static buffblock_T *bp; @@ -723,9 +714,9 @@ static int read_redo(bool init, bool old_redo) return c; } -// Copy the rest of the redo buffer into the stuff buffer (in a slow way). -// If old_redo is true, use old_redobuff instead of redobuff. -// The escaped K_SPECIAL and CSI are copied without translation. +/// Copy the rest of the redo buffer into the stuff buffer (in a slow way). +/// If old_redo is true, use old_redobuff instead of redobuff. +/// The escaped K_SPECIAL is copied without translation. static void copy_redo(bool old_redo) { int c; @@ -861,7 +852,7 @@ void init_default_mappings(void) // // If noremap is REMAP_YES, new string can be mapped again. // If noremap is REMAP_NONE, new string cannot be mapped again. -// If noremap is REMAP_SKIP, fist char of new string cannot be mapped again, +// If noremap is REMAP_SKIP, first char of new string cannot be mapped again, // but abbreviations are allowed. // If noremap is REMAP_SCRIPT, new string cannot be mapped again, except for // script-local mappings. @@ -991,36 +982,34 @@ int ins_typebuf(char_u *str, int noremap, int offset, bool nottyped, bool silent return OK; } -/* - * Put character "c" back into the typeahead buffer. - * Can be used for a character obtained by vgetc() that needs to be put back. - * Uses cmd_silent, KeyTyped and KeyNoremap to restore the flags belonging to - * the char. - */ -void ins_char_typebuf(int c) +/// Put character "c" back into the typeahead buffer. +/// Can be used for a character obtained by vgetc() that needs to be put back. +/// Uses cmd_silent, KeyTyped and KeyNoremap to restore the flags belonging to +/// the char. +/// @return the length of what was inserted +int ins_char_typebuf(int c, int modifier) { - char_u buf[MB_MAXBYTES + 1]; - if (IS_SPECIAL(c)) { + char_u buf[MB_MAXBYTES * 3 + 4]; + int len = 0; + if (modifier != 0) { buf[0] = K_SPECIAL; - buf[1] = (char_u)K_SECOND(c); - buf[2] = (char_u)K_THIRD(c); + buf[1] = KS_MODIFIER; + buf[2] = (char_u)modifier; buf[3] = NUL; + len = 3; + } + if (IS_SPECIAL(c)) { + buf[len] = K_SPECIAL; + buf[len + 1] = (char_u)K_SECOND(c); + buf[len + 2] = (char_u)K_THIRD(c); + buf[len + 3] = NUL; } else { - buf[utf_char2bytes(c, buf)] = NUL; - char_u *p = buf; - while (*p) { - if ((uint8_t)(*p) == CSI || (uint8_t)(*p) == K_SPECIAL) { - bool is_csi = (uint8_t)(*p) == CSI; - memmove(p + 3, p + 1, STRLEN(p + 1) + 1); - *p++ = K_SPECIAL; - *p++ = is_csi ? KS_EXTRA : KS_SPECIAL; - *p++ = is_csi ? KE_CSI : KE_FILLER; - } else { - p++; - } - } + 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; } /// Return TRUE if the typeahead buffer was changed (while waiting for a @@ -1180,6 +1169,18 @@ static void gotchars(const char_u *chars, size_t len) maptick++; } +/// Undo the last gotchars() for "len" bytes. To be used when putting a typed +/// character back into the typeahead buffer, thus gotchars() will be called +/// again. +/// Only affects recorded characters. +void ungetchars(int len) +{ + if (reg_recording != 0) { + delete_buff_tail(&recordbuff, len); + last_recorded_len -= (size_t)len; + } +} + /* * Sync undo. Called when typed characters are obtained from the typeahead * buffer, or when a menu is used. @@ -1426,15 +1427,13 @@ static void updatescript(int c) } } -/* - * Get the next input character. - * Can return a special key or a multi-byte character. - * Can return NUL when called recursively, use safe_vgetc() if that's not - * wanted. - * This translates escaped K_SPECIAL and CSI bytes to a K_SPECIAL or CSI byte. - * Collects the bytes of a multibyte character into the whole character. - * Returns the modifiers in the global "mod_mask". - */ +/// Get the next input character. +/// Can return a special key or a multi-byte character. +/// Can return NUL when called recursively, use safe_vgetc() if that's not +/// wanted. +/// This translates escaped K_SPECIAL bytes to a K_SPECIAL byte. +/// Collects the bytes of a multibyte character into the whole character. +/// Returns the modifiers in the global "mod_mask". int vgetc(void) { int c, c2; @@ -1460,8 +1459,9 @@ int vgetc(void) mouse_row = old_mouse_row; mouse_col = old_mouse_col; } else { - mod_mask = 0x0; + mod_mask = 0; last_recorded_len = 0; + for (;;) { // this is done twice if there are modifiers bool did_inc = false; if (mod_mask) { // no mapping after modifier has been read @@ -1571,14 +1571,9 @@ int vgetc(void) buf[i] = (char_u)vgetorpeek(true); if (buf[i] == K_SPECIAL) { // Must be a K_SPECIAL - KS_SPECIAL - KE_FILLER sequence, - // which represents a K_SPECIAL (0x80), - // or a CSI - KS_EXTRA - KE_CSI sequence, which represents - // a CSI (0x9B), - // of a K_SPECIAL - KS_EXTRA - KE_CSI, which is CSI too. - c = vgetorpeek(true); - if (vgetorpeek(true) == KE_CSI && c == KS_EXTRA) { - buf[i] = CSI; - } + // which represents a K_SPECIAL (0x80). + (void)vgetorpeek(true); // skip KS_SPECIAL + (void)vgetorpeek(true); // skip KE_FILLER } } no_mapping--; @@ -1592,8 +1587,9 @@ int vgetc(void) if (!no_mapping && KeyTyped && !(State & TERM_FOCUS) && (mod_mask == MOD_MASK_ALT || mod_mask == MOD_MASK_META)) { mod_mask = 0; - ins_char_typebuf(c); - ins_char_typebuf(ESC); + int len = ins_char_typebuf(c, 0); + (void)ins_char_typebuf(ESC, 0); + ungetchars(len + 3); // The ALT/META modifier takes three more bytes continue; } @@ -1693,7 +1689,7 @@ typedef enum { map_result_fail, // failed, break loop map_result_get, // get a character from typeahead map_result_retry, // try to map again - map_result_nomatch // no matching mapping, get char + map_result_nomatch, // no matching mapping, get char } map_result_T; /// Handle mappings in the typeahead buffer. @@ -1714,6 +1710,15 @@ static int handle_mapping(int *keylenp, bool *timedout, int *mapdepth) int keylen = *keylenp; int i; int local_State = get_real_state(); + bool is_plug_map = false; + + // Check if typehead starts with a <Plug> mapping. + // In that case we will ignore nore flag on it. + if (typebuf.tb_buf[typebuf.tb_off] == K_SPECIAL + && typebuf.tb_buf[typebuf.tb_off+1] == KS_EXTRA + && typebuf.tb_buf[typebuf.tb_off+2] == KE_PLUG) { + is_plug_map = true; + } // Check for a mappable key sequence. // Walk through one maphash[] list until we find an entry that matches. @@ -1729,7 +1734,7 @@ static int handle_mapping(int *keylenp, bool *timedout, int *mapdepth) tb_c1 = typebuf.tb_buf[typebuf.tb_off]; if (no_mapping == 0 && maphash_valid && (no_zero_mapping == 0 || tb_c1 != '0') - && (typebuf.tb_maplen == 0 + && (typebuf.tb_maplen == 0 || is_plug_map || (p_remap && !(typebuf.tb_noremap[typebuf.tb_off] & (RM_NONE|RM_ABBR)))) && !(p_paste && (State & (INSERT + CMDLINE))) @@ -1817,7 +1822,7 @@ static int handle_mapping(int *keylenp, bool *timedout, int *mapdepth) break; } } - if (n >= 0) { + if (!is_plug_map && n >= 0) { continue; } @@ -1902,7 +1907,7 @@ static int handle_mapping(int *keylenp, bool *timedout, int *mapdepth) // complete match if (keylen >= 0 && keylen <= typebuf.tb_len) { - char_u *map_str; + char_u *map_str = NULL; int save_m_expr; int save_m_noremap; int save_m_silent; @@ -1947,20 +1952,52 @@ static int handle_mapping(int *keylenp, bool *timedout, int *mapdepth) save_m_silent = mp->m_silent; char_u *save_m_keys = NULL; // only saved when needed char_u *save_m_str = NULL; // only saved when needed + LuaRef save_m_luaref = mp->m_luaref; // Handle ":map <expr>": evaluate the {rhs} as an // expression. Also save and restore the command line // for "normal :". if (mp->m_expr) { - int save_vgetc_busy = vgetc_busy; + const int save_vgetc_busy = vgetc_busy; const bool save_may_garbage_collect = may_garbage_collect; + const int save_cursor_row = ui_current_row(); + const int save_cursor_col = ui_current_col(); + const int prev_did_emsg = did_emsg; vgetc_busy = 0; may_garbage_collect = false; save_m_keys = vim_strsave(mp->m_keys); - save_m_str = vim_strsave(mp->m_str); - map_str = eval_map_expr(save_m_str, NUL); + if (save_m_luaref == LUA_NOREF) { + save_m_str = vim_strsave(mp->m_str); + } + map_str = eval_map_expr(mp, NUL); + + // The mapping may do anything, but we expect it to take care of + // redrawing. Do put the cursor back where it was. + ui_cursor_goto(save_cursor_row, save_cursor_col); + ui_flush(); + + // If an error was displayed and the expression returns an empty + // string, generate a <Nop> to allow for a redraw. + if (prev_did_emsg != did_emsg && (map_str == NULL || *map_str == NUL)) { + char_u buf[4]; + xfree(map_str); + buf[0] = K_SPECIAL; + buf[1] = KS_EXTRA; + buf[2] = KE_IGNORE; + buf[3] = NUL; + map_str = vim_strsave(buf); + if (State & CMDLINE) { + // redraw the command below the error + msg_didout = true; + if (msg_row < cmdline_row) { + msg_row = cmdline_row; + } + redrawcmd(); + } + } + vgetc_busy = save_vgetc_busy; may_garbage_collect = save_may_garbage_collect; } else { @@ -2039,7 +2076,7 @@ void vungetc(int c) /// /// When `no_mapping` (global) is zero, checks for mappings in the current mode. /// Only returns one byte (of a multi-byte character). -/// K_SPECIAL and CSI may be escaped, need to get two more bytes then. +/// K_SPECIAL may be escaped, need to get two more bytes then. static int vgetorpeek(bool advance) { int c, c1; @@ -2294,6 +2331,10 @@ static int vgetorpeek(bool advance) c = ESC; } tc = c; + + // no chars to block abbreviations for + typebuf.tb_no_abbr_cnt = 0; + break; } @@ -2452,7 +2493,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!!!! /// @@ -2470,7 +2511,7 @@ static int vgetorpeek(bool advance) /// Return the number of obtained characters. /// Return -1 when end of input script reached. /// -/// @param wait_time milli seconds +/// @param wait_time milliseconds int inchar(char_u *buf, int maxlen, long wait_time) { int len = 0; // Init for GCC. @@ -2569,7 +2610,7 @@ int fix_input_buffer(char_u *buf, int len) FUNC_ATTR_NONNULL_ALL { if (!using_script()) { - // Should not escape K_SPECIAL/CSI reading input from the user because vim + // Should not escape K_SPECIAL reading input from the user because vim // key codes keys are processed in input.c/input_enqueue. buf[len] = NUL; return len; @@ -2580,9 +2621,8 @@ int fix_input_buffer(char_u *buf, int len) char_u *p = buf; // Two characters are special: NUL and K_SPECIAL. - // Replace NUL by K_SPECIAL KS_ZERO KE_FILLER + // Replace NUL by K_SPECIAL KS_ZERO KE_FILLER // Replace K_SPECIAL by K_SPECIAL KS_SPECIAL KE_FILLER - // Replace CSI by K_SPECIAL KS_EXTRA KE_CSI for (i = len; --i >= 0; ++p) { if (p[0] == NUL || (p[0] == K_SPECIAL @@ -2618,11 +2658,13 @@ int fix_input_buffer(char_u *buf, int len) /// @param[in] orig_lhs Original mapping LHS, with characters to replace. /// @param[in] orig_lhs_len `strlen` of orig_lhs. /// @param[in] orig_rhs Original mapping RHS, with characters to replace. +/// @param[in] rhs_lua Lua reference for Lua maps. /// @param[in] orig_rhs_len `strlen` of orig_rhs. /// @param[in] cpo_flags See param docs for @ref replace_termcodes. /// @param[out] mapargs MapArguments struct holding the replaced strings. -void set_maparg_lhs_rhs(const char_u *orig_lhs, const size_t orig_lhs_len, const char_u *orig_rhs, - const size_t orig_rhs_len, int cpo_flags, MapArguments *mapargs) +void set_maparg_lhs_rhs(const char_u *orig_lhs, const size_t orig_lhs_len, + const char_u *orig_rhs, const size_t orig_rhs_len, + LuaRef rhs_lua, int cpo_flags, MapArguments *mapargs) { char_u *lhs_buf = NULL; char_u *rhs_buf = NULL; @@ -2638,22 +2680,34 @@ void set_maparg_lhs_rhs(const char_u *orig_lhs, const size_t orig_lhs_len, const true, true, true, cpo_flags); mapargs->lhs_len = STRLEN(replaced); STRLCPY(mapargs->lhs, replaced, sizeof(mapargs->lhs)); + mapargs->rhs_lua = rhs_lua; - mapargs->orig_rhs_len = orig_rhs_len; - mapargs->orig_rhs = xcalloc(mapargs->orig_rhs_len + 1, sizeof(char_u)); - STRLCPY(mapargs->orig_rhs, orig_rhs, mapargs->orig_rhs_len + 1); + if (rhs_lua == LUA_NOREF) { + mapargs->orig_rhs_len = orig_rhs_len; + mapargs->orig_rhs = xcalloc(mapargs->orig_rhs_len + 1, sizeof(char_u)); + STRLCPY(mapargs->orig_rhs, orig_rhs, mapargs->orig_rhs_len + 1); - if (STRICMP(orig_rhs, "<nop>") == 0) { // "<Nop>" means nothing - mapargs->rhs = xcalloc(1, sizeof(char_u)); // single null-char - mapargs->rhs_len = 0; - mapargs->rhs_is_noop = true; + if (STRICMP(orig_rhs, "<nop>") == 0) { // "<Nop>" means nothing + mapargs->rhs = xcalloc(1, sizeof(char_u)); // single null-char + mapargs->rhs_len = 0; + mapargs->rhs_is_noop = true; + } else { + replaced = replace_termcodes(orig_rhs, orig_rhs_len, &rhs_buf, + false, true, true, cpo_flags); + mapargs->rhs_len = STRLEN(replaced); + mapargs->rhs_is_noop = false; + mapargs->rhs = xcalloc(mapargs->rhs_len + 1, sizeof(char_u)); + STRLCPY(mapargs->rhs, replaced, mapargs->rhs_len + 1); + } } else { - replaced = replace_termcodes(orig_rhs, orig_rhs_len, &rhs_buf, - false, true, true, cpo_flags); - mapargs->rhs_len = STRLEN(replaced); - mapargs->rhs_is_noop = false; - mapargs->rhs = xcalloc(mapargs->rhs_len + 1, sizeof(char_u)); - STRLCPY(mapargs->rhs, replaced, mapargs->rhs_len + 1); + char tmp_buf[64]; + // orig_rhs is not used for Lua mappings, but still needs to be a string. + mapargs->orig_rhs = xcalloc(1, sizeof(char_u)); + 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)KS_EXTRA, KE_LUA, rhs_lua); + mapargs->rhs = vim_strsave((char_u *)tmp_buf); } xfree(lhs_buf); @@ -2765,7 +2819,7 @@ int str_to_mapargs(const char_u *strargs, bool is_unmap, MapArguments *mapargs) size_t orig_rhs_len = STRLEN(rhs_start); set_maparg_lhs_rhs(lhs_to_replace, orig_lhs_len, - rhs_start, orig_rhs_len, + rhs_start, orig_rhs_len, LUA_NOREF, CPO_TO_CPO_FLAGS, &parsed_args); xfree(lhs_to_replace); @@ -2827,7 +2881,7 @@ int buf_do_map(int maptype, MapArguments *args, int mode, bool is_abbrev, buf_T validate_maphash(); bool has_lhs = (args->lhs[0] != NUL); - bool has_rhs = (args->rhs[0] != NUL) || args->rhs_is_noop; + bool has_rhs = args->rhs_lua != LUA_NOREF || (args->rhs[0] != NUL) || args->rhs_is_noop; // check for :unmap without argument if (maptype == 1 && !has_lhs) { @@ -3017,10 +3071,14 @@ int buf_do_map(int maptype, MapArguments *args, int mode, bool is_abbrev, buf_T } else { // new rhs for existing entry mp->m_mode &= ~mode; // remove mode bits if (mp->m_mode == 0 && !did_it) { // reuse entry - xfree(mp->m_str); + XFREE_CLEAR(mp->m_str); + XFREE_CLEAR(mp->m_orig_str); + XFREE_CLEAR(mp->m_desc); + NLUA_CLEAR_REF(mp->m_luaref); + mp->m_str = vim_strsave(rhs); - xfree(mp->m_orig_str); mp->m_orig_str = vim_strsave(orig_rhs); + mp->m_luaref = args->rhs_lua; mp->m_noremap = noremap; mp->m_nowait = args->nowait; mp->m_silent = args->silent; @@ -3028,6 +3086,10 @@ int buf_do_map(int maptype, MapArguments *args, int mode, bool is_abbrev, buf_T mp->m_expr = args->expr; mp->m_script_ctx = current_sctx; mp->m_script_ctx.sc_lnum += sourcing_lnum; + nlua_set_sctx(&mp->m_script_ctx); + if (args->desc != NULL) { + mp->m_desc = xstrdup(args->desc); + } did_it = true; } } @@ -3096,6 +3158,7 @@ int buf_do_map(int maptype, MapArguments *args, int mode, bool is_abbrev, buf_T mp->m_keys = vim_strsave(lhs); mp->m_str = vim_strsave(rhs); mp->m_orig_str = vim_strsave(orig_rhs); + mp->m_luaref = args->rhs_lua; mp->m_keylen = (int)STRLEN(mp->m_keys); mp->m_noremap = noremap; mp->m_nowait = args->nowait; @@ -3104,6 +3167,11 @@ int buf_do_map(int maptype, MapArguments *args, int mode, bool is_abbrev, buf_T mp->m_expr = args->expr; mp->m_script_ctx = current_sctx; mp->m_script_ctx.sc_lnum += sourcing_lnum; + nlua_set_sctx(&mp->m_script_ctx); + mp->m_desc = NULL; + if (args->desc != NULL) { + mp->m_desc = xstrdup(args->desc); + } // add the new entry in front of the abbrlist or maphash[] list if (is_abbrev) { @@ -3200,8 +3268,10 @@ static void mapblock_free(mapblock_T **mpp) mp = *mpp; xfree(mp->m_keys); - xfree(mp->m_str); - xfree(mp->m_orig_str); + NLUA_CLEAR_REF(mp->m_luaref); + XFREE_CLEAR(mp->m_str); + XFREE_CLEAR(mp->m_orig_str); + XFREE_CLEAR(mp->m_desc); *mpp = mp->m_next; xfree(mp); } @@ -3392,7 +3462,8 @@ static void showmap(mapblock_T *mp, bool local) { size_t len = 1; - if (message_filtered(mp->m_keys) && 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; } @@ -3437,19 +3508,29 @@ static void showmap(mapblock_T *mp, bool local) /* Use FALSE below if we only want things like <Up> to show up as such on * the rhs, and not M-x etc, TRUE gets both -- webb */ - if (*mp->m_str == NUL) { + if (mp->m_luaref != LUA_NOREF) { + 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[0] == NUL) { msg_puts_attr("<Nop>", HL_ATTR(HLF_8)); } else { - // Remove escaping of CSI, because "m_str" is in a format to be used + // Remove escaping of K_SPECIAL, because "m_str" is in a format to be used // as typeahead. char_u *s = vim_strsave(mp->m_str); - vim_unescape_csi(s); + vim_unescape_ks(s); msg_outtrans_special(s, false, 0); xfree(s); } + + if (mp->m_desc != NULL) { + msg_puts("\n "); // Shift line to same level as rhs. + msg_puts(mp->m_desc); + } if (p_verbose > 0) { last_set_msg(mp->m_script_ctx); } + msg_clr_eos(); ui_flush(); // show one line at a time } @@ -3532,8 +3613,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) - && strstr((char *)mp->m_str, rhs) != NULL) { + if ((mp->m_mode & mode) && strstr((char *)mp->m_str, rhs) != NULL) { return true; } } @@ -3818,9 +3898,9 @@ bool check_abbr(int c, char_u *ptr, int col, int mincol) int match; if (strchr((const char *)mp->m_keys, K_SPECIAL) != NULL) { - // Might have CSI escaped mp->m_keys. + // Might have K_SPECIAL escaped mp->m_keys. q = vim_strsave(mp->m_keys); - vim_unescape_csi(q); + vim_unescape_ks(q); qlen = (int)STRLEN(q); } // find entries with right mode and keys @@ -3866,7 +3946,7 @@ bool check_abbr(int c, char_u *ptr, int col, int mincol) int newlen = utf_char2bytes(c, tb + j); tb[j + newlen] = NUL; // Need to escape K_SPECIAL. - char_u *escaped = vim_strsave_escape_csi(tb + j); + char_u *escaped = vim_strsave_escape_ks(tb + j); if (escaped != NULL) { newlen = (int)STRLEN(escaped); memmove(tb + j, escaped, (size_t)newlen); @@ -3879,7 +3959,7 @@ bool check_abbr(int c, char_u *ptr, int col, int mincol) (void)ins_typebuf(tb, 1, 0, true, mp->m_silent); } if (mp->m_expr) { - s = eval_map_expr(mp->m_str, c); + s = eval_map_expr(mp, c); } else { s = mp->m_str; } @@ -3909,20 +3989,22 @@ bool check_abbr(int c, char_u *ptr, int col, int mincol) /// special characters. /// /// @param c NUL or typed character for abbreviation -static char_u *eval_map_expr(char_u *str, int c) +static char_u *eval_map_expr(mapblock_T *mp, int c) { char_u *res; - char_u *p; - char_u *expr; + char_u *p = NULL; + char_u *expr = NULL; char_u *save_cmd; pos_T save_cursor; int save_msg_col; int save_msg_row; - /* Remove escaping of CSI, because "str" is in a format to be used as - * typeahead. */ - expr = vim_strsave(str); - vim_unescape_csi(expr); + // Remove escaping of K_SPECIAL, because "str" is in a format to be used as + // typeahead. + if (mp->m_luaref == LUA_NOREF) { + expr = vim_strsave(mp->m_str); + vim_unescape_ks(expr); + } save_cmd = save_cmdline_alloc(); @@ -3934,7 +4016,22 @@ static char_u *eval_map_expr(char_u *str, int c) save_cursor = curwin->w_cursor; save_msg_col = msg_col; save_msg_row = msg_row; - p = eval_to_string(expr, NULL, false); + if (mp->m_luaref != LUA_NOREF) { + Error err = ERROR_INIT; + Array args = ARRAY_DICT_INIT; + Object ret = nlua_call_ref(mp->m_luaref, NULL, args, true, &err); + if (ret.type == kObjectTypeString) { + p = (char_u *)xstrndup(ret.data.string.data, ret.data.string.size); + } + api_free_object(ret); + if (err.type != kErrorTypeNone) { + semsg_multiline("E5108: %s", err.msg); + api_clear_error(&err); + } + } else { + p = eval_to_string(expr, NULL, false); + xfree(expr); + } textlock--; ex_normal_lock--; curwin->w_cursor = save_cursor; @@ -3942,23 +4039,20 @@ static char_u *eval_map_expr(char_u *str, int c) msg_row = save_msg_row; restore_cmdline_alloc(save_cmd); - xfree(expr); if (p == NULL) { return NULL; } - // Escape CSI in the result to be able to use the string as typeahead. - res = vim_strsave_escape_csi(p); + // Escape K_SPECIAL in the result to be able to use the string as typeahead. + res = vim_strsave_escape_ks(p); xfree(p); return res; } -/* - * Copy "p" to allocated memory, escaping K_SPECIAL and CSI so that the result - * can be put in the typeahead buffer. - */ -char_u *vim_strsave_escape_csi(char_u *p) +/// Copy "p" to allocated memory, escaping K_SPECIAL so that the result +/// can be put in the typeahead buffer. +char_u *vim_strsave_escape_ks(char_u *p) { // Need a buffer to hold up to three times as much. Four in case of an // illegal utf-8 byte: @@ -3973,7 +4067,7 @@ char_u *vim_strsave_escape_csi(char_u *p) *d++ = *s++; } else { // Add character, possibly multi-byte to destination, escaping - // CSI and K_SPECIAL. Be careful, it can be an illegal byte! + // K_SPECIAL. Be careful, it can be an illegal byte! d = add_char2buf(utf_ptr2char(s), d); s += utf_ptr2len(s); } @@ -3983,11 +4077,9 @@ char_u *vim_strsave_escape_csi(char_u *p) return res; } -/* - * Remove escaping from CSI and K_SPECIAL characters. Reverse of - * vim_strsave_escape_csi(). Works in-place. - */ -void vim_unescape_csi(char_u *p) +/// Remove escaping from K_SPECIAL characters. Reverse of +/// vim_strsave_escape_ks(). Works in-place. +void vim_unescape_ks(char_u *p) { char_u *s = p, *d = p; @@ -3995,10 +4087,6 @@ void vim_unescape_csi(char_u *p) if (s[0] == K_SPECIAL && s[1] == KS_SPECIAL && s[2] == KE_FILLER) { *d++ = K_SPECIAL; s += 3; - } else if ((s[0] == K_SPECIAL || s[0] == CSI) - && s[1] == KS_EXTRA && s[2] == (int)KE_CSI) { - *d++ = CSI; - s += 3; } else { *d++ = *s++; } @@ -4049,8 +4137,11 @@ int makemap(FILE *fd, buf_T *buf) continue; } - // skip mappings that contain a <SNR> (script-local thing), + // skip lua mappings and mappings that contain a <SNR> (script-local thing), // they probably don't work when loaded again + if (mp->m_luaref != LUA_NOREF) { + continue; + } for (p = mp->m_str; *p != NUL; p++) { if (p[0] == K_SPECIAL && p[1] == KS_EXTRA && p[2] == (int)KE_SNR) { @@ -4238,7 +4329,7 @@ int put_escstr(FILE *fd, char_u *strstart, int what) for (; *str != NUL; str++) { // Check for a multi-byte character, which may contain escaped - // K_SPECIAL and CSI bytes. + // K_SPECIAL bytes. const char *p = mb_unescape((const char **)&str); if (p != NULL) { while (*p != NUL) { @@ -4331,10 +4422,11 @@ int put_escstr(FILE *fd, char_u *strstart, int what) /// @param mp_ptr return: pointer to mapblock or NULL /// @param local_ptr return: buffer-local mapping or NULL char_u *check_map(char_u *keys, int mode, int exact, int ign_mod, int abbr, mapblock_T **mp_ptr, - int *local_ptr) + int *local_ptr, int *rhs_lua) { int len, minlen; mapblock_T *mp; + *rhs_lua = LUA_NOREF; validate_maphash(); @@ -4375,7 +4467,8 @@ char_u *check_map(char_u *keys, int mode, int exact, int ign_mod, int abbr, mapb if (local_ptr != NULL) { *local_ptr = local; } - return mp->m_str; + *rhs_lua = mp->m_luaref; + return mp->m_luaref == LUA_NOREF ? mp->m_str : NULL; } } } @@ -4560,3 +4653,47 @@ char_u *getcmdkeycmd(int promptc, void *cookie, int indent, bool do_concat) return (char_u *)line_ga.ga_data; } + +bool map_execute_lua(void) +{ + garray_T line_ga; + int c1 = -1; + bool aborted = false; + + ga_init(&line_ga, 1, 32); + + no_mapping++; + + got_int = false; + while (c1 != NUL && !aborted) { + ga_grow(&line_ga, 32); + // Get one character at a time. + c1 = vgetorpeek(true); + if (got_int) { + aborted = true; + } else if (c1 == '\r' || c1 == '\n') { + c1 = NUL; // end the line + } else { + ga_append(&line_ga, (char)c1); + } + } + + no_mapping--; + + if (aborted) { + ga_clear(&line_ga); + return false; + } + + LuaRef ref = (LuaRef)atoi(line_ga.ga_data); + Error err = ERROR_INIT; + Array args = ARRAY_DICT_INIT; + nlua_call_ref(ref, NULL, args, false, &err); + if (err.type != kErrorTypeNone) { + semsg_multiline("E5108: %s", err.msg); + api_clear_error(&err); + } + + ga_clear(&line_ga); + return true; +} diff --git a/src/nvim/getchar.h b/src/nvim/getchar.h index 5950611d3f..f24a4e7c7c 100644 --- a/src/nvim/getchar.h +++ b/src/nvim/getchar.h @@ -50,14 +50,16 @@ struct map_arguments { char_u *rhs; /// The {rhs} of the mapping. size_t rhs_len; + LuaRef rhs_lua; /// lua function as rhs bool rhs_is_noop; /// True when the {orig_rhs} is <nop>. char_u *orig_rhs; /// The original text of the {rhs}. size_t orig_rhs_len; + char *desc; /// map description }; typedef struct map_arguments MapArguments; #define MAP_ARGUMENTS_INIT { false, false, false, false, false, false, false, \ - { 0 }, 0, NULL, 0, false, NULL, 0 } + { 0 }, 0, NULL, 0, LUA_NOREF, false, NULL, 0, NULL } #define KEYLEN_PART_KEY -1 // keylen value for incomplete key-code #define KEYLEN_PART_MAP -2 // keylen value for incomplete mapping diff --git a/src/nvim/globals.h b/src/nvim/globals.h index 697d4b11a7..35ad57906b 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -28,7 +28,7 @@ #endif #ifndef FILETYPE_FILE -# define FILETYPE_FILE "filetype.vim" +# define FILETYPE_FILE "filetype.lua filetype.vim" #endif #ifndef FTPLUGIN_FILE @@ -127,7 +127,7 @@ typedef off_t off_T; // When vgetc() is called, it sets mod_mask to the set of modifiers that are // held down based on the MOD_MASK_* symbols that are read first. -EXTERN int mod_mask INIT(= 0x0); // current key modifiers +EXTERN int mod_mask INIT(= 0); // current key modifiers // Cmdline_row is the row where the command line starts, just below the @@ -326,16 +326,16 @@ EXTERN int want_garbage_collect INIT(= false); EXTERN int garbage_collect_at_exit INIT(= false); // Special values for current_SID. -#define SID_MODELINE -1 // when using a modeline -#define SID_CMDARG -2 // for "--cmd" argument -#define SID_CARG -3 // for "-c" argument -#define SID_ENV -4 // for sourcing environment variable -#define SID_ERROR -5 // option was reset because of an error -#define SID_NONE -6 // don't set scriptID -#define SID_WINLAYOUT -7 // changing window size -#define SID_LUA -8 // for Lua scripts/chunks -#define SID_API_CLIENT -9 // for API clients -#define SID_STR -10 // for sourcing a string with no script item +#define SID_MODELINE (-1) // when using a modeline +#define SID_CMDARG (-2) // for "--cmd" argument +#define SID_CARG (-3) // for "-c" argument +#define SID_ENV (-4) // for sourcing environment variable +#define SID_ERROR (-5) // option was reset because of an error +#define SID_NONE (-6) // don't set scriptID +#define SID_WINLAYOUT (-7) // changing window size +#define SID_LUA (-8) // for Lua scripts/chunks +#define SID_API_CLIENT (-9) // for API clients +#define SID_STR (-10) // for sourcing a string with no script item // Script CTX being sourced or was sourced to define the current function. EXTERN sctx_T current_sctx INIT(= { 0 COMMA 0 COMMA 0 }); @@ -524,6 +524,8 @@ EXTERN pos_T VIsual; EXTERN int VIsual_active INIT(= false); /// Whether Select mode is active. EXTERN int VIsual_select INIT(= false); +/// Register name for Select mode +EXTERN int VIsual_select_reg INIT(= 0); /// Restart Select mode when next cmd finished EXTERN int restart_VIsual_select INIT(= 0); /// Whether to restart the selection after a Select-mode mapping or menu. @@ -977,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")); @@ -997,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/hardcopy.c b/src/nvim/hardcopy.c index 6fc70144ac..eb10c65be9 100644 --- a/src/nvim/hardcopy.c +++ b/src/nvim/hardcopy.c @@ -386,30 +386,43 @@ static uint32_t prt_get_term_color(int colorindex) return cterm_color_8[colorindex % 8]; } -static void prt_get_attr(int hl_id, prt_text_attr_T *pattr, int modec) +static uint32_t prt_get_color(int hl_id, int modec) { int colorindex; uint32_t fg_color; + const char *color = highlight_color(hl_id, "fg#", 'g'); + if (color != NULL) { + RgbValue rgb = name_to_color(color); + if (rgb != -1) { + return (uint32_t)rgb; + } + } + + color = highlight_color(hl_id, "fg", modec); + if (color == NULL) { + colorindex = 0; + } else { + colorindex = atoi(color); + } + + if (colorindex >= 0 && colorindex < t_colors) { + fg_color = prt_get_term_color(colorindex); + } else { + fg_color = PRCOLOR_BLACK; + } + + return fg_color; +} + +static void prt_get_attr(int hl_id, prt_text_attr_T *pattr, int modec) +{ pattr->bold = (highlight_has_attr(hl_id, HL_BOLD, modec) != NULL); pattr->italic = (highlight_has_attr(hl_id, HL_ITALIC, modec) != NULL); pattr->underline = (highlight_has_attr(hl_id, HL_UNDERLINE, modec) != NULL); pattr->undercurl = (highlight_has_attr(hl_id, HL_UNDERCURL, modec) != NULL); - { - const char *color = highlight_color(hl_id, "fg", modec); - if (color == NULL) { - colorindex = 0; - } else { - colorindex = atoi(color); - } - - if (colorindex >= 0 && colorindex < t_colors) { - fg_color = prt_get_term_color(colorindex); - } else { - fg_color = PRCOLOR_BLACK; - } - } + uint32_t fg_color = prt_get_color(hl_id, modec); if (fg_color == PRCOLOR_WHITE) { fg_color = PRCOLOR_BLACK; diff --git a/src/nvim/highlight.c b/src/nvim/highlight.c index 3050ca02de..e43a56086f 100644 --- a/src/nvim/highlight.c +++ b/src/nvim/highlight.c @@ -144,13 +144,19 @@ int hl_get_syn_attr(int ns_id, int idx, HlAttrs at_en) } } -void ns_hl_def(NS ns_id, int hl_id, HlAttrs attrs, int link_id) +void ns_hl_def(NS ns_id, int hl_id, HlAttrs attrs, int link_id, Dict(highlight) *dict) { - DecorProvider *p = get_decor_provider(ns_id, true); if ((attrs.rgb_ae_attr & HL_DEFAULT) && map_has(ColorKey, ColorItem)(&ns_hl, ColorKey(ns_id, hl_id))) { return; } + if (ns_id == 0) { + assert(dict); + // set in global (':highlight') namespace + set_hl_group(hl_id, attrs, dict, link_id); + return; + } + DecorProvider *p = get_decor_provider(ns_id, true); int attr_id = link_id > 0 ? -1 : hl_get_syn_attr(ns_id, hl_id, attrs); ColorItem it = { .attr_id = attr_id, .link_id = link_id, @@ -192,23 +198,17 @@ int ns_get_hl(NS ns_id, int hl_id, bool link, bool nodefault) int tmp = false; HlAttrs attrs = HLATTRS_INIT; if (ret.type == kObjectTypeDictionary) { - Dictionary dict = ret.data.dictionary; fallback = false; - attrs = dict2hlattrs(dict, true, &it.link_id, &err); - for (size_t i = 0; i < dict.size; i++) { - char *key = dict.items[i].key.data; - Object val = dict.items[i].value; - bool truthy = api_object_to_bool(val, key, false, &err); - - if (strequal(key, "fallback")) { - fallback = truthy; - } else if (strequal(key, "temp")) { - tmp = truthy; + Dict(highlight) dict = { 0 }; + if (api_dict_to_keydict(&dict, KeyDict_highlight_get_field, + ret.data.dictionary, &err)) { + attrs = dict2hlattrs(&dict, true, &it.link_id, &err); + fallback = api_object_to_bool(dict.fallback, "fallback", true, &err); + tmp = api_object_to_bool(dict.fallback, "tmp", false, &err); + if (it.link_id >= 0) { + fallback = true; } } - if (it.link_id >= 0) { - fallback = true; - } } it.attr_id = fallback ? -1 : hl_get_syn_attr((int)ns_id, hl_id, attrs); @@ -796,112 +796,121 @@ Dictionary hlattrs2dict(HlAttrs ae, bool use_rgb) return hl; } -HlAttrs dict2hlattrs(Dictionary dict, bool use_rgb, int *link_id, Error *err) +HlAttrs dict2hlattrs(Dict(highlight) *dict, bool use_rgb, int *link_id, Error *err) { HlAttrs hlattrs = HLATTRS_INIT; - int32_t fg = -1, bg = -1, ctermfg = -1, ctermbg = -1, sp = -1; + int blend = -1; int16_t mask = 0; int16_t cterm_mask = 0; bool cterm_mask_provided = false; - for (size_t i = 0; i < dict.size; i++) { - char *key = dict.items[i].key.data; - Object val = dict.items[i].value; - - struct { - const char *name; - int16_t flag; - } flags[] = { - { "bold", HL_BOLD }, - { "standout", HL_STANDOUT }, - { "underline", HL_UNDERLINE }, - { "undercurl", HL_UNDERCURL }, - { "italic", HL_ITALIC }, - { "reverse", HL_INVERSE }, - { "default", HL_DEFAULT }, - { "global", HL_GLOBAL }, - { NULL, 0 }, - }; - - int j; - for (j = 0; flags[j].name; j++) { - if (strequal(flags[j].name, key)) { - if (api_object_to_bool(val, key, false, err)) { - mask = mask | flags[j].flag; - } - break; - } +#define CHECK_FLAG(d, m, name, extra, flag) \ + if (api_object_to_bool(d->name ## extra, #name, false, err)) { \ + m = m | flag; \ } - // Handle cterm attrs - if (strequal(key, "cterm") && val.type == kObjectTypeDictionary) { - cterm_mask_provided = true; - Dictionary cterm_dict = val.data.dictionary; - for (size_t l = 0; l < cterm_dict.size; l++) { - char *cterm_dict_key = cterm_dict.items[l].key.data; - Object cterm_dict_val = cterm_dict.items[l].value; - for (int m = 0; flags[m].name; m++) { - if (strequal(flags[m].name, cterm_dict_key)) { - if (api_object_to_bool(cterm_dict_val, cterm_dict_key, false, - err)) { - cterm_mask |= flags[m].flag; - } - break; - } - } - } + CHECK_FLAG(dict, mask, bold, , HL_BOLD); + CHECK_FLAG(dict, mask, standout, , HL_STANDOUT); + CHECK_FLAG(dict, mask, underline, , HL_UNDERLINE); + 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", true, err); + } else if (HAS_KEY(dict->foreground)) { + 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", true, err); + } else if (HAS_KEY(dict->background)) { + 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", true, err); + } else if (HAS_KEY(dict->special)) { + sp = object_to_color(dict->special, "special", true, err); + } + if (ERROR_SET(err)) { + return hlattrs; + } + + if (dict->blend.type == kObjectTypeInteger) { + Integer blend0 = dict->blend.data.integer; + if (blend0 < 0 || blend0 > 100) { + api_set_error(err, kErrorTypeValidation, "'blend' is not between 0 to 100"); + } else { + blend = (int)blend0; } + } else if (HAS_KEY(dict->blend)) { + api_set_error(err, kErrorTypeValidation, "'blend' must be an integer"); + } + if (ERROR_SET(err)) { + return hlattrs; + } - struct { - const char *name; - const char *shortname; - int *dest; - } colors[] = { - { "foreground", "fg", &fg }, - { "background", "bg", &bg }, - { "ctermfg", NULL, &ctermfg }, - { "ctermbg", NULL, &ctermbg }, - { "special", "sp", &sp }, - { NULL, NULL, NULL }, - }; - - int k; - for (k = 0; (!flags[j].name) && colors[k].name; k++) { - if (strequal(colors[k].name, key) || strequal(colors[k].shortname, key)) { - if (val.type == kObjectTypeInteger) { - *colors[k].dest = (int)val.data.integer; - } else if (val.type == kObjectTypeString) { - String str = val.data.string; - // TODO(bfredl): be more fancy with "bg", "fg" etc - if (str.size) { - *colors[k].dest = name_to_color(str.data); - } - } else { - api_set_error(err, kErrorTypeValidation, - "'%s' must be string or integer", key); - } - break; + if (HAS_KEY(dict->link)) { + if (link_id) { + *link_id = object_to_hl_id(dict->link, "link", err); + if (ERROR_SET(err)) { + return hlattrs; } + } else { + api_set_error(err, kErrorTypeValidation, "Invalid Key: 'link'"); } + } - if (flags[j].name || colors[k].name) { - // handled above - } else if (link_id && strequal(key, "link")) { - if (val.type == kObjectTypeString) { - String str = val.data.string; - *link_id = syn_check_group(str.data, (int)str.size); - } else if (val.type == kObjectTypeInteger) { - // TODO(bfredl): validate range? - *link_id = (int)val.data.integer; - } else { - api_set_error(err, kErrorTypeValidation, - "'link' must be string or integer"); - } + // Handle cterm attrs + if (dict->cterm.type == kObjectTypeDictionary) { + Dict(highlight_cterm) cterm[1] = { 0 }; + if (!api_dict_to_keydict(cterm, KeyDict_highlight_cterm_get_field, + dict->cterm.data.dictionary, err)) { + return hlattrs; } + cterm_mask_provided = true; + CHECK_FLAG(cterm, cterm_mask, bold, , HL_BOLD); + CHECK_FLAG(cterm, cterm_mask, standout, , HL_STANDOUT); + CHECK_FLAG(cterm, cterm_mask, underline, , HL_UNDERLINE); + 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", false, err); if (ERROR_SET(err)) { - return hlattrs; // error set, caller should not use retval + return hlattrs; + } + } + + if (HAS_KEY(dict->ctermbg)) { + ctermbg = object_to_color(dict->ctermbg, "ctermbg", false, err); + if (ERROR_SET(err)) { + return hlattrs; } } @@ -914,19 +923,45 @@ HlAttrs dict2hlattrs(Dictionary dict, bool use_rgb, int *link_id, Error *err) 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.hl_blend = blend; + 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, 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 + 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; + } +} + Array hl_inspect(int attr) { Array ret = ARRAY_DICT_INIT; diff --git a/src/nvim/if_cscope.c b/src/nvim/if_cscope.c index daef8db267..9ca01137cf 100644 --- a/src/nvim/if_cscope.c +++ b/src/nvim/if_cscope.c @@ -919,8 +919,8 @@ static int cs_find(exarg_T *eap) /// Common code for cscope find, shared by cs_find() and ex_cstag(). -static bool cs_find_common(char *opt, char *pat, int forceit, int verbose, - bool use_ll, char_u *cmdline) +static bool cs_find_common(char *opt, char *pat, int forceit, int verbose, bool use_ll, + char_u *cmdline) { char *cmd; int *nummatches; @@ -1594,7 +1594,6 @@ static char *cs_pathcomponents(char *path) char *s = path + strlen(path) - 1; for (int i = 0; i < p_cspc; i++) { while (s > path && *--s != '/') { - continue; } } if ((s > path && *s == '/')) { @@ -1813,7 +1812,6 @@ static int cs_read_prompt(size_t i) static void sig_handler(int s) { // do nothing - return; } #endif diff --git a/src/nvim/indent_c.c b/src/nvim/indent_c.c index faa9b38cf7..7f483d02ab 100644 --- a/src/nvim/indent_c.c +++ b/src/nvim/indent_c.c @@ -41,9 +41,7 @@ static pos_T *ind_find_start_comment(void) // XXX pos_T *find_start_comment(int ind_maxcomment) // XXX { - pos_T *pos; - char_u *line; - char_u *p; + pos_T *pos; int64_t cur_maxcomment = ind_maxcomment; for (;; ) { @@ -55,11 +53,9 @@ pos_T *find_start_comment(int ind_maxcomment) // XXX * Check if the comment start we found is inside a string. * If it is then restrict the search to below this line and try again. */ - line = ml_get(pos->lnum); - for (p = line; *p && (colnr_T)(p - line) < pos->col; ++p) - p = skip_string(p); - if ((colnr_T)(p - line) <= pos->col) + if (!is_pos_in_string(ml_get(pos->lnum), pos->col)) { break; + } cur_maxcomment = curwin->w_cursor.lnum - pos->lnum - 1; if (cur_maxcomment <= 0) { pos = NULL; @@ -110,8 +106,6 @@ static pos_T *ind_find_start_CORS(linenr_T *is_raw) static pos_T *find_start_rawstring(int ind_maxcomment) // XXX { pos_T *pos; - char_u *line; - char_u *p; long cur_maxcomment = ind_maxcomment; for (;;) @@ -124,11 +118,9 @@ static pos_T *find_start_rawstring(int ind_maxcomment) // XXX * Check if the raw string start we found is inside a string. * If it is then restrict the search to below this line and try again. */ - line = ml_get(pos->lnum); - for (p = line; *p && (colnr_T)(p - line) < pos->col; ++p) - p = skip_string(p); - if ((colnr_T)(p - line) <= pos->col) - break; + if (!is_pos_in_string(ml_get(pos->lnum), pos->col)) { + break; + } cur_maxcomment = curwin->w_cursor.lnum - pos->lnum - 1; if (cur_maxcomment <= 0) { @@ -143,7 +135,7 @@ static pos_T *find_start_rawstring(int ind_maxcomment) // XXX * Skip to the end of a "string" and a 'c' character. * If there is no string or character, return argument unmodified. */ -static char_u *skip_string(char_u *p) +static const char_u *skip_string(const char_u *p) { int i; @@ -152,11 +144,11 @@ static char_u *skip_string(char_u *p) */ for (;; p++) { if (p[0] == '\'') { // 'c' or '\n' or '\000' - if (!p[1]) { // ' at end of line + if (p[1] == NUL) { // ' at end of line break; } i = 2; - if (p[1] == '\\') { // '\n' or '\000' + if (p[1] == '\\' && p[2] != NUL) { // '\n' or '\000' i++; while (ascii_isdigit(p[i - 1])) { // '\000' i++; @@ -178,24 +170,24 @@ static char_u *skip_string(char_u *p) continue; // continue for another string } } else if (p[0] == 'R' && p[1] == '"') { - // Raw string: R"[delim](...)[delim]" - char_u *delim = p + 2; - char_u *paren = vim_strchr(delim, '('); - - if (paren != NULL) { - const ptrdiff_t delim_len = paren - delim; - - for (p += 3; *p; ++p) - if (p[0] == ')' && STRNCMP(p + 1, delim, delim_len) == 0 - && p[delim_len + 1] == '"') - { - p += delim_len + 1; - break; - } - if (p[0] == '"') { - continue; // continue for another string - } + // Raw string: R"[delim](...)[delim]" + const char_u *delim = p + 2; + const char_u *paren = vim_strchr(delim, '('); + + if (paren != NULL) { + const ptrdiff_t delim_len = paren - delim; + + for (p += 3; *p; p++) { + if (p[0] == ')' && STRNCMP(p + 1, delim, delim_len) == 0 + && p[delim_len + 1] == '"') { + p += delim_len + 1; + break; + } + } + if (p[0] == '"') { + continue; // continue for another string } + } } break; // no string found } @@ -205,6 +197,16 @@ static char_u *skip_string(char_u *p) return p; } +/// @returns true if "line[col]" is inside a C string. +int is_pos_in_string(const char_u *line, colnr_T col) +{ + const char_u *p; + + for (p = line; *p && (colnr_T)(p - line) < col; p++) { + p = skip_string(p); + } + return !((colnr_T)(p - line) <= col); +} /* * Functions for C-indenting. @@ -218,7 +220,7 @@ static char_u *skip_string(char_u *p) /* * Return true if the string "line" starts with a word from 'cinwords'. */ -bool cin_is_cinword(char_u *line) +bool cin_is_cinword(const char_u *line) { bool retval = false; @@ -246,10 +248,10 @@ bool cin_is_cinword(char_u *line) * Skip over white space and C comments within the line. * Also skip over Perl/shell comments if desired. */ -static char_u *cin_skipcomment(char_u *s) +static const char_u *cin_skipcomment(const char_u *s) { while (*s) { - char_u *prev_s = s; + const char_u *prev_s = s; s = skipwhite(s); @@ -283,7 +285,7 @@ static char_u *cin_skipcomment(char_u *s) * Return TRUE if there is no code at *s. White space and comments are * not considered code. */ -static int cin_nocode(char_u *s) +static int cin_nocode(const char_u *s) { return *cin_skipcomment(s) == NUL; } @@ -312,9 +314,9 @@ static pos_T *find_line_comment(void) // XXX } /// Checks if `text` starts with "key:". -static bool cin_has_js_key(char_u *text) +static bool cin_has_js_key(const char_u *text) { - char_u *s = skipwhite(text); + const char_u *s = skipwhite(text); char_u quote = 0; if (*s == '\'' || *s == '"') { @@ -341,7 +343,7 @@ static bool cin_has_js_key(char_u *text) /// Checks if string matches "label:"; move to character after ':' if true. /// "*s" must point to the start of the label, if there is one. -static bool cin_islabel_skip(char_u **s) +static bool cin_islabel_skip(const char_u **s) FUNC_ATTR_NONNULL_ALL { if (!vim_isIDc(**s)) { // need at least one ID character @@ -361,7 +363,7 @@ static bool cin_islabel_skip(char_u **s) // Note: curwin->w_cursor must be where we are looking for the label. bool cin_islabel(void) // XXX { - char_u *s = cin_skipcomment(get_cursor_line_ptr()); + const char_u *s = cin_skipcomment(get_cursor_line_ptr()); // Exclude "default" from labels, since it should be indented // like a switch label. Same for C++ scope declarations. @@ -380,8 +382,8 @@ bool cin_islabel(void) // XXX * label. */ pos_T cursor_save; - pos_T *trypos; - char_u *line; + pos_T *trypos; + const char_u *line; cursor_save = curwin->w_cursor; while (curwin->w_cursor.lnum > 1) { @@ -424,8 +426,8 @@ bool cin_islabel(void) // XXX */ static int cin_isinit(void) { - char_u *s; - static char *skip[] = {"static", "public", "protected", "private"}; + const char_u *s; + static char *skip[] = { "static", "public", "protected", "private" }; s = cin_skipcomment(get_cursor_line_ptr()); @@ -460,7 +462,7 @@ static int cin_isinit(void) * Recognize a switch label: "case .*:" or "default:". */ bool cin_iscase( - char_u *s, + const char_u *s, bool strict // Allow relaxed check of case statement for JS ) { @@ -503,7 +505,7 @@ bool cin_iscase( /* * Recognize a "default" switch label. */ -static int cin_isdefault(char_u *s) +static int cin_isdefault(const char_u *s) { return STRNCMP(s, "default", 7) == 0 && *(s = cin_skipcomment(s + 7)) == ':' @@ -513,7 +515,7 @@ static int cin_isdefault(char_u *s) /* * Recognize a "public/private/protected" scope declaration label. */ -bool cin_isscopedecl(char_u *s) +bool cin_isscopedecl(const char_u *s) { int i; @@ -534,13 +536,18 @@ bool cin_isscopedecl(char_u *s) #define FIND_NAMESPACE_LIM 20 // Recognize a "namespace" scope declaration. -static bool cin_is_cpp_namespace(char_u *s) +static bool cin_is_cpp_namespace(const char_u *s) { - char_u *p; + const char_u *p; bool has_name = false; bool has_name_start = false; s = cin_skipcomment(s); + + if (STRNCMP(s, "inline", 6) == 0 && (s[6] == NUL || !vim_iswordc(s[6]))) { + s = cin_skipcomment(skipwhite(s + 6)); + } + if (STRNCMP(s, "namespace", 9) == 0 && (s[9] == NUL || !vim_iswordc(s[9]))) { p = cin_skipcomment(skipwhite(s + 9)); while (*p != NUL) { @@ -576,7 +583,7 @@ static bool cin_is_cpp_namespace(char_u *s) * case 234: a = b; * ^ */ -static char_u *after_label(char_u *l) +static const char_u *after_label(const char_u *l) { for (; *l; ++l) { if (*l == ':') { @@ -603,10 +610,10 @@ static char_u *after_label(char_u *l) */ static int get_indent_nolabel(linenr_T lnum) // XXX { - char_u *l; + const char_u *l; pos_T fp; colnr_T col; - char_u *p; + const char_u *p; l = ml_get(lnum); p = after_label(l); @@ -625,9 +632,9 @@ static int get_indent_nolabel(linenr_T lnum) // XXX * label: if (asdf && asdfasdf) * ^ */ -static int skip_label(linenr_T lnum, char_u **pp) +static int skip_label(linenr_T lnum, const char_u **pp) { - char_u *l; + const char_u *l; int amount; pos_T cursor_save; @@ -708,8 +715,8 @@ static int cin_first_id_amount(void) */ static int cin_get_equal_amount(linenr_T lnum) { - char_u *line; - char_u *s; + const char_u *line; + const char_u *s; colnr_T col; pos_T fp; @@ -747,7 +754,7 @@ static int cin_get_equal_amount(linenr_T lnum) /* * Recognize a preprocessor statement: Any line that starts with '#'. */ -static int cin_ispreproc(char_u *s) +static int cin_ispreproc(const char_u *s) { if (*skipwhite(s) == '#') return TRUE; @@ -758,9 +765,9 @@ static int cin_ispreproc(char_u *s) /// continuation line of a preprocessor statement. Decrease "*lnump" to the /// start and return the line in "*pp". /// Put the amount of indent in "*amount". -static int cin_ispreproc_cont(char_u **pp, linenr_T *lnump, int *amount) +static int cin_ispreproc_cont(const char_u **pp, linenr_T *lnump, int *amount) { - char_u *line = *pp; + const char_u *line = *pp; linenr_T lnum = *lnump; int retval = false; int candidate_amount = *amount; @@ -794,7 +801,7 @@ static int cin_ispreproc_cont(char_u **pp, linenr_T *lnump, int *amount) /* * Recognize the start of a C or C++ comment. */ -static int cin_iscomment(char_u *p) +static int cin_iscomment(const char_u *p) { return p[0] == '/' && (p[1] == '*' || p[1] == '/'); } @@ -802,7 +809,7 @@ static int cin_iscomment(char_u *p) /* * Recognize the start of a "//" comment. */ -static int cin_islinecomment(char_u *p) +static int cin_islinecomment(const char_u *p) { return p[0] == '/' && p[1] == '/'; } @@ -817,8 +824,8 @@ static int cin_islinecomment(char_u *p) * both apply in order to determine initializations). */ static char_u -cin_isterminated ( - char_u *s, +cin_isterminated( + const char_u *s, int incl_open, // include '{' at the end as terminator int incl_comma // recognize a trailing comma ) @@ -867,9 +874,9 @@ cin_isterminated ( /// lines here. /// @param[in] first_lnum Where to start looking. /// @param[in] min_lnum The line before which we will not be looking. -static int cin_isfuncdecl(char_u **sp, linenr_T first_lnum, linenr_T min_lnum) +static int cin_isfuncdecl(const char_u **sp, linenr_T first_lnum, linenr_T min_lnum) { - char_u *s; + const char_u *s; linenr_T lnum = first_lnum; linenr_T save_lnum = curwin->w_cursor.lnum; int retval = false; @@ -923,11 +930,10 @@ static int cin_isfuncdecl(char_u **sp, linenr_T first_lnum, linenr_T min_lnum) while (*s && *s != ';' && *s != '\'' && *s != '"') { if (*s == ')' && cin_nocode(s + 1)) { - /* ')' at the end: may have found a match - * Check for he previous line not to end in a backslash: - * #if defined(x) && \ - * defined(y) - */ + // ')' at the end: may have found a match + // Check for the previous line not to end in a backslash: + // #if defined(x) && {backslash} + // defined(y) lnum = first_lnum - 1; s = ml_get(lnum); if (*s == NUL || s[STRLEN(s) - 1] != '\\') @@ -971,12 +977,12 @@ done: return retval; } -static int cin_isif(char_u *p) +static int cin_isif(const char_u *p) { return STRNCMP(p, "if", 2) == 0 && !vim_isIDc(p[2]); } -static int cin_iselse(char_u *p) +static int cin_iselse(const char_u *p) { if (*p == '}') { // accept "} else" p = cin_skipcomment(p + 1); @@ -984,7 +990,7 @@ static int cin_iselse(char_u *p) return STRNCMP(p, "else", 4) == 0 && !vim_isIDc(p[4]); } -static int cin_isdo(char_u *p) +static int cin_isdo(const char_u *p) { return STRNCMP(p, "do", 2) == 0 && !vim_isIDc(p[2]); } @@ -994,7 +1000,7 @@ static int cin_isdo(char_u *p) * We only accept a "while (condition) ;", with only white space between the * ')' and ';'. The condition may be spread over several lines. */ -static int cin_iswhileofdo(char_u *p, linenr_T lnum) // XXX +static int cin_iswhileofdo(const char_u *p, linenr_T lnum) // XXX { pos_T cursor_save; pos_T *trypos; @@ -1028,7 +1034,7 @@ static int cin_iswhileofdo(char_u *p, linenr_T lnum) // XXX * Otherwise return !0 and update "*poffset" to point to the place where the * string was found. */ -static int cin_is_if_for_while_before_offset(char_u *line, int *poffset) +static int cin_is_if_for_while_before_offset(const char_u *line, int *poffset) { int offset = *poffset; @@ -1072,10 +1078,10 @@ probablyFound: */ static int cin_iswhileofdo_end(int terminated) { - char_u *line; - char_u *p; - char_u *s; - pos_T *trypos; + const char_u *line; + const char_u *p; + const char_u *s; + pos_T *trypos; int i; if (terminated != ';') { // there must be a ';' at the end @@ -1115,7 +1121,7 @@ static int cin_iswhileofdo_end(int terminated) return FALSE; } -static int cin_isbreak(char_u *p) +static int cin_isbreak(const char_u *p) { return STRNCMP(p, "break", 5) == 0 && !vim_isIDc(p[5]); } @@ -1135,10 +1141,10 @@ static int cin_isbreak(char_u *p) */ static int cin_is_cpp_baseclass(cpp_baseclass_cache_T *cached) { lpos_T *pos = &cached->lpos; // find position - char_u *s; + const char_u *s; int class_or_struct, lookfor_ctor_init, cpp_base_class; linenr_T lnum = curwin->w_cursor.lnum; - char_u *line = get_cursor_line_ptr(); + const char_u *line = get_cursor_line_ptr(); if (pos->lnum <= lnum) { return cached->found; // Use the cached result @@ -1306,10 +1312,10 @@ static int get_baseclass_amount(int col) * white space and comments. Skip strings and comments. * Ignore "ignore" after "find" if it's not NULL. */ -static int cin_ends_in(char_u *s, char_u *find, char_u *ignore) +static int cin_ends_in(const char_u *s, const char_u *find, const char_u *ignore) { - char_u *p = s; - char_u *r; + const char_u *p = s; + const char_u *r; int len = (int)STRLEN(find); while (*p != NUL) { @@ -1330,7 +1336,7 @@ static int cin_ends_in(char_u *s, char_u *find, char_u *ignore) /* * Return TRUE when "s" starts with "word" and then a non-ID character. */ -static int cin_starts_with(char_u *s, char *word) +static int cin_starts_with(const char_u *s, const char *word) { int l = (int)STRLEN(word); @@ -1338,10 +1344,10 @@ static int cin_starts_with(char_u *s, char *word) } /// Recognize a `extern "C"` or `extern "C++"` linkage specifications. -static int cin_is_cpp_extern_c(char_u *s) +static int cin_is_cpp_extern_c(const char_u *s) { - char_u *p; - int has_string_literal = false; + const char_u *p; + int has_string_literal = false; s = cin_skipcomment(s); if (STRNCMP(s, "extern", 6) == 0 && (s[6] == NUL || !vim_iswordc(s[6]))) { @@ -1380,9 +1386,9 @@ static int cin_is_cpp_extern_c(char_u *s) */ static int cin_skip2pos(pos_T *trypos) { - char_u *line; - char_u *p; - char_u *new_p; + const char_u *line; + const char_u *p; + const char_u *new_p; p = line = ml_get(trypos->lnum); while (*p && (colnr_T)(p - line) < trypos->col) { @@ -1412,8 +1418,8 @@ static int cin_skip2pos(pos_T *trypos) static pos_T *find_start_brace(void) // XXX { pos_T cursor_save; - pos_T *trypos; - pos_T *pos; + pos_T *trypos; + pos_T *pos; static pos_T pos_copy; cursor_save = curwin->w_cursor; @@ -1428,7 +1434,7 @@ static pos_T *find_start_brace(void) // XXX break; } if (pos != NULL) { - curwin->w_cursor.lnum = pos->lnum; + curwin->w_cursor = *pos; } } curwin->w_cursor = cursor_save; @@ -1525,7 +1531,7 @@ static int corr_ind_maxparen(pos_T *startpos) * Set w_cursor.col to the column number of the last unmatched ')' or '{' in * line "l". "l" must point to the start of the line. */ -static int find_last_paren(char_u *l, int start, int end) +static int find_last_paren(const char_u *l, int start, int end) { int i; int retval = FALSE; @@ -1634,8 +1640,8 @@ void parse_cino(buf_T *buf) * itself is also unclosed. */ buf->b_ind_unclosed2 = sw; - /* Suppress ignoring spaces from the indent of a line starting with an - * unclosed parentheses. */ + // Suppress ignoring spaces from the indent of a line starting with an + // unclosed parenthesis. buf->b_ind_unclosed_noignore = 0; /* If the opening paren is the last nonwhite character on the line, and @@ -1647,11 +1653,11 @@ void parse_cino(buf_T *buf) * an unclosed parentheses. */ buf->b_ind_unclosed_whiteok = 0; - /* Indent a closing parentheses under the line start of the matching - * opening parentheses. */ + // Indent a closing parenthesis under the line start of the matching + // opening parenthesis. buf->b_ind_matching_paren = 0; - // Indent a closing parentheses under the previous line. + // Indent a closing parenthesis under the previous line. buf->b_ind_paren_prev = 0; // Extra indent for comments. @@ -1797,8 +1803,8 @@ int get_c_indent(void) #define BRACE_AT_START 2 // '{' is at start of line #define BRACE_AT_END 3 // '{' is at end of line linenr_T ourscope; - char_u *l; - char_u *look; + const char_u *l; + const char_u *look; char_u terminated; int lookfor; #define LOOKFOR_INITIAL 0 @@ -1902,12 +1908,25 @@ int get_c_indent(void) * If we're inside a "//" comment and there is a "//" comment in a * previous line, lineup with that one. */ - if (cin_islinecomment(theline) - && (trypos = find_line_comment()) != NULL) { // XXX - // find how indented the line beginning the comment is - getvcol(curwin, trypos, &col, NULL, NULL); - amount = col; - goto theend; + if (cin_islinecomment(theline)) { + pos_T linecomment_pos; + + trypos = find_line_comment(); // XXX + if (trypos == NULL && curwin->w_cursor.lnum > 1) { + // There may be a statement before the comment, search from the end + // of the line for a comment start. + linecomment_pos.col = check_linecomment(ml_get(curwin->w_cursor.lnum - 1)); + if (linecomment_pos.col != MAXCOL) { + trypos = &linecomment_pos; + trypos->lnum = curwin->w_cursor.lnum - 1; + } + } + if (trypos != NULL) { + // find how indented the line beginning the comment is + getvcol(curwin, trypos, &col, NULL, NULL); + amount = col; + goto theend; + } } /* * If we're inside a comment and not looking at the start of the @@ -3598,9 +3617,9 @@ laterend: static int find_match(int lookfor, linenr_T ourscope) { - char_u *look; - pos_T *theirscope; - char_u *mightbeif; + const char_u *look; + pos_T *theirscope; + const char_u *mightbeif; int elselevel; int whilelevel; diff --git a/src/nvim/input.c b/src/nvim/input.c index 2f7c5c2c16..5fa9b8b343 100644 --- a/src/nvim/input.c +++ b/src/nvim/input.c @@ -105,7 +105,7 @@ int get_keystroke(MultiQueue *events) // terminal code to complete. n = os_inchar(buf + len, maxlen, len == 0 ? -1L : 100L, 0, events); if (n > 0) { - // Replace zero and CSI by a special key code. + // Replace zero and K_SPECIAL by a special key code. n = fix_input_buffer(buf + len, n); len += n; waited = 0; diff --git a/src/nvim/keymap.c b/src/nvim/keymap.c index abf016b832..32f2158d7b 100644 --- a/src/nvim/keymap.c +++ b/src/nvim/keymap.c @@ -158,7 +158,6 @@ static const struct key_name_entry { { ESC, "Esc" }, { ESC, "Escape" }, // Alternative name { CSI, "CSI" }, - { K_CSI, "xCSI" }, { '|', "Bar" }, { '\\', "Bslash" }, { K_DEL, "Del" }, @@ -964,7 +963,6 @@ char_u *replace_termcodes(const char_u *from, const size_t from_len, char_u **bu for (i = utfc_ptr2len_len(src, (int)(end - src) + 1); i > 0; i--) { // If the character is K_SPECIAL, replace it with K_SPECIAL // KS_SPECIAL KE_FILLER. - // If compiled with the GUI replace CSI with K_CSI. if (*src == K_SPECIAL) { result[dlen++] = K_SPECIAL; result[dlen++] = KS_SPECIAL; diff --git a/src/nvim/keymap.h b/src/nvim/keymap.h index 5ff5a38614..ae2ec7835e 100644 --- a/src/nvim/keymap.h +++ b/src/nvim/keymap.h @@ -122,8 +122,6 @@ // // Entries must be in the range 0x02-0x7f (see comment at K_SPECIAL). enum key_extra { - KE_NAME = 3, // name of this terminal entry - KE_S_UP = 4, // shift-up KE_S_DOWN = 5, // shift-down @@ -220,7 +218,7 @@ enum key_extra { KE_KINS = 79, // keypad Insert key KE_KDEL = 80, // keypad Delete key - KE_CSI = 81, // CSI typed directly + // KE_CSI = 81, // Nvim doesn't need escaping CSI KE_SNR = 82, // <SNR> KE_PLUG = 83, // <Plug> KE_CMDWIN = 84, // open command-line window from Command-line Mode @@ -245,6 +243,7 @@ enum key_extra { KE_MOUSEMOVE = 100, // mouse moved with no button down // , KE_CANCEL = 101 // return from vgetc KE_EVENT = 102, // event + KE_LUA = 103, // lua special key KE_COMMAND = 104, // <Cmd> special key }; @@ -434,7 +433,6 @@ enum key_extra { #define K_MOUSELEFT TERMCAP2KEY(KS_EXTRA, KE_MOUSELEFT) #define K_MOUSERIGHT TERMCAP2KEY(KS_EXTRA, KE_MOUSERIGHT) -#define K_CSI TERMCAP2KEY(KS_EXTRA, KE_CSI) #define K_SNR TERMCAP2KEY(KS_EXTRA, KE_SNR) #define K_PLUG TERMCAP2KEY(KS_EXTRA, KE_PLUG) #define K_CMDWIN TERMCAP2KEY(KS_EXTRA, KE_CMDWIN) @@ -443,6 +441,7 @@ enum key_extra { #define K_EVENT TERMCAP2KEY(KS_EXTRA, KE_EVENT) #define K_COMMAND TERMCAP2KEY(KS_EXTRA, KE_COMMAND) +#define K_LUA TERMCAP2KEY(KS_EXTRA, KE_LUA) // Bits for modifier mask // 0x01 cannot be used, because the modifier must be 0x02 or higher diff --git a/src/nvim/lua/converter.c b/src/nvim/lua/converter.c index b6792a5a97..2b54e56df1 100644 --- a/src/nvim/lua/converter.c +++ b/src/nvim/lua/converter.c @@ -156,7 +156,7 @@ static LuaTableProps nlua_traverse_table(lua_State *const lstate) && ret.string_keys_num == 0)) { ret.type = kObjectTypeArray; if (tsize == 0 && lua_getmetatable(lstate, -1)) { - nlua_pushref(lstate, nlua_empty_dict_ref); + nlua_pushref(lstate, nlua_global_refs->empty_dict_ref); if (lua_rawequal(lstate, -2, -1)) { ret.type = kObjectTypeDictionary; } @@ -316,7 +316,7 @@ bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv) LuaRef table_ref = LUA_NOREF; if (lua_getmetatable(lstate, -1)) { lua_pop(lstate, 1); - table_ref = nlua_ref(lstate, -1); + table_ref = nlua_ref_global(lstate, -1); } const LuaTableProps table_props = nlua_traverse_table(lstate); @@ -389,7 +389,7 @@ nlua_pop_typval_table_processing_end: } case LUA_TFUNCTION: { LuaCFunctionState *state = xmalloc(sizeof(LuaCFunctionState)); - state->lua_callable.func_ref = nlua_ref(lstate, -1); + state->lua_callable.func_ref = nlua_ref_global(lstate, -1); char_u *name = register_cfunc(&nlua_CFunction_func_call, &nlua_CFunction_func_free, @@ -401,7 +401,7 @@ nlua_pop_typval_table_processing_end: } case LUA_TUSERDATA: { // TODO(bfredl): check mt.__call and convert to function? - nlua_pushref(lstate, nlua_nil_ref); + nlua_pushref(lstate, nlua_global_refs->nil_ref); bool is_nil = lua_rawequal(lstate, -2, -1); lua_pop(lstate, 1); if (is_nil) { @@ -445,7 +445,7 @@ static bool typval_conv_special = false; if (typval_conv_special) { \ lua_pushnil(lstate); \ } else { \ - nlua_pushref(lstate, nlua_nil_ref); \ + nlua_pushref(lstate, nlua_global_refs->nil_ref); \ } \ } while (0) @@ -495,7 +495,7 @@ static bool typval_conv_special = false; nlua_create_typed_table(lstate, 0, 0, kObjectTypeDictionary); \ } else { \ lua_createtable(lstate, 0, 0); \ - nlua_pushref(lstate, nlua_empty_dict_ref); \ + nlua_pushref(lstate, nlua_global_refs->empty_dict_ref); \ lua_setmetatable(lstate, -2); \ } \ } while (0) @@ -617,6 +617,14 @@ bool nlua_push_typval(lua_State *lstate, typval_T *const tv, bool special) semsg(_("E1502: Lua failed to grow stack to %i"), initial_size + 4); return false; } + if (tv->v_type == VAR_FUNC) { + ufunc_T *fp = find_func(tv->vval.v_string); + assert(fp != NULL); + if (fp->uf_cb == nlua_CFunction_func_call) { + nlua_pushref(lstate, ((LuaCFunctionState *)fp->uf_cb_state)->lua_callable.func_ref); + return true; + } + } if (encode_vim_to_lua(lstate, tv, "nlua_push_typval argument") == FAIL) { return false; } @@ -726,7 +734,7 @@ void nlua_push_Dictionary(lua_State *lstate, const Dictionary dict, bool special } else { lua_createtable(lstate, 0, (int)dict.size); if (dict.size == 0 && !special) { - nlua_pushref(lstate, nlua_empty_dict_ref); + nlua_pushref(lstate, nlua_global_refs->empty_dict_ref); lua_setmetatable(lstate, -2); } } @@ -774,7 +782,7 @@ void nlua_push_Object(lua_State *lstate, const Object obj, bool special) if (special) { lua_pushnil(lstate); } else { - nlua_pushref(lstate, nlua_nil_ref); + nlua_pushref(lstate, nlua_global_refs->nil_ref); } break; case kObjectTypeLuaRef: { @@ -1191,14 +1199,14 @@ Object nlua_pop_Object(lua_State *const lstate, bool ref, Error *const err) case LUA_TFUNCTION: if (ref) { - *cur.obj = LUAREF_OBJ(nlua_ref(lstate, -1)); + *cur.obj = LUAREF_OBJ(nlua_ref_global(lstate, -1)); } else { goto type_error; } break; case LUA_TUSERDATA: { - nlua_pushref(lstate, nlua_nil_ref); + nlua_pushref(lstate, nlua_global_refs->nil_ref); bool is_nil = lua_rawequal(lstate, -2, -1); lua_pop(lstate, 1); if (is_nil) { @@ -1232,7 +1240,7 @@ type_error: LuaRef nlua_pop_LuaRef(lua_State *const lstate, Error *err) { - LuaRef rv = nlua_ref(lstate, -1); + LuaRef rv = nlua_ref_global(lstate, -1); lua_pop(lstate, 1); return rv; } @@ -1242,7 +1250,12 @@ LuaRef nlua_pop_LuaRef(lua_State *const lstate, Error *err) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT \ { \ type ret; \ - ret = (type)lua_tonumber(lstate, -1); \ + if (lua_type(lstate, -1) != LUA_TNUMBER) { \ + api_set_error(err, kErrorTypeValidation, "Expected Lua number"); \ + ret = (type)-1; \ + } else { \ + ret = (type)lua_tonumber(lstate, -1); \ + } \ lua_pop(lstate, 1); \ return ret; \ } diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index 107ff22913..7ac80f01f0 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -4,6 +4,7 @@ #include <lauxlib.h> #include <lua.h> #include <lualib.h> +#include <tree_sitter/api.h> #include "luv/luv.h" #include "nvim/api/private/defs.h" @@ -15,9 +16,11 @@ #include "nvim/change.h" #include "nvim/cursor.h" #include "nvim/eval/userfunc.h" +#include "nvim/eval/typval.h" #include "nvim/event/loop.h" #include "nvim/event/time.h" #include "nvim/ex_cmds2.h" +#include "nvim/ex_docmd.h" #include "nvim/ex_getln.h" #include "nvim/extmark.h" #include "nvim/func_attr.h" @@ -43,6 +46,8 @@ static int in_fast_callback = 0; // Initialized in nlua_init(). static lua_State *global_lstate = NULL; +static uv_thread_t main_thread; + typedef struct { Error err; String lua_err_str; @@ -63,11 +68,16 @@ typedef struct { } #if __has_feature(address_sanitizer) -static PMap(handle_T) nlua_ref_markers = MAP_INIT; static bool nlua_track_refs = false; # define NLUA_TRACK_REFS #endif +typedef enum luv_err_type { + kCallback, + kThread, + kThreadCallback, +} luv_err_t; + /// Convert lua error into a Vim error message /// /// @param lstate Lua interpreter state. @@ -120,8 +130,21 @@ static int nlua_nvim_version(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL static void nlua_luv_error_event(void **argv) { char *error = (char *)argv[0]; + luv_err_t type = (luv_err_t)(intptr_t)argv[1]; msg_ext_set_kind("lua_error"); - semsg_multiline("Error executing luv callback:\n%s", error); + switch (type) { + case kCallback: + semsg_multiline("Error executing luv callback:\n%s", error); + break; + case kThread: + semsg_multiline("Error in luv thread:\n%s", error); + break; + case kThreadCallback: + semsg_multiline("Error in luv callback, thread:\n%s", error); + break; + default: + break; + } xfree(error); } @@ -146,7 +169,7 @@ static int nlua_luv_cfpcall(lua_State *lstate, int nargs, int nresult, int flags const char *error = lua_tostring(lstate, -1); multiqueue_put(main_loop.events, nlua_luv_error_event, - 1, xstrdup(error)); + 2, xstrdup(error), (intptr_t)kCallback); lua_pop(lstate, 1); // error message retval = -status; } else { // LUA_OK @@ -160,12 +183,112 @@ static int nlua_luv_cfpcall(lua_State *lstate, int nargs, int nresult, int flags return retval; } +static int nlua_luv_thread_cb_cfpcall(lua_State *lstate, int nargs, int nresult, + int flags) +{ + return nlua_luv_thread_common_cfpcall(lstate, nargs, nresult, flags, true); +} + +static int nlua_luv_thread_cfpcall(lua_State *lstate, int nargs, int nresult, + int flags) + FUNC_ATTR_NONNULL_ALL +{ + return nlua_luv_thread_common_cfpcall(lstate, nargs, nresult, flags, false); +} + +static int nlua_luv_thread_cfcpcall(lua_State *lstate, lua_CFunction func, + void *ud, int flags) + FUNC_ATTR_NONNULL_ARG(1, 2) +{ + lua_pushcfunction(lstate, func); + lua_pushlightuserdata(lstate, ud); + int retval = nlua_luv_thread_cfpcall(lstate, 1, 0, flags); + return retval; +} + +static int nlua_luv_thread_common_cfpcall(lua_State *lstate, int nargs, int nresult, + int flags, bool is_callback) + FUNC_ATTR_NONNULL_ALL +{ + int retval; + + int top = lua_gettop(lstate); + int status = lua_pcall(lstate, nargs, nresult, 0); + if (status) { + if (status == LUA_ERRMEM && !(flags & LUVF_CALLBACK_NOEXIT)) { + // Terminate this thread, as the main thread may be able to continue + // execution. + mch_errmsg(e_outofmem); + mch_errmsg("\n"); + lua_close(lstate); +#ifdef WIN32 + ExitThread(0); +#else + pthread_exit(0); +#endif + } + const char *error = lua_tostring(lstate, -1); + + loop_schedule_deferred(&main_loop, + event_create(nlua_luv_error_event, 2, + xstrdup(error), + is_callback + ? (intptr_t)kThreadCallback + : (intptr_t)kThread)); + lua_pop(lstate, 1); // error message + retval = -status; + } else { // LUA_OK + if (nresult == LUA_MULTRET) { + nresult = lua_gettop(lstate) - top + nargs + 1; + } + retval = nresult; + } + + return retval; +} + +static int nlua_thr_api_nvim__get_runtime(lua_State *lstate) +{ + if (lua_gettop(lstate) != 3) { + return luaL_error(lstate, "Expected 3 arguments"); + } + + luaL_checktype(lstate, -1, LUA_TTABLE); + lua_getfield(lstate, -1, "is_lua"); + if (!lua_isboolean(lstate, -1)) { + return luaL_error(lstate, "is_lua is not a boolean"); + } + bool is_lua = lua_toboolean(lstate, -1); + lua_pop(lstate, 2); + + luaL_checktype(lstate, -1, LUA_TBOOLEAN); + bool all = lua_toboolean(lstate, -1); + lua_pop(lstate, 1); + + Error err = ERROR_INIT; + const Array pat = nlua_pop_Array(lstate, &err); + if (ERROR_SET(&err)) { + luaL_where(lstate, 1); + lua_pushstring(lstate, err.msg); + api_clear_error(&err); + lua_concat(lstate, 2); + return lua_error(lstate); + } + + ArrayOf(String) ret = runtime_get_named_thread(is_lua, pat, all); + nlua_push_Array(lstate, ret, true); + api_free_array(ret); + api_free_array(pat); + + return 1; +} + static void nlua_schedule_event(void **argv) { LuaRef cb = (LuaRef)(ptrdiff_t)argv[0]; lua_State *const lstate = global_lstate; nlua_pushref(lstate, cb); - nlua_unref(lstate, cb); + nlua_unref_global(lstate, cb); if (nlua_pcall(lstate, 0, 0)) { nlua_error(lstate, _("Error executing vim.schedule lua callback: %.*s")); } @@ -182,7 +305,7 @@ static int nlua_schedule(lua_State *const lstate) return lua_error(lstate); } - LuaRef cb = nlua_ref(lstate, 1); + LuaRef cb = nlua_ref_global(lstate, 1); multiqueue_put(main_loop.events, nlua_schedule_event, 1, (void *)(ptrdiff_t)cb); @@ -300,6 +423,152 @@ static int nlua_wait(lua_State *lstate) return 2; } +static nlua_ref_state_t *nlua_new_ref_state(lua_State *lstate, bool is_thread) + FUNC_ATTR_NONNULL_ALL +{ + nlua_ref_state_t *ref_state = lua_newuserdata(lstate, sizeof(*ref_state)); + memset(ref_state, 0, sizeof(*ref_state)); + ref_state->nil_ref = LUA_NOREF; + ref_state->empty_dict_ref = LUA_NOREF; + if (!is_thread) { + nlua_global_refs = ref_state; + } + return ref_state; +} + +static nlua_ref_state_t *nlua_get_ref_state(lua_State *lstate) + FUNC_ATTR_NONNULL_ALL +{ + lua_getfield(lstate, LUA_REGISTRYINDEX, "nlua.ref_state"); + nlua_ref_state_t *ref_state = lua_touserdata(lstate, -1); + lua_pop(lstate, 1); + + return ref_state; +} + +LuaRef nlua_get_nil_ref(lua_State *lstate) + FUNC_ATTR_NONNULL_ALL +{ + nlua_ref_state_t *ref_state = nlua_get_ref_state(lstate); + return ref_state->nil_ref; +} + +LuaRef nlua_get_empty_dict_ref(lua_State *lstate) + FUNC_ATTR_NONNULL_ALL +{ + nlua_ref_state_t *ref_state = nlua_get_ref_state(lstate); + return ref_state->empty_dict_ref; +} + +int nlua_get_global_ref_count(void) +{ + return nlua_global_refs->ref_count; +} + +static void nlua_common_vim_init(lua_State *lstate, bool is_thread) + FUNC_ATTR_NONNULL_ARG(1) +{ + nlua_ref_state_t *ref_state = nlua_new_ref_state(lstate, is_thread); + lua_setfield(lstate, LUA_REGISTRYINDEX, "nlua.ref_state"); + + // vim.is_thread + lua_pushboolean(lstate, is_thread); + lua_setfield(lstate, LUA_REGISTRYINDEX, "nvim.thread"); + lua_pushcfunction(lstate, &nlua_is_thread); + lua_setfield(lstate, -2, "is_thread"); + + // vim.NIL + lua_newuserdata(lstate, 0); + lua_createtable(lstate, 0, 0); + lua_pushcfunction(lstate, &nlua_nil_tostring); + lua_setfield(lstate, -2, "__tostring"); + lua_setmetatable(lstate, -2); + ref_state->nil_ref = nlua_ref(lstate, ref_state, -1); + lua_pushvalue(lstate, -1); + lua_setfield(lstate, LUA_REGISTRYINDEX, "mpack.NIL"); + lua_setfield(lstate, -2, "NIL"); + + // vim._empty_dict_mt + lua_createtable(lstate, 0, 0); + lua_pushcfunction(lstate, &nlua_empty_dict_tostring); + lua_setfield(lstate, -2, "__tostring"); + ref_state->empty_dict_ref = nlua_ref(lstate, ref_state, -1); + lua_pushvalue(lstate, -1); + lua_setfield(lstate, LUA_REGISTRYINDEX, "mpack.empty_dict"); + lua_setfield(lstate, -2, "_empty_dict_mt"); + + // vim.loop + if (is_thread) { + luv_set_callback(lstate, nlua_luv_thread_cb_cfpcall); + luv_set_thread(lstate, nlua_luv_thread_cfpcall); + luv_set_cthread(lstate, nlua_luv_thread_cfcpcall); + } else { + luv_set_loop(lstate, &main_loop.uv); + luv_set_callback(lstate, nlua_luv_cfpcall); + } + luaopen_luv(lstate); + lua_pushvalue(lstate, -1); + lua_setfield(lstate, -3, "loop"); + + // package.loaded.luv = vim.loop + // otherwise luv will be reinitialized when require'luv' + lua_getglobal(lstate, "package"); + lua_getfield(lstate, -1, "loaded"); + lua_pushvalue(lstate, -3); + lua_setfield(lstate, -2, "luv"); + lua_pop(lstate, 3); +} + +static void nlua_common_package_init(lua_State *lstate) + FUNC_ATTR_NONNULL_ALL +{ + { + const char *code = (char *)&shared_module[0]; + if (luaL_loadbuffer(lstate, code, sizeof(shared_module) - 1, "@vim/shared.lua") + || nlua_pcall(lstate, 0, 0)) { + nlua_error(lstate, _("E5106: Error while creating shared module: %.*s\n")); + return; + } + } + + { + const char *code = (char *)&lua_load_package_module[0]; + if (luaL_loadbuffer(lstate, code, sizeof(lua_load_package_module) - 1, "@vim/_load_package.lua") + || lua_pcall(lstate, 0, 0, 0)) { + nlua_error(lstate, _("E5106: Error while creating _load_package module: %.*s")); + return; + } + } + + { + lua_getglobal(lstate, "package"); // [package] + lua_getfield(lstate, -1, "loaded"); // [package, loaded] + + const char *code = (char *)&inspect_module[0]; + if (luaL_loadbuffer(lstate, code, sizeof(inspect_module) - 1, "@vim/inspect.lua") + || nlua_pcall(lstate, 0, 1)) { + nlua_error(lstate, _("E5106: Error while creating inspect module: %.*s\n")); + return; + } + + // [package, loaded, inspect] + lua_setfield(lstate, -2, "vim.inspect"); // [package, loaded] + } + + { + const char *code = (char *)&lua_F_module[0]; + if (luaL_loadbuffer(lstate, code, sizeof(lua_F_module) - 1, "@vim/F.lua") + || nlua_pcall(lstate, 0, 1)) { + nlua_error(lstate, _("E5106: Error while creating vim.F module: %.*s\n")); + return; + } + // [package, loaded, module] + lua_setfield(lstate, -2, "vim.F"); // [package, loaded] + + lua_pop(lstate, 2); // [] + } +} + /// Initialize lua interpreter state /// /// Called by lua interpreter itself to initialize state. @@ -360,78 +629,38 @@ static int nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL lua_pushcfunction(lstate, &nlua_wait); lua_setfield(lstate, -2, "wait"); - // vim.NIL - lua_newuserdata(lstate, 0); - lua_createtable(lstate, 0, 0); - lua_pushcfunction(lstate, &nlua_nil_tostring); - lua_setfield(lstate, -2, "__tostring"); - lua_setmetatable(lstate, -2); - nlua_nil_ref = nlua_ref(lstate, -1); - lua_pushvalue(lstate, -1); - lua_setfield(lstate, LUA_REGISTRYINDEX, "mpack.NIL"); - lua_setfield(lstate, -2, "NIL"); - - // vim._empty_dict_mt - lua_createtable(lstate, 0, 0); - lua_pushcfunction(lstate, &nlua_empty_dict_tostring); - lua_setfield(lstate, -2, "__tostring"); - nlua_empty_dict_ref = nlua_ref(lstate, -1); - lua_pushvalue(lstate, -1); - lua_setfield(lstate, LUA_REGISTRYINDEX, "mpack.empty_dict"); - lua_setfield(lstate, -2, "_empty_dict_mt"); + nlua_common_vim_init(lstate, false); // internal vim._treesitter... API nlua_add_treesitter(lstate); - // vim.loop - luv_set_loop(lstate, &main_loop.uv); - luv_set_callback(lstate, nlua_luv_cfpcall); - luaopen_luv(lstate); - lua_pushvalue(lstate, -1); - lua_setfield(lstate, -3, "loop"); - - // package.loaded.luv = vim.loop - // otherwise luv will be reinitialized when require'luv' - lua_getglobal(lstate, "package"); - lua_getfield(lstate, -1, "loaded"); - lua_pushvalue(lstate, -3); - lua_setfield(lstate, -2, "luv"); - lua_pop(lstate, 3); - - nlua_state_add_stdlib(lstate); + nlua_state_add_stdlib(lstate, false); lua_setglobal(lstate, "vim"); - { - const char *code = (char *)&shared_module[0]; - if (luaL_loadbuffer(lstate, code, sizeof(shared_module) - 1, "@vim/shared.lua") - || nlua_pcall(lstate, 0, 0)) { - nlua_error(lstate, _("E5106: Error while creating shared module: %.*s\n")); - return 1; - } - } + nlua_common_package_init(lstate); { lua_getglobal(lstate, "package"); // [package] lua_getfield(lstate, -1, "loaded"); // [package, loaded] - const char *code = (char *)&inspect_module[0]; - if (luaL_loadbuffer(lstate, code, sizeof(inspect_module) - 1, "@vim/inspect.lua") + char *code = (char *)&lua_filetype_module[0]; + if (luaL_loadbuffer(lstate, code, sizeof(lua_filetype_module) - 1, "@vim/filetype.lua") || nlua_pcall(lstate, 0, 1)) { - nlua_error(lstate, _("E5106: Error while creating inspect module: %.*s\n")); + nlua_error(lstate, _("E5106: Error while creating vim.filetype module: %.*s")); return 1; } - // [package, loaded, inspect] - lua_setfield(lstate, -2, "vim.inspect"); // [package, loaded] + // [package, loaded, module] + lua_setfield(lstate, -2, "vim.filetype"); // [package, loaded] - code = (char *)&lua_F_module[0]; - if (luaL_loadbuffer(lstate, code, sizeof(lua_F_module) - 1, "@vim/F.lua") + code = (char *)&lua_keymap_module[0]; + if (luaL_loadbuffer(lstate, code, sizeof(lua_keymap_module) - 1, "@vim/keymap.lua") || nlua_pcall(lstate, 0, 1)) { - nlua_error(lstate, _("E5106: Error while creating vim.F module: %.*s\n")); + nlua_error(lstate, _("E5106: Error while creating vim.keymap module: %.*s")); return 1; } // [package, loaded, module] - lua_setfield(lstate, -2, "vim.F"); // [package, loaded] + lua_setfield(lstate, -2, "vim.keymap"); // [package, loaded] lua_pop(lstate, 2); // [] } @@ -484,9 +713,60 @@ void nlua_init(void) luaL_openlibs(lstate); nlua_state_init(lstate); + luv_set_thread_cb(nlua_thread_acquire_vm, nlua_common_free_all_mem); + global_lstate = lstate; + + main_thread = uv_thread_self(); } +static lua_State *nlua_thread_acquire_vm(void) +{ + // If it is called from the main thread, it will attempt to rebuild the cache. + const uv_thread_t self = uv_thread_self(); + if (uv_thread_equal(&main_thread, &self)) { + runtime_search_path_validate(); + } + + lua_State *lstate = luaL_newstate(); + + // Add in the lua standard libraries + luaL_openlibs(lstate); + + // print + lua_pushcfunction(lstate, &nlua_print); + lua_setglobal(lstate, "print"); + + lua_pushinteger(lstate, 0); + lua_setfield(lstate, LUA_REGISTRYINDEX, "nlua.refcount"); + + // vim + lua_newtable(lstate); + + nlua_common_vim_init(lstate, true); + + nlua_state_add_stdlib(lstate, true); + + lua_setglobal(lstate, "vim"); + + nlua_common_package_init(lstate); + + lua_getglobal(lstate, "vim"); + lua_getglobal(lstate, "package"); + lua_getfield(lstate, -1, "loaded"); + lua_getfield(lstate, -1, "vim.inspect"); + lua_setfield(lstate, -4, "inspect"); + lua_pop(lstate, 3); + + lua_getglobal(lstate, "vim"); + lua_createtable(lstate, 0, 0); + lua_pushcfunction(lstate, nlua_thr_api_nvim__get_runtime); + lua_setfield(lstate, -2, "nvim__get_runtime"); + lua_setfield(lstate, -2, "api"); + lua_pop(lstate, 1); + + return lstate; +} void nlua_free_all_mem(void) { @@ -494,26 +774,30 @@ void nlua_free_all_mem(void) return; } lua_State *lstate = global_lstate; + nlua_common_free_all_mem(lstate); +} - nlua_unref(lstate, nlua_nil_ref); - nlua_unref(lstate, nlua_empty_dict_ref); +static void nlua_common_free_all_mem(lua_State *lstate) + FUNC_ATTR_NONNULL_ALL +{ + nlua_ref_state_t *ref_state = nlua_get_ref_state(lstate); + nlua_unref(lstate, ref_state, ref_state->nil_ref); + nlua_unref(lstate, ref_state, ref_state->empty_dict_ref); #ifdef NLUA_TRACK_REFS - if (nlua_refcount) { - fprintf(stderr, "%d lua references were leaked!", nlua_refcount); + if (ref_state->ref_count) { + fprintf(stderr, "%d lua references were leaked!", ref_state->ref_count); } if (nlua_track_refs) { // in case there are leaked luarefs, leak the associated memory // to get LeakSanitizer stacktraces on exit - pmap_destroy(handle_T)(&nlua_ref_markers); + pmap_destroy(handle_T)(&ref_state->ref_markers); } #endif - nlua_refcount = 0; lua_close(lstate); } - static void nlua_print_event(void **argv) { char *str = argv[0]; @@ -591,9 +875,18 @@ static int nlua_print(lua_State *const lstate) #undef PRINT_ERROR ga_append(&msg_ga, NUL); - if (in_fast_callback) { + lua_getfield(lstate, LUA_REGISTRYINDEX, "nvim.thread"); + bool is_thread = lua_toboolean(lstate, -1); + lua_pop(lstate, 1); + + if (is_thread) { + loop_schedule_deferred(&main_loop, + event_create(nlua_print_event, 2, + msg_ga.ga_data, + (intptr_t)msg_ga.ga_len)); + } else if (in_fast_callback) { multiqueue_put(main_loop.events, nlua_print_event, - 2, msg_ga.ga_data, msg_ga.ga_len); + 2, msg_ga.ga_data, (intptr_t)msg_ga.ga_len); } else { nlua_print_event((void *[]){ msg_ga.ga_data, (void *)(intptr_t)msg_ga.ga_len }); @@ -602,10 +895,12 @@ static int nlua_print(lua_State *const lstate) nlua_print_error: ga_clear(&msg_ga); + char *buff = xmalloc(IOSIZE); const char *fmt = _("E5114: Error while converting print argument #%i: %.*s"); - size_t len = (size_t)vim_snprintf((char *)IObuff, IOSIZE, fmt, curargidx, + size_t len = (size_t)vim_snprintf(buff, IOSIZE, fmt, curargidx, (int)errmsg_len, errmsg); - lua_pushlstring(lstate, (char *)IObuff, len); + lua_pushlstring(lstate, buff, len); + xfree(buff); return lua_error(lstate); } @@ -671,7 +966,7 @@ int nlua_call(lua_State *lstate) typval_T vim_args[MAX_FUNC_ARGS + 1]; int i = 0; // also used for freeing the variables for (; i < nargs; i++) { - lua_pushvalue(lstate, (int)i+2); + lua_pushvalue(lstate, i+2); if (!nlua_pop_typval(lstate, &vim_args[i])) { api_set_error(&err, kErrorTypeException, "error converting argument %d", i+1); @@ -736,7 +1031,7 @@ static int nlua_rpc(lua_State *lstate, bool request) Array args = ARRAY_DICT_INIT; for (int i = 0; i < nargs; i++) { - lua_pushvalue(lstate, (int)i+3); + lua_pushvalue(lstate, i+3); ADD(args, nlua_pop_Object(lstate, false, &err)); if (ERROR_SET(&err)) { api_free_array(args); @@ -795,40 +1090,53 @@ static int nlua_getenv(lua_State *lstate) /// add the value to the registry -LuaRef nlua_ref(lua_State *lstate, int index) +/// The current implementation does not support calls from threads. +LuaRef nlua_ref(lua_State *lstate, nlua_ref_state_t *ref_state, int index) { lua_pushvalue(lstate, index); LuaRef ref = luaL_ref(lstate, LUA_REGISTRYINDEX); if (ref > 0) { - nlua_refcount++; + ref_state->ref_count++; #ifdef NLUA_TRACK_REFS if (nlua_track_refs) { // dummy allocation to make LeakSanitizer track our luarefs - pmap_put(handle_T)(&nlua_ref_markers, ref, xmalloc(3)); + pmap_put(handle_T)(&ref_state->ref_markers, ref, xmalloc(3)); } #endif } return ref; } + +LuaRef nlua_ref_global(lua_State *lstate, int index) +{ + return nlua_ref(lstate, nlua_global_refs, index); +} + /// remove the value from the registry -void nlua_unref(lua_State *lstate, LuaRef ref) +void nlua_unref(lua_State *lstate, nlua_ref_state_t *ref_state, LuaRef ref) { if (ref > 0) { - nlua_refcount--; + ref_state->ref_count--; #ifdef NLUA_TRACK_REFS // NB: don't remove entry from map to track double-unref if (nlua_track_refs) { - xfree(pmap_get(handle_T)(&nlua_ref_markers, ref)); + xfree(pmap_get(handle_T)(&ref_state->ref_markers, ref)); } #endif luaL_unref(lstate, LUA_REGISTRYINDEX, ref); } } +void nlua_unref_global(lua_State *lstate, LuaRef ref) +{ + nlua_unref(lstate, nlua_global_refs, ref); +} + + void api_free_luaref(LuaRef ref) { - nlua_unref(global_lstate, ref); + nlua_unref_global(global_lstate, ref); } /// push a value referenced in the registry @@ -850,7 +1158,7 @@ LuaRef api_new_luaref(LuaRef original_ref) lua_State *const lstate = global_lstate; nlua_pushref(lstate, original_ref); - LuaRef new_ref = nlua_ref(lstate, -1); + LuaRef new_ref = nlua_ref_global(lstate, -1); lua_pop(lstate, 1); return new_ref; } @@ -914,6 +1222,24 @@ void nlua_typval_call(const char *str, size_t len, typval_T *const args, int arg } } +void nlua_call_user_expand_func(expand_T *xp, typval_T *ret_tv) + FUNC_ATTR_NONNULL_ALL +{ + lua_State *const lstate = global_lstate; + + nlua_pushref(lstate, xp->xp_luaref); + lua_pushstring(lstate, (char *)xp->xp_pattern); + lua_pushstring(lstate, (char *)xp->xp_line); + lua_pushinteger(lstate, xp->xp_col); + + if (nlua_pcall(lstate, 3, 1)) { + nlua_error(lstate, _("E5108: Error executing Lua function: %.*s")); + return; + } + + nlua_pop_typval(lstate, ret_tv); +} + static void nlua_typval_exec(const char *lcmd, size_t lcmd_len, const char *name, typval_T *const args, int argcount, bool special, typval_T *ret_tv) { @@ -1034,6 +1360,19 @@ Object nlua_exec(const String str, const Array args, Error *err) return nlua_pop_Object(lstate, false, err); } +bool nlua_ref_is_function(LuaRef ref) +{ + lua_State *const lstate = global_lstate; + nlua_pushref(lstate, ref); + + // TODO(tjdevries): This should probably check for callable tables as well. + // We should put some work maybe into simplifying how all of that works + bool is_function = (lua_type(lstate, -1) == LUA_TFUNCTION); + lua_pop(lstate, 1); + + return is_function; +} + /// call a LuaRef as a function (or table with __call metamethod) /// /// @param ref the reference to call (not consumed) @@ -1096,11 +1435,23 @@ void ex_lua(exarg_T *const eap) FUNC_ATTR_NONNULL_ALL { size_t len; - char *const code = script_get(eap, &len); + char *code = script_get(eap, &len); if (eap->skip) { xfree(code); return; } + // When =expr is used transform it to print(vim.inspect(expr)) + if (code[0] == '=') { + len += sizeof("vim.pretty_print()") - sizeof("="); + // code_buf needs to be 1 char larger then len for null byte in the end. + // lua nlua_typval_exec doesn't expect null terminated string so len + // needs to end before null byte. + char *code_buf = xmallocz(len); + vim_snprintf(code_buf, len+1, "vim.pretty_print(%s)", code+1); + xfree(code); + code = code_buf; + } + nlua_typval_exec(code, len, ":lua", NULL, 0, false, NULL); xfree(code); @@ -1200,6 +1551,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) @@ -1208,11 +1562,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; @@ -1227,6 +1600,12 @@ int tslua_get_language_version(lua_State *L) return 1; } +int tslua_get_minimum_language_version(lua_State *L) +{ + lua_pushnumber(L, TREE_SITTER_MIN_COMPATIBLE_LANGUAGE_VERSION); + return 1; +} + static void nlua_add_treesitter(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL { tslua_init(lstate); @@ -1248,6 +1627,9 @@ static void nlua_add_treesitter(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL lua_pushcfunction(lstate, tslua_get_language_version); lua_setfield(lstate, -2, "_ts_get_language_version"); + + lua_pushcfunction(lstate, tslua_get_minimum_language_version); + lua_setfield(lstate, -2, "_ts_get_minimum_language_version"); } int nlua_expand_pat(expand_T *xp, char_u *pat, int *num_results, char_u ***results) @@ -1319,6 +1701,13 @@ cleanup: return ret; } +static int nlua_is_thread(lua_State *lstate) +{ + lua_getfield(lstate, LUA_REGISTRYINDEX, "nvim.thread"); + + return 1; +} + // Required functions for lua c functions as VimL callbacks int nlua_CFunction_func_call(int argcount, typval_T *argvars, typval_T *rettv, void *state) @@ -1335,7 +1724,7 @@ void nlua_CFunction_func_free(void *state) lua_State *const lstate = global_lstate; LuaCFunctionState *funcstate = (LuaCFunctionState *)state; - nlua_unref(lstate, funcstate->lua_callable.func_ref); + nlua_unref_global(lstate, funcstate->lua_callable.func_ref); xfree(funcstate); } @@ -1385,7 +1774,7 @@ char_u *nlua_register_table_as_callable(typval_T *const arg) lua_pop(lstate, 2); // [table] LuaCFunctionState *state = xmalloc(sizeof(LuaCFunctionState)); - state->lua_callable.func_ref = nlua_ref(lstate, -1); + state->lua_callable.func_ref = nlua_ref_global(lstate, -1); char_u *name = register_cfunc(&nlua_CFunction_func_call, &nlua_CFunction_func_free, state); @@ -1432,3 +1821,123 @@ void nlua_execute_on_key(int c) #endif } +// Sets the editor "script context" during Lua execution. Used by :verbose. +// @param[out] current +void nlua_set_sctx(sctx_T *current) +{ + if (p_verbose <= 0 || current->sc_sid != SID_LUA) { + return; + } + lua_State *const lstate = global_lstate; + lua_Debug *info = (lua_Debug *)xmalloc(sizeof(lua_Debug)); + + // Files where internal wrappers are defined so we can ignore them + // like vim.o/opt etc are defined in _meta.lua + char *ignorelist[] = { + "vim/_meta.lua", + "vim/keymap.lua", + }; + int ignorelist_size = sizeof(ignorelist) / sizeof(ignorelist[0]); + + for (int level = 1; true; level++) { + if (lua_getstack(lstate, level, info) != 1) { + goto cleanup; + } + if (lua_getinfo(lstate, "nSl", info) == 0) { + goto cleanup; + } + + bool is_ignored = false; + if (info->what[0] == 'C' || info->source[0] != '@') { + is_ignored = true; + } else { + for (int i = 0; i < ignorelist_size; i++) { + if (strncmp(ignorelist[i], info->source+1, strlen(ignorelist[i])) == 0) { + is_ignored = true; + break; + } + } + } + if (is_ignored) { + continue; + } + break; + } + char *source_path = fix_fname(info->source + 1); + get_current_script_id((char_u *)source_path, current); + xfree(source_path); + current->sc_lnum = info->currentline; + current->sc_seq = -1; + +cleanup: + xfree(info); +} + +void nlua_do_ucmd(ucmd_T *cmd, exarg_T *eap) +{ + lua_State *const lstate = global_lstate; + + nlua_pushref(lstate, cmd->uc_luaref); + + lua_newtable(lstate); + lua_pushboolean(lstate, eap->forceit == 1); + lua_setfield(lstate, -2, "bang"); + + lua_pushinteger(lstate, eap->line1); + lua_setfield(lstate, -2, "line1"); + + lua_pushinteger(lstate, eap->line2); + lua_setfield(lstate, -2, "line2"); + + lua_newtable(lstate); // f-args table + lua_pushstring(lstate, (const char *)eap->arg); + lua_pushvalue(lstate, -1); // Reference for potential use on f-args + lua_setfield(lstate, -4, "args"); + + // Split args by unescaped whitespace |<f-args>| (nargs dependent) + if (cmd->uc_argt & EX_NOSPC) { + // Commands where nargs = 1 or "?" fargs is the same as args + lua_rawseti(lstate, -2, 1); + } else { + // Commands with more than one possible argument we split + lua_pop(lstate, 1); // Pop the reference of opts.args + int length = (int)STRLEN(eap->arg); + int start = 0; + int end = 0; + int i = 1; + bool res = true; + while (res) { + res = uc_split_args_iter(eap->arg, i, &start, &end, length); + lua_pushlstring(lstate, (const char *)eap->arg + start, (size_t)(end - start + 1)); + lua_rawseti(lstate, -2, i); + i++; + } + } + lua_setfield(lstate, -2, "fargs"); + + lua_pushstring(lstate, (const char *)&eap->regname); + lua_setfield(lstate, -2, "reg"); + + lua_pushinteger(lstate, eap->addr_count); + lua_setfield(lstate, -2, "range"); + + if (eap->addr_count > 0) { + lua_pushinteger(lstate, eap->line2); + } else { + lua_pushinteger(lstate, cmd->uc_def); + } + lua_setfield(lstate, -2, "count"); + + // The size of this buffer is chosen empirically to be large enough to hold + // every possible modifier (with room to spare). If the list of possible + // modifiers grows this may need to be updated. + char buf[200] = { 0 }; + (void)uc_mods(buf); + lua_pushstring(lstate, buf); + lua_setfield(lstate, -2, "mods"); + + if (nlua_pcall(lstate, 1, 0)) { + nlua_error(lstate, _("Error executing Lua callback: %.*s")); + } +} + diff --git a/src/nvim/lua/executor.h b/src/nvim/lua/executor.h index a1f66bd02b..47ac51dadb 100644 --- a/src/nvim/lua/executor.h +++ b/src/nvim/lua/executor.h @@ -4,27 +4,25 @@ #include <lauxlib.h> #include <lua.h> +#include "nvim/assert.h" #include "nvim/api/private/defs.h" #include "nvim/eval/typval.h" #include "nvim/ex_cmds_defs.h" +#include "nvim/ex_docmd.h" #include "nvim/func_attr.h" #include "nvim/lua/converter.h" // Generated by msgpack-gen.lua void nlua_add_api_functions(lua_State *lstate) REAL_FATTR_NONNULL_ALL; -EXTERN LuaRef nlua_nil_ref INIT(= LUA_NOREF); -EXTERN LuaRef nlua_empty_dict_ref INIT(= LUA_NOREF); - -EXTERN int nlua_refcount INIT(= 0); - -#define set_api_error(s, err) \ - do { \ - Error *err_ = (err); \ - err_->type = kErrorTypeException; \ - err_->set = true; \ - memcpy(&err_->msg[0], s, sizeof(s)); \ - } while (0) +typedef struct { + LuaRef nil_ref; + LuaRef empty_dict_ref; + int ref_count; +#if __has_feature(address_sanitizer) + PMap(handle_T) ref_markers; +#endif +} nlua_ref_state_t; #define NLUA_CLEAR_REF(x) \ do { \ @@ -38,4 +36,7 @@ EXTERN int nlua_refcount INIT(= 0); #ifdef INCLUDE_GENERATED_DECLARATIONS # include "lua/executor.h.generated.h" #endif + +EXTERN nlua_ref_state_t *nlua_global_refs INIT(= NULL); + #endif // NVIM_LUA_EXECUTOR_H diff --git a/src/nvim/lua/spell.c b/src/nvim/lua/spell.c index b84124bc19..3a63f61200 100644 --- a/src/nvim/lua/spell.c +++ b/src/nvim/lua/spell.c @@ -1,3 +1,5 @@ +// This is an open source non-commercial project. Dear PVS-Studio, please check +// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com #include <lua.h> #include <lauxlib.h> diff --git a/src/nvim/lua/stdlib.c b/src/nvim/lua/stdlib.c index 18a579ed0f..c2ce899a74 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 } @@ -464,43 +471,52 @@ static int nlua_stricmp(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL } -void nlua_state_add_stdlib(lua_State *const lstate) +void nlua_state_add_stdlib(lua_State *const lstate, bool is_thread) { - // stricmp - lua_pushcfunction(lstate, &nlua_stricmp); - lua_setfield(lstate, -2, "stricmp"); - // str_utfindex - lua_pushcfunction(lstate, &nlua_str_utfindex); - lua_setfield(lstate, -2, "str_utfindex"); - // str_byteindex - lua_pushcfunction(lstate, &nlua_str_byteindex); - lua_setfield(lstate, -2, "str_byteindex"); - // str_utf_pos - lua_pushcfunction(lstate, &nlua_str_utf_pos); - lua_setfield(lstate, -2, "str_utf_pos"); - // str_utf_start - lua_pushcfunction(lstate, &nlua_str_utf_start); - lua_setfield(lstate, -2, "str_utf_start"); - // str_utf_end - lua_pushcfunction(lstate, &nlua_str_utf_end); - lua_setfield(lstate, -2, "str_utf_end"); - // regex - lua_pushcfunction(lstate, &nlua_regex); - lua_setfield(lstate, -2, "regex"); - luaL_newmetatable(lstate, "nvim_regex"); - luaL_register(lstate, NULL, regex_meta); - - lua_pushvalue(lstate, -1); // [meta, meta] - lua_setfield(lstate, -2, "__index"); // [meta] - lua_pop(lstate, 1); // don't use metatable now - - // _getvar - lua_pushcfunction(lstate, &nlua_getvar); - lua_setfield(lstate, -2, "_getvar"); - - // _setvar - lua_pushcfunction(lstate, &nlua_setvar); - lua_setfield(lstate, -2, "_setvar"); + if (!is_thread) { + // TODO(bfredl): some of basic string functions should already be + // (or be easy to make) threadsafe + + // stricmp + lua_pushcfunction(lstate, &nlua_stricmp); + lua_setfield(lstate, -2, "stricmp"); + // str_utfindex + lua_pushcfunction(lstate, &nlua_str_utfindex); + lua_setfield(lstate, -2, "str_utfindex"); + // str_byteindex + lua_pushcfunction(lstate, &nlua_str_byteindex); + lua_setfield(lstate, -2, "str_byteindex"); + // str_utf_pos + lua_pushcfunction(lstate, &nlua_str_utf_pos); + lua_setfield(lstate, -2, "str_utf_pos"); + // str_utf_start + lua_pushcfunction(lstate, &nlua_str_utf_start); + lua_setfield(lstate, -2, "str_utf_start"); + // str_utf_end + lua_pushcfunction(lstate, &nlua_str_utf_end); + lua_setfield(lstate, -2, "str_utf_end"); + // regex + lua_pushcfunction(lstate, &nlua_regex); + lua_setfield(lstate, -2, "regex"); + luaL_newmetatable(lstate, "nvim_regex"); + luaL_register(lstate, NULL, regex_meta); + + lua_pushvalue(lstate, -1); // [meta, meta] + lua_setfield(lstate, -2, "__index"); // [meta] + lua_pop(lstate, 1); // don't use metatable now + + // _getvar + lua_pushcfunction(lstate, &nlua_getvar); + lua_setfield(lstate, -2, "_getvar"); + + // _setvar + lua_pushcfunction(lstate, &nlua_setvar); + lua_setfield(lstate, -2, "_setvar"); + + // vim.spell + luaopen_spell(lstate); + lua_setfield(lstate, -2, "spell"); + } // vim.mpack luaopen_mpack(lstate); @@ -519,10 +535,7 @@ void nlua_state_add_stdlib(lua_State *const lstate) lua_pushcfunction(lstate, &nlua_xdl_diff); lua_setfield(lstate, -2, "diff"); - // vim.spell - luaopen_spell(lstate); - lua_setfield(lstate, -2, "spell"); - + // vim.json lua_cjson_new(lstate); lua_setfield(lstate, -2, "json"); } diff --git a/src/nvim/lua/treesitter.c b/src/nvim/lua/treesitter.c index 60a000843f..f4067ad02f 100644 --- a/src/nvim/lua/treesitter.c +++ b/src/nvim/lua/treesitter.c @@ -129,6 +129,10 @@ void tslua_init(lua_State *L) build_meta(L, TS_META_QUERY, query_meta); build_meta(L, TS_META_QUERYCURSOR, querycursor_meta); build_meta(L, TS_META_TREECURSOR, treecursor_meta); + +#ifdef NVIM_TS_HAS_SET_ALLOCATOR + ts_set_allocator(xmalloc, xcalloc, xrealloc, xfree); +#endif } int tslua_has_language(lua_State *L) diff --git a/src/nvim/lua/vim.lua b/src/nvim/lua/vim.lua index c1a1e7f162..c0247ad996 100644 --- a/src/nvim/lua/vim.lua +++ b/src/nvim/lua/vim.lua @@ -40,51 +40,11 @@ assert(vim) vim.inspect = package.loaded['vim.inspect'] assert(vim.inspect) -local pathtrails = {} -vim._so_trails = {} -for s in (package.cpath..';'):gmatch('[^;]*;') do - s = s:sub(1, -2) -- Strip trailing semicolon - -- Find out path patterns. pathtrail should contain something like - -- /?.so, \?.dll. This allows not to bother determining what correct - -- suffixes are. - local pathtrail = s:match('[/\\][^/\\]*%?.*$') - if pathtrail and not pathtrails[pathtrail] then - pathtrails[pathtrail] = true - table.insert(vim._so_trails, pathtrail) - end -end - -function vim._load_package(name) - local basename = name:gsub('%.', '/') - local paths = {"lua/"..basename..".lua", "lua/"..basename.."/init.lua"} - local found = vim.api.nvim__get_runtime(paths, false, {is_lua=true}) - if #found > 0 then - local f, err = loadfile(found[1]) - return f or error(err) - end +vim.filetype = package.loaded['vim.filetype'] +assert(vim.filetype) - local so_paths = {} - for _,trail in ipairs(vim._so_trails) do - local path = "lua"..trail:gsub('?', basename) -- so_trails contains a leading slash - table.insert(so_paths, path) - end - - found = vim.api.nvim__get_runtime(so_paths, false, {is_lua=true}) - if #found > 0 then - -- Making function name in Lua 5.1 (see src/loadlib.c:mkfuncname) is - -- a) strip prefix up to and including the first dash, if any - -- b) replace all dots by underscores - -- c) prepend "luaopen_" - -- So "foo-bar.baz" should result in "luaopen_bar_baz" - local dash = name:find("-", 1, true) - local modname = dash and name:sub(dash + 1) or name - local f, err = package.loadlib(found[1], "luaopen_"..modname:gsub("%.", "_")) - return f or error(err) - end - return nil -end - -table.insert(package.loaders, 1, vim._load_package) +vim.keymap = package.loaded['vim.keymap'] +assert(vim.keymap) -- These are for loading runtime modules lazily since they aren't available in -- the nvim binary as specified in executor.c @@ -419,23 +379,43 @@ function vim.defer_fn(fn, timeout) end ---- Notification provider +--- Display a notification to the user. --- ---- Without a runtime, writes to :Messages ----@see :help nvim_notify ----@param msg string Content of the notification to show to the user ----@param log_level number|nil enum from vim.log.levels ----@param opts table|nil additional options (timeout, etc) -function vim.notify(msg, log_level, opts) -- luacheck: no unused - if log_level == vim.log.levels.ERROR then +--- This function can be overridden by plugins to display notifications using a +--- custom provider (such as the system notification provider). By default, +--- writes to |:messages|. +--- +---@param msg string Content of the notification to show to the user. +---@param level number|nil One of the values from |vim.log.levels|. +---@param opts table|nil Optional parameters. Unused by default. +function vim.notify(msg, level, opts) -- luacheck: no unused args + if level == vim.log.levels.ERROR then vim.api.nvim_err_writeln(msg) - elseif log_level == vim.log.levels.WARN then + elseif level == vim.log.levels.WARN then vim.api.nvim_echo({{msg, 'WarningMsg'}}, true, {}) else vim.api.nvim_echo({{msg}}, true, {}) end end +do + local notified = {} + + --- Display a notification only one time. + --- + --- Like |vim.notify()|, but subsequent calls with the same message will not + --- display a notification. + --- + ---@param msg string Content of the notification to show to the user. + ---@param level number|nil One of the values from |vim.log.levels|. + ---@param opts table|nil Optional parameters. Unused by default. + function vim.notify_once(msg, level, opts) -- luacheck: no unused args + if not notified[msg] then + vim.notify(msg, level, opts) + notified[msg] = true + end + end +end ---@private function vim.register_keystroke_callback() @@ -663,4 +643,23 @@ vim._expand_pat_get_parts = function(lua_string) return parts, search_index end +---Prints given arguments in human-readable format. +---Example: +---<pre> +--- -- Print highlight group Normal and store it's contents in a variable. +--- local hl_normal = vim.pretty_print(vim.api.nvim_get_hl_by_name("Normal", true)) +---</pre> +---@see |vim.inspect()| +---@return given arguments. +function vim.pretty_print(...) + local objects = {} + for i = 1, select('#', ...) do + local v = select(i, ...) + table.insert(objects, vim.inspect(v)) + end + + print(table.concat(objects, ' ')) + return ... +end + return module diff --git a/src/nvim/lua/xdiff.c b/src/nvim/lua/xdiff.c index b2e971f9f3..ea7a700e1e 100644 --- a/src/nvim/lua/xdiff.c +++ b/src/nvim/lua/xdiff.c @@ -184,11 +184,11 @@ static NluaXdiffMode process_xdl_diff_opts(lua_State *lstate, xdemitconf_t *cfg, if (strequal("myers", v->data.string.data)) { // default } else if (strequal("minimal", v->data.string.data)) { - cfg->flags |= XDF_NEED_MINIMAL; + params->flags |= XDF_NEED_MINIMAL; } else if (strequal("patience", v->data.string.data)) { - cfg->flags |= XDF_PATIENCE_DIFF; + params->flags |= XDF_PATIENCE_DIFF; } else if (strequal("histogram", v->data.string.data)) { - cfg->flags |= XDF_HISTOGRAM_DIFF; + params->flags |= XDF_HISTOGRAM_DIFF; } else { api_set_error(err, kErrorTypeValidation, "not a valid algorithm"); goto exit_1; diff --git a/src/nvim/macros.h b/src/nvim/macros.h index c2b2c89abf..d5611f587b 100644 --- a/src/nvim/macros.h +++ b/src/nvim/macros.h @@ -105,6 +105,9 @@ #define MB_PTR_BACK(s, p) \ (p -= utf_head_off((char_u *)s, (char_u *)p - 1) + 1) +// MB_CHAR2BYTES(): convert character to bytes and advance pointer to bytes +#define MB_CHAR2BYTES(c, b) ((b) += utf_char2bytes((c), (b))) + #define RESET_BINDING(wp) \ do { \ (wp)->w_p_scb = false; \ diff --git a/src/nvim/main.c b/src/nvim/main.c index cbd1f53727..d0b3a435c3 100644 --- a/src/nvim/main.c +++ b/src/nvim/main.c @@ -9,7 +9,7 @@ #include <string.h> #include "nvim/ascii.h" -#include "nvim/aucmd.h" +#include "nvim/autocmd.h" #include "nvim/buffer.h" #include "nvim/channel.h" #include "nvim/charset.h" @@ -154,10 +154,10 @@ bool event_teardown(void) void early_init(mparm_T *paramp) { env_init(); - fs_init(); eval_init(); // init global variables init_path(argv0 ? argv0 : "nvim"); init_normal_cmds(); // Init the table of Normal mode commands. + runtime_init(); highlight_init(); #ifdef WIN32 @@ -230,6 +230,10 @@ int main(int argc, char **argv) // `argc` and `argv` are also copied, so that they can be changed. init_params(¶ms, argc, argv); + // Since os_open is called during the init_startuptime, we need to call + // fs_init before it. + fs_init(); + init_startuptime(¶ms); // Need to find "--clean" before actually parsing arguments. @@ -341,9 +345,11 @@ int main(int argc, char **argv) init_default_autocmds(); TIME_MSG("init default autocommands"); + bool vimrc_none = params.use_vimrc != NULL && strequal(params.use_vimrc, "NONE"); + // Reset 'loadplugins' for "-u NONE" before "--cmd" arguments. // Allows for setting 'loadplugins' there. - if (params.use_vimrc != NULL && strequal(params.use_vimrc, "NONE")) { + if (vimrc_none) { // When using --clean we still want to load plugins p_lpl = params.clean; } @@ -351,14 +357,23 @@ int main(int argc, char **argv) // Execute --cmd arguments. exe_pre_commands(¶ms); + if (!vimrc_none || params.clean) { + // Sources ftplugin.vim and indent.vim. We do this *before* the user startup scripts to ensure + // ftplugins run before FileType autocommands defined in the init file (which allows those + // autocommands to overwrite settings from ftplugins). + filetype_plugin_enable(); + } + // Source startup scripts. source_startup_scripts(¶ms); // If using the runtime (-u is not NONE), enable syntax & filetype plugins. - if (params.use_vimrc == NULL || !strequal(params.use_vimrc, "NONE")) { - // Does ":filetype plugin indent on". + if (!vimrc_none || params.clean) { + // Sources filetype.lua and filetype.vim unless the user explicitly disabled it with :filetype + // off. filetype_maybe_enable(); - // Sources syntax/syntax.vim, which calls `:filetype on`. + // Sources syntax/syntax.vim. We do this *after* the user startup scripts so that users can + // disable syntax highlighting with `:syntax off` if they wish. syn_maybe_enable(); } @@ -1549,7 +1564,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; } @@ -1561,7 +1576,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; } @@ -1608,7 +1623,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/map.c b/src/nvim/map.c index 1d9abe3ef2..091d653046 100644 --- a/src/nvim/map.c +++ b/src/nvim/map.c @@ -29,8 +29,6 @@ #define uint32_t_eq kh_int_hash_equal #define int_hash kh_int_hash_func #define int_eq kh_int_hash_equal -#define linenr_T_hash kh_int_hash_func -#define linenr_T_eq kh_int_hash_equal #define handle_T_hash kh_int_hash_func #define handle_T_eq kh_int_hash_equal @@ -173,15 +171,13 @@ MAP_IMPL(ptr_t, ptr_t, DEFAULT_INITIALIZER) MAP_IMPL(uint64_t, ptr_t, DEFAULT_INITIALIZER) MAP_IMPL(uint64_t, ssize_t, SSIZE_INITIALIZER) MAP_IMPL(uint64_t, uint64_t, DEFAULT_INITIALIZER) -#define EXTMARK_NS_INITIALIZER { { MAP_INIT }, 1 } -MAP_IMPL(uint64_t, ExtmarkNs, EXTMARK_NS_INITIALIZER) -#define EXTMARK_ITEM_INITIALIZER { 0, 0, NULL } -MAP_IMPL(uint64_t, ExtmarkItem, EXTMARK_ITEM_INITIALIZER) +MAP_IMPL(uint32_t, uint32_t, DEFAULT_INITIALIZER) MAP_IMPL(handle_T, ptr_t, DEFAULT_INITIALIZER) #define MSGPACK_HANDLER_INITIALIZER { .fn = NULL, .fast = false } MAP_IMPL(String, MsgpackRpcRequestHandler, MSGPACK_HANDLER_INITIALIZER) MAP_IMPL(HlEntry, int, DEFAULT_INITIALIZER) MAP_IMPL(String, handle_T, 0) +MAP_IMPL(String, int, DEFAULT_INITIALIZER) MAP_IMPL(ColorKey, ColorItem, COLOR_ITEM_INITIALIZER) diff --git a/src/nvim/map.h b/src/nvim/map.h index dbd85a4e1f..c9c89bf2fd 100644 --- a/src/nvim/map.h +++ b/src/nvim/map.h @@ -40,20 +40,13 @@ MAP_DECLS(ptr_t, ptr_t) MAP_DECLS(uint64_t, ptr_t) MAP_DECLS(uint64_t, ssize_t) MAP_DECLS(uint64_t, uint64_t) +MAP_DECLS(uint32_t, uint32_t) -// NB: this is the only way to define a struct both containing and contained -// in a map... -typedef struct ExtmarkNs { // For namespacing extmarks - Map(uint64_t, uint64_t) map[1]; // For fast lookup - uint64_t free_id; // For automatically assigning id's -} ExtmarkNs; - -MAP_DECLS(uint64_t, ExtmarkNs) -MAP_DECLS(uint64_t, ExtmarkItem) MAP_DECLS(handle_T, ptr_t) MAP_DECLS(String, MsgpackRpcRequestHandler) MAP_DECLS(HlEntry, int) MAP_DECLS(String, handle_T) +MAP_DECLS(String, int) MAP_DECLS(ColorKey, ColorItem) diff --git a/src/nvim/mark.c b/src/nvim/mark.c index 39f18b333d..8b29aa3676 100644 --- a/src/nvim/mark.c +++ b/src/nvim/mark.c @@ -217,8 +217,8 @@ void checkpcmark(void) && (equalpos(curwin->w_pcmark, curwin->w_cursor) || curwin->w_pcmark.lnum == 0)) { curwin->w_pcmark = curwin->w_prev_pcmark; - curwin->w_prev_pcmark.lnum = 0; // Show it has been checked } + curwin->w_prev_pcmark.lnum = 0; // it has been checked } /* diff --git a/src/nvim/marktree.c b/src/nvim/marktree.c index 38014ab375..918db8b76c 100644 --- a/src/nvim/marktree.c +++ b/src/nvim/marktree.c @@ -56,12 +56,6 @@ #define T MT_BRANCH_FACTOR #define ILEN (sizeof(mtnode_t)+(2 * T) * sizeof(void *)) -#define RIGHT_GRAVITY (((uint64_t)1) << 63) -#define ANTIGRAVITY(id) ((id)&(RIGHT_GRAVITY-1)) -#define IS_RIGHT(id) ((id)&RIGHT_GRAVITY) - -#define PAIRED MARKTREE_PAIRED_FLAG -#define END_FLAG MARKTREE_END_FLAG #define ID_INCR (((uint64_t)1) << 2) #define rawkey(itr) (itr->node->key[itr->i]) @@ -119,7 +113,7 @@ static int key_cmp(mtkey_t a, mtkey_t b) } // NB: keeping the events at the same pos sorted by id is actually not // necessary only make sure that START is before END etc. - return mt_generic_cmp(a.id, b.id); + return mt_generic_cmp(a.flags, b.flags); } static inline int marktree_getp_aux(const mtnode_t *x, mtkey_t k, int *r) @@ -148,7 +142,7 @@ static inline int marktree_getp_aux(const mtnode_t *x, mtkey_t k, int *r) static inline void refkey(MarkTree *b, mtnode_t *x, int i) { - pmap_put(uint64_t)(b->id2node, ANTIGRAVITY(x->key[i].id), x); + pmap_put(uint64_t)(b->id2node, mt_lookup_key(x->key[i]), x); } // put functions @@ -221,38 +215,28 @@ static inline void marktree_putp_aux(MarkTree *b, mtnode_t *x, mtkey_t k) } } -uint64_t marktree_put(MarkTree *b, int row, int col, bool right_gravity, uint8_t decor_level) +void marktree_put(MarkTree *b, mtkey_t key, int end_row, int end_col, bool end_right) { - uint64_t id = (b->next_id+=ID_INCR); - assert(decor_level < DECOR_LEVELS); - id = id | ((uint64_t)decor_level << DECOR_OFFSET); - uint64_t keyid = id; - if (right_gravity) { - // order all right gravity keys after the left ones, for effortless - // insertion (but not deletion!) - keyid |= RIGHT_GRAVITY; - } - marktree_put_key(b, row, col, keyid); - return id; -} + assert(!(key.flags & ~MT_FLAG_EXTERNAL_MASK)); + if (end_row >= 0) { + key.flags |= MT_FLAG_PAIRED; + } -uint64_t marktree_put_pair(MarkTree *b, int start_row, int start_col, bool start_right, int end_row, - int end_col, bool end_right, uint8_t decor_level) -{ - uint64_t id = (b->next_id+=ID_INCR)|PAIRED; - assert(decor_level < DECOR_LEVELS); - id = id | ((uint64_t)decor_level << DECOR_OFFSET); - uint64_t start_id = id|(start_right?RIGHT_GRAVITY:0); - uint64_t end_id = id|END_FLAG|(end_right?RIGHT_GRAVITY:0); - marktree_put_key(b, start_row, start_col, start_id); - marktree_put_key(b, end_row, end_col, end_id); - return id; + marktree_put_key(b, key); + + if (end_row >= 0) { + mtkey_t end_key = key; + end_key.flags = (uint16_t)((uint16_t)(key.flags & ~MT_FLAG_RIGHT_GRAVITY) + |(uint16_t)MT_FLAG_END + |(uint16_t)(end_right ? MT_FLAG_RIGHT_GRAVITY : 0)); + end_key.pos = (mtpos_t){ end_row, end_col }; + marktree_put_key(b, end_key); + } } -void marktree_put_key(MarkTree *b, int row, int col, uint64_t id) +void marktree_put_key(MarkTree *b, mtkey_t k) { - mtkey_t k = { .pos = { .row = row, .col = col }, .id = id }; - + k.flags |= MT_FLAG_REAL; // let's be real. if (!b->root) { b->root = (mtnode_t *)xcalloc(1, ILEN); b->n_nodes++; @@ -302,7 +286,7 @@ void marktree_del_itr(MarkTree *b, MarkTreeIter *itr, bool rev) mtnode_t *cur = itr->node; int curi = itr->i; - uint64_t id = cur->key[curi].id; + uint64_t id = mt_lookup_key(cur->key[curi]); // fprintf(stderr, "\nDELET %lu\n", id); if (itr->node->level) { @@ -364,7 +348,7 @@ void marktree_del_itr(MarkTree *b, MarkTreeIter *itr, bool rev) } b->n_keys--; - pmap_del(uint64_t)(b->id2node, ANTIGRAVITY(id)); + pmap_del(uint64_t)(b->id2node, id); // 5. bool itr_dirty = false; @@ -570,23 +554,29 @@ void marktree_free_node(mtnode_t *x) } /// NB: caller must check not pair! -uint64_t marktree_revise(MarkTree *b, MarkTreeIter *itr, uint8_t decor_level) +void marktree_revise(MarkTree *b, MarkTreeIter *itr, uint8_t decor_level, mtkey_t key) { - uint64_t old_id = rawkey(itr).id; - pmap_del(uint64_t)(b->id2node, ANTIGRAVITY(old_id)); - uint64_t new_id = (b->next_id += ID_INCR) + ((uint64_t)decor_level << DECOR_OFFSET); - rawkey(itr).id = new_id + (RIGHT_GRAVITY&old_id); - refkey(b, itr->node, itr->i); - return new_id; + // TODO(bfredl): clean up this mess and re-instantiate &= and |= forms + // once we upgrade to a non-broken version of gcc in functionaltest-lua CI + rawkey(itr).flags = (uint16_t)((uint16_t)rawkey(itr).flags & (uint16_t)~MT_FLAG_DECOR_MASK); + rawkey(itr).flags = (uint16_t)((uint16_t)rawkey(itr).flags + | (uint16_t)(decor_level << MT_FLAG_DECOR_OFFSET) + | (uint16_t)(key.flags & MT_FLAG_DECOR_MASK)); + rawkey(itr).decor_full = key.decor_full; + rawkey(itr).hl_id = key.hl_id; + rawkey(itr).priority = key.priority; } void marktree_move(MarkTree *b, MarkTreeIter *itr, int row, int col) { - uint64_t old_id = rawkey(itr).id; + mtkey_t key = rawkey(itr); // TODO(bfredl): optimize when moving a mark within a leaf without moving it // across neighbours! marktree_del_itr(b, itr, false); - marktree_put_key(b, row, col, old_id); + key.pos = (mtpos_t){ row, col }; + + + marktree_put_key(b, key); itr->node = NULL; // itr might become invalid by put } @@ -602,14 +592,15 @@ bool marktree_itr_get(MarkTree *b, int row, int col, MarkTreeIter *itr) bool marktree_itr_get_ext(MarkTree *b, mtpos_t p, MarkTreeIter *itr, bool last, bool gravity, mtpos_t *oldbase) { - mtkey_t k = { .pos = p, .id = gravity ? RIGHT_GRAVITY : 0 }; - if (last && !gravity) { - k.id = UINT64_MAX; - } if (b->n_keys == 0) { itr->node = NULL; return false; } + + mtkey_t k = { .pos = p, .flags = gravity ? MT_FLAG_RIGHT_GRAVITY : 0 }; + if (last && !gravity) { + k.flags = MT_FLAG_LAST; + } itr->pos = (mtpos_t){ 0, 0 }; itr->node = b->root; itr->lvl = 0; @@ -816,25 +807,29 @@ mtpos_t marktree_itr_pos(MarkTreeIter *itr) return pos; } -mtmark_t marktree_itr_current(MarkTreeIter *itr) +mtkey_t marktree_itr_current(MarkTreeIter *itr) { if (itr->node) { - uint64_t keyid = rawkey(itr).id; - mtpos_t pos = marktree_itr_pos(itr); - mtmark_t mark = { .row = pos.row, - .col = pos.col, - .id = ANTIGRAVITY(keyid), - .right_gravity = keyid & RIGHT_GRAVITY }; - return mark; - } - return (mtmark_t){ -1, -1, 0, false }; + mtkey_t key = rawkey(itr); + key.pos = marktree_itr_pos(itr); + return key; + } + return MT_INVALID_KEY; +} + +static bool itr_eq(MarkTreeIter *itr1, MarkTreeIter *itr2) +{ + return (&rawkey(itr1) == &rawkey(itr2)); } -static void swap_id(uint64_t *id1, uint64_t *id2) +static void itr_swap(MarkTreeIter *itr1, MarkTreeIter *itr2) { - uint64_t temp = *id1; - *id1 = *id2; - *id2 = temp; + mtkey_t key1 = rawkey(itr1); + mtkey_t key2 = rawkey(itr2); + rawkey(itr1) = key2; + rawkey(itr1).pos = key1.pos; + rawkey(itr2) = key1; + rawkey(itr2).pos = key2.pos; } bool marktree_splice(MarkTree *b, int start_line, int start_col, int old_extent_line, @@ -865,7 +860,7 @@ bool marktree_splice(MarkTree *b, int start_line, int start_col, int old_extent_ mtpos_t ipos = marktree_itr_pos(itr); if (!pos_leq(old_extent, ipos) || (old_extent.row == ipos.row && old_extent.col == ipos.col - && !IS_RIGHT(rawkey(itr).id))) { + && !mt_right(rawkey(itr)))) { marktree_itr_get_ext(b, old_extent, enditr, true, true, NULL); assert(enditr->node); // "assert" (itr <= enditr) @@ -895,13 +890,13 @@ continue_same_node: break; } - if (IS_RIGHT(rawkey(itr).id)) { - while (rawkey(itr).id != rawkey(enditr).id - && IS_RIGHT(rawkey(enditr).id)) { + if (mt_right(rawkey(itr))) { + while (!itr_eq(itr, enditr) + && mt_right(rawkey(enditr))) { marktree_itr_prev(b, enditr); } - if (!IS_RIGHT(rawkey(enditr).id)) { - swap_id(&rawkey(itr).id, &rawkey(enditr).id); + if (!mt_right(rawkey(enditr))) { + itr_swap(itr, enditr); refkey(b, itr->node, itr->i); refkey(b, enditr->node, enditr->i); } else { @@ -911,7 +906,7 @@ continue_same_node: } } - if (rawkey(itr).id == rawkey(enditr).id) { + if (itr_eq(itr, enditr)) { // actually, will be past_right after this key past_right = true; } @@ -1006,13 +1001,13 @@ void marktree_move_region(MarkTree *b, int start_row, colnr_T start_col, int ext marktree_itr_get_ext(b, start, itr, false, true, NULL); kvec_t(mtkey_t) saved = KV_INITIAL_VALUE; while (itr->node) { - mtpos_t pos = marktree_itr_pos(itr); - if (!pos_leq(pos, end) || (pos.row == end.row && pos.col == end.col - && rawkey(itr).id & RIGHT_GRAVITY)) { + mtkey_t k = marktree_itr_current(itr); + if (!pos_leq(k.pos, end) || (k.pos.row == end.row && k.pos.col == end.col + && mt_right(k))) { break; } - relative(start, &pos); - kv_push(saved, ((mtkey_t){ .pos = pos, .id = rawkey(itr).id })); + relative(start, &k.pos); + kv_push(saved, k); marktree_del_itr(b, itr, false); } @@ -1024,30 +1019,36 @@ void marktree_move_region(MarkTree *b, int start_row, colnr_T start_col, int ext for (size_t i = 0; i < kv_size(saved); i++) { mtkey_t item = kv_A(saved, i); unrelative(new, &item.pos); - marktree_put_key(b, item.pos.row, item.pos.col, item.id); + marktree_put_key(b, item); } kv_destroy(saved); } /// @param itr OPTIONAL. set itr to pos. -mtpos_t marktree_lookup(MarkTree *b, uint64_t id, MarkTreeIter *itr) +mtkey_t marktree_lookup_ns(MarkTree *b, uint32_t ns, uint32_t id, bool end, MarkTreeIter *itr) +{ + return marktree_lookup(b, mt_lookup_id(ns, id, end), itr); +} + +/// @param itr OPTIONAL. set itr to pos. +mtkey_t marktree_lookup(MarkTree *b, uint64_t id, MarkTreeIter *itr) { mtnode_t *n = pmap_get(uint64_t)(b->id2node, id); if (n == NULL) { if (itr) { itr->node = NULL; } - return (mtpos_t){ -1, -1 }; + return MT_INVALID_KEY; } int i = 0; for (i = 0; i < n->n; i++) { - if (ANTIGRAVITY(n->key[i].id) == id) { + if (mt_lookup_key(n->key[i]) == id) { goto found; } } abort(); found: {} - mtpos_t pos = n->key[i].pos; + mtkey_t key = n->key[i]; if (itr) { itr->i = i; itr->node = n; @@ -1066,14 +1067,28 @@ found_node: itr->s[b->root->level-p->level].i = i; } if (i > 0) { - unrelative(p->key[i-1].pos, &pos); + unrelative(p->key[i-1].pos, &key.pos); } n = p; } if (itr) { marktree_itr_fix_pos(b, itr); } - return pos; + return key; +} + +mtpos_t marktree_get_altpos(MarkTree *b, mtkey_t mark, MarkTreeIter *itr) +{ + return marktree_get_alt(b, mark, itr).pos; +} + +mtkey_t marktree_get_alt(MarkTree *b, mtkey_t mark, MarkTreeIter *itr) +{ + mtkey_t end = MT_INVALID_KEY; + if (mt_paired(mark)) { + end = marktree_lookup_ns(b, mark.ns, mark.id, !mt_end(mark), itr); + } + return end; } static void marktree_itr_fix_pos(MarkTree *b, MarkTreeIter *itr) @@ -1092,6 +1107,20 @@ static void marktree_itr_fix_pos(MarkTree *b, MarkTreeIter *itr) assert(x == itr->node); } +// for unit test +void marktree_put_test(MarkTree *b, uint32_t id, int row, int col, bool right_gravity) +{ + mtkey_t key = { { row, col }, UINT32_MAX, id, 0, + mt_flags(right_gravity, 0), 0, NULL }; + marktree_put(b, key, -1, -1, false); +} + +// for unit test +bool mt_right_test(mtkey_t key) +{ + return mt_right(key); +} + void marktree_check(MarkTree *b) { #ifndef NDEBUG @@ -1134,11 +1163,11 @@ static size_t check_node(MarkTree *b, mtnode_t *x, mtpos_t *last, bool *last_rig } assert(pos_leq(*last, x->key[i].pos)); if (last->row == x->key[i].pos.row && last->col == x->key[i].pos.col) { - assert(!*last_right || IS_RIGHT(x->key[i].id)); + assert(!*last_right || mt_right(x->key[i])); } - *last_right = IS_RIGHT(x->key[i].id); + *last_right = mt_right(x->key[i]); assert(x->key[i].pos.col >= 0); - assert(pmap_get(uint64_t)(b->id2node, ANTIGRAVITY(x->key[i].id)) == x); + assert(pmap_get(uint64_t)(b->id2node, mt_lookup_key(x->key[i])) == x); } if (x->level) { diff --git a/src/nvim/marktree.h b/src/nvim/marktree.h index a1dcdf5164..30f5aacebc 100644 --- a/src/nvim/marktree.h +++ b/src/nvim/marktree.h @@ -2,9 +2,12 @@ #define NVIM_MARKTREE_H #include <stdint.h> +#include <assert.h> +#include "nvim/assert.h" #include "nvim/garray.h" #include "nvim/map.h" +#include "nvim/types.h" #include "nvim/pos.h" #define MT_MAX_DEPTH 20 @@ -15,13 +18,6 @@ typedef struct { int32_t col; } mtpos_t; -typedef struct { - int32_t row; - int32_t col; - uint64_t id; - bool right_gravity; -} mtmark_t; - typedef struct mtnode_s mtnode_t; typedef struct { int oldcol; @@ -39,12 +35,75 @@ typedef struct { // Internal storage // -// NB: actual marks have id > 0, so we can use (row,col,0) pseudo-key for +// NB: actual marks have flags > 0, so we can use (row,col,0) pseudo-key for // "space before (row,col)" typedef struct { mtpos_t pos; - uint64_t id; + uint32_t ns; + uint32_t id; + int32_t hl_id; + uint16_t flags; + uint16_t priority; + Decoration *decor_full; } mtkey_t; +#define MT_INVALID_KEY (mtkey_t) { { -1, -1 }, 0, 0, 0, 0, 0, NULL } + +#define MT_FLAG_REAL (((uint16_t)1) << 0) +#define MT_FLAG_END (((uint16_t)1) << 1) +#define MT_FLAG_PAIRED (((uint16_t)1) << 2) +#define MT_FLAG_HL_EOL (((uint16_t)1) << 3) + +#define DECOR_LEVELS 4 +#define MT_FLAG_DECOR_OFFSET 4 +#define MT_FLAG_DECOR_MASK (((uint16_t)(DECOR_LEVELS-1)) << MT_FLAG_DECOR_OFFSET) + +// next flag is (((uint16_t)1) << 6) + +// These _must_ be last to preserve ordering of marks +#define MT_FLAG_RIGHT_GRAVITY (((uint16_t)1) << 14) +#define MT_FLAG_LAST (((uint16_t)1) << 15) + +#define MT_FLAG_EXTERNAL_MASK (MT_FLAG_DECOR_MASK | MT_FLAG_RIGHT_GRAVITY | MT_FLAG_HL_EOL) + +#define MARKTREE_END_FLAG (((uint64_t)1) << 63) +static inline uint64_t mt_lookup_id(uint32_t ns, uint32_t id, bool enda) +{ + return (uint64_t)ns << 32 | id | (enda?MARKTREE_END_FLAG:0); +} +#undef MARKTREE_END_FLAG + +static inline uint64_t mt_lookup_key(mtkey_t key) +{ + return mt_lookup_id(key.ns, key.id, key.flags & MT_FLAG_END); +} + +static inline bool mt_paired(mtkey_t key) +{ + return key.flags & MT_FLAG_PAIRED; +} + +static inline bool mt_end(mtkey_t key) +{ + return key.flags & MT_FLAG_END; +} + +static inline bool mt_right(mtkey_t key) +{ + return key.flags & MT_FLAG_RIGHT_GRAVITY; +} + +static inline uint8_t marktree_decor_level(mtkey_t key) +{ + return (uint8_t)((key.flags&MT_FLAG_DECOR_MASK) >> MT_FLAG_DECOR_OFFSET); +} + +static inline uint16_t mt_flags(bool right_gravity, uint8_t decor_level) +{ + assert(decor_level < DECOR_LEVELS); + return (uint16_t)((right_gravity ? MT_FLAG_RIGHT_GRAVITY : 0) + | (decor_level << MT_FLAG_DECOR_OFFSET)); +} + struct mtnode_s { int32_t n; @@ -61,7 +120,6 @@ struct mtnode_s { typedef struct { mtnode_t *root; size_t n_keys, n_nodes; - uint64_t next_id; // TODO(bfredl): the pointer to node could be part of the larger // Map(uint64_t, ExtmarkItem) essentially; PMap(uint64_t) id2node[1]; @@ -72,16 +130,4 @@ typedef struct { # include "marktree.h.generated.h" #endif -#define MARKTREE_PAIRED_FLAG (((uint64_t)1) << 1) -#define MARKTREE_END_FLAG (((uint64_t)1) << 0) - -#define DECOR_LEVELS 4 -#define DECOR_OFFSET 61 -#define DECOR_MASK (((uint64_t)(DECOR_LEVELS-1)) << DECOR_OFFSET) - -static inline uint8_t marktree_decor_level(uint64_t id) -{ - return (uint8_t)((id&DECOR_MASK) >> DECOR_OFFSET); -} - #endif // NVIM_MARKTREE_H diff --git a/src/nvim/mbyte.c b/src/nvim/mbyte.c index 5eb209a6f6..f634c7dda8 100644 --- a/src/nvim/mbyte.c +++ b/src/nvim/mbyte.c @@ -1067,7 +1067,7 @@ bool utf_printable(int c) static struct interval nonprint[] = { { 0x070f, 0x070f }, { 0x180b, 0x180e }, { 0x200b, 0x200f }, { 0x202a, 0x202e }, - { 0x206a, 0x206f }, { 0xd800, 0xdfff }, { 0xfeff, 0xfeff }, { 0xfff9, 0xfffb }, + { 0x2060, 0x206f }, { 0xd800, 0xdfff }, { 0xfeff, 0xfeff }, { 0xfff9, 0xfffb }, { 0xfffe, 0xffff } }; @@ -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++) { @@ -2089,8 +2090,7 @@ const char *mb_unescape(const char **const pp) size_t buf_idx = 0; uint8_t *str = (uint8_t *)(*pp); - // Must translate K_SPECIAL KS_SPECIAL KE_FILLER to K_SPECIAL and CSI - // KS_EXTRA KE_CSI to CSI. + // Must translate K_SPECIAL KS_SPECIAL KE_FILLER to K_SPECIAL. // Maximum length of a utf-8 character is 4 bytes. for (size_t str_idx = 0; str[str_idx] != NUL && buf_idx < 4; str_idx++) { if (str[str_idx] == K_SPECIAL @@ -2098,11 +2098,6 @@ const char *mb_unescape(const char **const pp) && str[str_idx + 2] == KE_FILLER) { buf[buf_idx++] = (char)K_SPECIAL; str_idx += 2; - } else if ((str[str_idx] == K_SPECIAL) - && str[str_idx + 1] == KS_EXTRA - && str[str_idx + 2] == KE_CSI) { - buf[buf_idx++] = (char)CSI; - str_idx += 2; } else if (str[str_idx] == K_SPECIAL) { break; // A special key can't be a multibyte char. } else { @@ -2207,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/memfile.c b/src/nvim/memfile.c index 3397296b3a..2a72d1e6a0 100644 --- a/src/nvim/memfile.c +++ b/src/nvim/memfile.c @@ -247,7 +247,7 @@ bhdr_T *mf_new(memfile_T *mfp, bool negative, unsigned page_count) } else { // need to allocate memory for this block // If the number of pages matches use the bhdr_T from the free list and // allocate the data. - void *p = xmalloc(mfp->mf_page_size * page_count); + void *p = xmalloc((size_t)mfp->mf_page_size * page_count); hp = mf_rem_free(mfp); hp->bh_data = p; } @@ -269,7 +269,7 @@ bhdr_T *mf_new(memfile_T *mfp, bool negative, unsigned page_count) // Init the data to all zero, to avoid reading uninitialized data. // This also avoids that the passwd file ends up in the swap file! - (void)memset(hp->bh_data, 0, mfp->mf_page_size * page_count); + (void)memset(hp->bh_data, 0, (size_t)mfp->mf_page_size * page_count); return hp; } @@ -528,7 +528,7 @@ bool mf_release_all(void) static bhdr_T *mf_alloc_bhdr(memfile_T *mfp, unsigned page_count) { bhdr_T *hp = xmalloc(sizeof(bhdr_T)); - hp->bh_data = xmalloc(mfp->mf_page_size * page_count); + hp->bh_data = xmalloc((size_t)mfp->mf_page_size * page_count); hp->bh_page_count = page_count; return hp; } diff --git a/src/nvim/memline.c b/src/nvim/memline.c index 08521c0dc3..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; } @@ -1032,9 +1035,9 @@ void ml_recover(bool checkext) line_count = 0; idx = 0; // start with first index in block 1 error = 0; - buf->b_ml.ml_stack_top = 0; + buf->b_ml.ml_stack_top = 0; // -V1048 buf->b_ml.ml_stack = NULL; - buf->b_ml.ml_stack_size = 0; // no stack yet + buf->b_ml.ml_stack_size = 0; // -V1048 if (curbuf->b_ffname == NULL) { cannot_open = true; @@ -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; @@ -4139,7 +4143,7 @@ long ml_find_line_or_offset(buf_T *buf, linenr_T lnum, long *offp, bool no_ff) || (offset != 0 && offset > size + buf->b_ml.ml_chunksize[curix].mlcs_totalsize - + ffdos * buf->b_ml.ml_chunksize[curix].mlcs_numlines))) { + + (long)ffdos * buf->b_ml.ml_chunksize[curix].mlcs_numlines))) { curline += buf->b_ml.ml_chunksize[curix].mlcs_numlines; size += buf->b_ml.ml_chunksize[curix].mlcs_totalsize; if (offset && ffdos) { diff --git a/src/nvim/menu.c b/src/nvim/menu.c index ac4d52c392..0db9d69a7e 100644 --- a/src/nvim/menu.c +++ b/src/nvim/menu.c @@ -361,7 +361,7 @@ static int add_menu_path(const char_u *const menu_path, vimmenu_T *menuarg, goto erret; } - // Not already there, so lets add it + // Not already there, so let's add it menu = xcalloc(1, sizeof(vimmenu_T)); menu->modes = modes; diff --git a/src/nvim/message.c b/src/nvim/message.c index befca8c76b..b39450cdc6 100644 --- a/src/nvim/message.c +++ b/src/nvim/message.c @@ -262,7 +262,6 @@ void msg_multiline_attr(const char *s, int attr, bool check_int, bool *need_clea if (*s != NUL) { msg_outtrans_attr((char_u *)s, attr); } - return; } @@ -329,7 +328,7 @@ bool msg_attr_keep(const char *s, int attr, bool keep, bool multiline) } retval = msg_end(); - if (keep && retval && vim_strsize((char_u *)s) < (int)(Rows - cmdline_row - 1) + if (keep && retval && vim_strsize((char_u *)s) < (Rows - cmdline_row - 1) * Columns + sc_col) { set_keep_msg((char *)s, 0); } @@ -356,10 +355,10 @@ char_u *msg_strtrunc(char_u *s, int force) len = vim_strsize(s); if (msg_scrolled != 0) { // Use all the columns. - room = (int)(Rows - msg_row) * Columns - 1; + room = (Rows - msg_row) * Columns - 1; } else { // Use up to 'showcmd' column. - room = (int)(Rows - msg_row - 1) * Columns + sc_col - 1; + room = (Rows - msg_row - 1) * Columns + sc_col - 1; } if (len > room && room > 0) { // may have up to 18 bytes per cell (6 per char, up to two @@ -873,7 +872,7 @@ char_u *msg_may_trunc(bool force, char_u *s) { int room; - room = (int)(Rows - cmdline_row - 1) * Columns + sc_col - 1; + room = (Rows - cmdline_row - 1) * Columns + sc_col - 1; if ((force || (shortmess(SHM_TRUNC) && !exmode_active)) && (int)STRLEN(s) - room > 0) { int size = vim_strsize(s); @@ -1095,6 +1094,10 @@ void wait_return(int redraw) return; } + if (headless_mode && !ui_active()) { + return; + } + /* * When inside vgetc(), we can't wait for a typed character at all. * With the global command (and some others) we only need one return at @@ -1215,7 +1218,7 @@ void wait_return(int redraw) } else if (vim_strchr((char_u *)"\r\n ", c) == NULL && c != Ctrl_C) { // Put the character back in the typeahead buffer. Don't use the // stuff buffer, because lmaps wouldn't work. - ins_char_typebuf(c); + ins_char_typebuf(c, mod_mask); do_redraw = true; // need a redraw even though there is // typeahead } @@ -1269,7 +1272,7 @@ static void hit_return_msg(void) { int save_p_more = p_more; - p_more = FALSE; // don't want see this message when scrolling back + p_more = false; // don't want to see this message when scrolling back if (msg_didout) { // start on a new line msg_putchar('\n'); } @@ -2647,6 +2650,17 @@ static void msg_puts_printf(const char *str, const ptrdiff_t maxlen) char buf[7]; char *p; + if (on_print.type != kCallbackNone) { + typval_T argv[1]; + argv[0].v_type = VAR_STRING; + argv[0].v_lock = VAR_UNLOCKED; + argv[0].vval.v_string = (char_u *)str; + typval_T rettv = TV_INITIAL_VALUE; + callback_call(&on_print, 1, argv, &rettv); + tv_clear(&rettv); + return; + } + while ((maxlen < 0 || s - str < maxlen) && *s != NUL) { int len = utf_ptr2len((const char_u *)s); if (!(silent_mode && p_verbose == 0)) { @@ -3450,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) { @@ -3497,7 +3512,7 @@ int do_dialog(int type, char_u *title, char_u *message, char_u *buttons, int dfl } if (c == ':' && ex_cmd) { retval = dfltbutton; - ins_char_typebuf(':'); + ins_char_typebuf(':', 0); break; } diff --git a/src/nvim/move.c b/src/nvim/move.c index 15ba6645f5..27cc2b341c 100644 --- a/src/nvim/move.c +++ b/src/nvim/move.c @@ -346,10 +346,10 @@ void update_topline(win_T *wp) */ void update_topline_win(win_T *win) { - win_T *save_curwin; - switch_win(&save_curwin, NULL, win, NULL, true); + switchwin_T switchwin; + switch_win(&switchwin, win, NULL, true); update_topline(curwin); - restore_win(save_curwin, NULL, true); + restore_win(&switchwin, true); } /* @@ -909,7 +909,7 @@ void curs_columns(win_T *wp, int may_scroll) } wp->w_skipcol = n * width; } else if (extra == 1) { - // less then 'scrolloff' lines above, decrease skipcol + // less than 'scrolloff' lines above, decrease skipcol assert(so <= INT_MAX); extra = (wp->w_skipcol + (int)so * width - wp->w_virtcol + width - 1) / width; @@ -920,7 +920,7 @@ void curs_columns(win_T *wp, int may_scroll) wp->w_skipcol -= extra * width; } } else if (extra == 2) { - // less then 'scrolloff' lines below, increase skipcol + // less than 'scrolloff' lines below, increase skipcol endcol = (n - wp->w_height_inner + 1) * width; while (endcol > wp->w_virtcol) { endcol -= width; diff --git a/src/nvim/normal.c b/src/nvim/normal.c index 3246596f16..7fe6469527 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -164,7 +164,7 @@ static const struct nv_cmd { { Ctrl_O, nv_ctrlo, 0, 0 }, { Ctrl_P, nv_up, NV_STS, false }, { Ctrl_Q, nv_visual, 0, false }, - { Ctrl_R, nv_redo, 0, 0 }, + { Ctrl_R, nv_redo_or_register, 0, 0 }, { Ctrl_S, nv_ignore, 0, 0 }, { Ctrl_T, nv_tagpop, NV_NCW, 0 }, { Ctrl_U, nv_halfpage, 0, 0 }, @@ -334,6 +334,7 @@ static const struct nv_cmd { { K_SELECT, nv_select, 0, 0 }, { K_EVENT, nv_event, NV_KEEPREG, 0 }, { K_COMMAND, nv_colon, 0, 0 }, + { K_LUA, nv_colon, 0, 0 }, }; // Number of commands in nv_cmds[]. @@ -823,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. @@ -960,6 +958,7 @@ normal_end: && s->oa.regname == 0) { if (restart_VIsual_select == 1) { VIsual_select = true; + VIsual_select_reg = 0; trigger_modechanged(); showmode(); restart_VIsual_select = 0; @@ -1009,7 +1008,14 @@ static int normal_execute(VimState *state, int key) // restart automatically. // Insert the typed character in the typeahead buffer, so that it can // be mapped in Insert mode. Required for ":lmap" to work. - ins_char_typebuf(s->c); + int len = ins_char_typebuf(s->c, mod_mask); + + // When recording and gotchars() was called the character will be + // recorded again, remove the previous recording. + if (KeyTyped) { + ungetchars(len); + } + if (restart_edit != 0) { s->c = 'd'; } else { @@ -1021,7 +1027,7 @@ static int normal_execute(VimState *state, int key) s->need_flushbuf = add_to_showcmd(s->c); - while (normal_get_command_count(s)) { continue; } + while (normal_get_command_count(s)) { } if (s->c == K_EVENT) { // Save the count values so that ca.opcount and ca.count0 are exactly @@ -1037,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, @@ -3089,8 +3095,14 @@ static void nv_gd(oparg_T *oap, int nchar, int thisblock) if ((len = find_ident_under_cursor(&ptr, FIND_IDENT)) == 0 || !find_decl(ptr, len, nchar == 'd', thisblock, SEARCH_START)) { clearopbeep(oap); - } else if ((fdo_flags & FDO_SEARCH) && KeyTyped && oap->op_type == OP_NOP) { - foldOpenCursor(); + } else { + if ((fdo_flags & FDO_SEARCH) && KeyTyped && oap->op_type == OP_NOP) { + foldOpenCursor(); + } + // clear any search statistics + if (messaging() && !msg_silent && !shortmess(SHM_SEARCHCOUNT)) { + clear_cmdline = true; + } } } @@ -3280,7 +3292,7 @@ static bool nv_screengo(oparg_T *oap, int dir, long dist) int col_off1; // margin offset for first screen line int col_off2; // margin offset for wrapped screen line int width1; // text width for first screen line - int width2; // test width for wrapped screen line + int width2; // text width for wrapped screen line oap->motion_type = kMTCharWise; oap->inclusive = (curwin->w_curswant == MAXCOL); @@ -3404,6 +3416,13 @@ static bool nv_screengo(oparg_T *oap, int dir, long dist) virtcol -= vim_strsize(get_showbreak_value(curwin)); } + int c = utf_ptr2char(get_cursor_pos_ptr()); + if (dir == FORWARD && virtcol < curwin->w_curswant + && (curwin->w_curswant <= (colnr_T)width1) + && !vim_isprintc(c) && c > 255) { + oneright(); + } + if (virtcol > curwin->w_curswant && (curwin->w_curswant < (colnr_T)width1 ? (curwin->w_curswant > (colnr_T)width1 / 2) @@ -4043,21 +4062,22 @@ static void nv_regreplay(cmdarg_T *cap) } } -/// Handle a ":" command and <Cmd>. +/// Handle a ":" command and <Cmd> or Lua keymaps. static void nv_colon(cmdarg_T *cap) { int old_p_im; bool cmd_result; bool is_cmdkey = cap->cmdchar == K_COMMAND; + bool is_lua = cap->cmdchar == K_LUA; - if (VIsual_active && !is_cmdkey) { + if (VIsual_active && !is_cmdkey && !is_lua) { nv_operator(cap); } else { if (cap->oap->op_type != OP_NOP) { // Using ":" as a movement is charwise exclusive. cap->oap->motion_type = kMTCharWise; cap->oap->inclusive = false; - } else if (cap->count0 && !is_cmdkey) { + } else if (cap->count0 && !is_cmdkey && !is_lua) { // translate "count:" into ":.,.+(count - 1)" stuffcharReadbuff('.'); if (cap->count0 > 1) { @@ -4073,9 +4093,13 @@ static void nv_colon(cmdarg_T *cap) old_p_im = p_im; - // get a command line and execute it - cmd_result = do_cmdline(NULL, is_cmdkey ? getcmdkeycmd : getexline, NULL, - cap->oap->op_type != OP_NOP ? DOCMD_KEEPLINE : 0); + if (is_lua) { + cmd_result = map_execute_lua(); + } else { + // get a command line and execute it + cmd_result = do_cmdline(NULL, is_cmdkey ? getcmdkeycmd : getexline, NULL, + cap->oap->op_type != OP_NOP ? DOCMD_KEEPLINE : 0); + } // If 'insertmode' changed, enter or exit Insert mode if (p_im != old_p_im) { @@ -4431,11 +4455,7 @@ static void nv_ident(cmdarg_T *cap) // Start insert mode in terminal buffer restart_edit = 'i'; - add_map((char_u *)"<buffer> <esc> <Cmd>call jobstop(&channel)<CR>", TERM_FOCUS, true); - do_cmdline_cmd("autocmd TermClose <buffer> " - " if !v:event.status |" - " exec 'bdelete! ' .. expand('<abuf>') |" - " endif"); + add_map((char_u *)"<buffer> <esc> <Cmd>bdelete!<CR>", TERM_FOCUS, true); } } @@ -4470,8 +4490,13 @@ bool get_visual_text(cmdarg_T *cap, char_u **pp, size_t *lenp) *pp = ml_get_pos(&VIsual); *lenp = (size_t)curwin->w_cursor.col - (size_t)VIsual.col + 1; } - // Correct the length to include the whole last character. - *lenp += (size_t)(utfc_ptr2len(*pp + (*lenp - 1)) - 1); + if (**pp == NUL) { + *lenp = 0; + } + if (*lenp > 0) { + // Correct the length to include all bytes of the last character. + *lenp += (size_t)(utfc_ptr2len(*pp + (*lenp - 1)) - 1); + } } reset_VIsual_and_resel(); return true; @@ -5005,9 +5030,7 @@ static void nv_brackets(cmdarg_T *cap) * identifier "]i" "[i" "]I" "[I" "]^I" "[^I" * define "]d" "[d" "]D" "[D" "]^D" "[^D" */ - if (vim_strchr((char_u *) - "iI\011dD\004", - cap->nchar) != NULL) { + if (vim_strchr((char_u *)"iI\011dD\004", cap->nchar) != NULL) { char_u *ptr; size_t len; @@ -5961,11 +5984,8 @@ static void nv_visual(cmdarg_T *cap) * was only one -- webb */ if (resel_VIsual_mode != 'v' || resel_VIsual_line_count > 1) { - curwin->w_cursor.lnum += - resel_VIsual_line_count * cap->count0 - 1; - if (curwin->w_cursor.lnum > curbuf->b_ml.ml_line_count) { - curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count; - } + curwin->w_cursor.lnum += resel_VIsual_line_count * cap->count0 - 1; + check_cursor(); } VIsual_mode = resel_VIsual_mode; if (VIsual_mode == 'v') { @@ -6044,7 +6064,7 @@ static void n_start_visual_mode(int c) // Corner case: the 0 position in a tab may change when going into // virtualedit. Recalculate curwin->w_cursor to avoid bad highlighting. // - if (c == Ctrl_V && (ve_flags & VE_BLOCK) && gchar_cursor() == TAB) { + if (c == Ctrl_V && (get_ve_flags() & VE_BLOCK) && gchar_cursor() == TAB) { validate_virtcol(); coladvance(curwin->w_virtcol); } @@ -6184,6 +6204,7 @@ static void nv_g_cmd(cmdarg_T *cap) // start Select mode. if (cap->arg) { VIsual_select = true; + VIsual_select_reg = 0; } else { may_start_select('c'); } @@ -6310,20 +6331,17 @@ static void nv_g_cmd(cmdarg_T *cap) curwin->w_set_curswant = true; break; - case 'M': { - const char_u *const ptr = get_cursor_line_ptr(); - + case 'M': oap->motion_type = kMTCharWise; oap->inclusive = false; - i = (int)mb_string2cells_len(ptr, STRLEN(ptr)); + i = linetabsize(get_cursor_line_ptr()); if (cap->count0 > 0 && cap->count0 <= 100) { coladvance((colnr_T)(i * cap->count0 / 100)); } else { coladvance((colnr_T)(i / 2)); } curwin->w_set_curswant = true; - } - break; + break; case '_': /* "g_": to the last non-blank character in the line or <count> lines @@ -6667,9 +6685,8 @@ static void n_opencmd(cmdarg_T *cap) (cap->cmdchar == 'o' ? 1 : 0)) ) && open_line(cap->cmdchar == 'O' ? BACKWARD : FORWARD, - has_format_option(FO_OPEN_COMS) - ? OPENLINE_DO_COM : 0, - 0)) { + has_format_option(FO_OPEN_COMS) ? OPENLINE_DO_COM : 0, + 0, NULL)) { if (win_cursorline_standout(curwin)) { // force redraw of cursorline curwin->w_valid &= ~VALID_CROW; @@ -6696,11 +6713,26 @@ static void nv_dot(cmdarg_T *cap) } } -/* - * CTRL-R: undo undo - */ -static void nv_redo(cmdarg_T *cap) +// CTRL-R: undo undo or specify register in select mode +static void nv_redo_or_register(cmdarg_T *cap) { + if (VIsual_select && VIsual_active) { + int reg; + // Get register name + no_mapping++; + reg = plain_vgetc(); + LANGMAP_ADJUST(reg, true); + no_mapping--; + + if (reg == '"') { + // the unnamed register is 0 + reg = 0; + } + + VIsual_select_reg = valid_yank_reg(reg, true) ? reg : 0; + return; + } + if (!checkclearopq(cap->oap)) { u_redo((int)cap->count1); curwin->w_set_curswant = true; @@ -6949,7 +6981,7 @@ static void adjust_cursor(oparg_T *oap) if (curwin->w_cursor.col > 0 && gchar_cursor() == NUL && (!VIsual_active || *p_sel == 'o') && !virtual_active() - && (ve_flags & VE_ONEMORE) == 0) { + && (get_ve_flags() & VE_ONEMORE) == 0) { curwin->w_cursor.col--; // prevent cursor from moving on the trail byte mb_adjust_cursor(); @@ -7021,6 +7053,7 @@ static void nv_select(cmdarg_T *cap) { if (VIsual_active) { VIsual_select = true; + VIsual_select_reg = 0; } else if (VIsual_reselect) { cap->nchar = 'v'; // fake "gv" command cap->arg = true; @@ -7155,7 +7188,7 @@ static void nv_esc(cmdarg_T *cap) void set_cursor_for_append_to_line(void) { curwin->w_set_curswant = true; - if (ve_flags == VE_ALL) { + if (get_ve_flags() == VE_ALL) { const int save_State = State; // Pretend Insert mode here to allow the cursor on the @@ -7473,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 @@ -7488,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; @@ -7497,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 c6f9c5f04f..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; @@ -267,14 +275,14 @@ void op_shift(oparg_T *oap, int curs_top, int amount) msg_attr_keep((char *)IObuff, 0, true, false); } - /* - * Set "'[" and "']" marks. - */ - curbuf->b_op_start = oap->start; - curbuf->b_op_end.lnum = oap->end.lnum; - curbuf->b_op_end.col = (colnr_T)STRLEN(ml_get(oap->end.lnum)); - if (curbuf->b_op_end.col > 0) { - curbuf->b_op_end.col--; + if (!cmdmod.lockmarks) { + // Set "'[" and "']" marks. + curbuf->b_op_start = oap->start; + curbuf->b_op_end.lnum = oap->end.lnum; + curbuf->b_op_end.col = (colnr_T)STRLEN(ml_get(oap->end.lnum)); + if (curbuf->b_op_end.col > 0) { + curbuf->b_op_end.col--; + } } changed_lines(oap->start.lnum, 0, oap->end.lnum + 1, 0L, true); @@ -381,8 +389,8 @@ static void shift_block(oparg_T *oap, int amount) } } for (; ascii_iswhite(*bd.textstart);) { - // TODO: is passing bd.textstart for start of the line OK? - incr = lbr_chartabsize_adv(bd.textstart, &bd.textstart, (bd.start_vcol)); + // TODO(fmoralesc): is passing bd.textstart for start of the line OK? + incr = lbr_chartabsize_adv(bd.textstart, &bd.textstart, bd.start_vcol); total += incr; bd.start_vcol += incr; } @@ -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) { @@ -694,9 +704,11 @@ void op_reindent(oparg_T *oap, Indenter how) "%" PRId64 " lines indented ", i), (int64_t)i); } - // set '[ and '] marks - curbuf->b_op_start = oap->start; - curbuf->b_op_end = oap->end; + if (!cmdmod.lockmarks) { + // set '[ and '] marks + curbuf->b_op_start = oap->start; + curbuf->b_op_end = oap->end; + } } /* @@ -915,10 +927,29 @@ int do_record(int c) apply_autocmds(EVENT_RECORDINGENTER, NULL, NULL, false, curbuf); } } else { // stop recording - // Get the recorded key hits. K_SPECIAL and CSI will be escaped, this + save_v_event_T save_v_event; + // Set the v:event dictionary with information about the recording. + dict_T *dict = get_v_event(&save_v_event); + + // The recorded text contents. + p = get_recorded(); + if (p != NULL) { + // Remove escaping for K_SPECIAL in multi-byte chars. + vim_unescape_ks(p); + (void)tv_dict_add_str(dict, S_LEN("regcontents"), (const char *)p); + } + + // Name of requested register, or empty string for unnamed operation. + char buf[NUMBUFLEN+2]; + buf[0] = (char)regname; + buf[1] = NUL; + (void)tv_dict_add_str(dict, S_LEN("regname"), buf); + + // Get the recorded key hits. K_SPECIAL will be escaped, this // needs to be removed again to put it in a register. exec_reg then // adds the escaping back later. apply_autocmds(EVENT_RECORDINGLEAVE, NULL, NULL, false, curbuf); + restore_v_event(dict, &save_v_event); reg_recorded = reg_recording; reg_recording = 0; if (ui_has(kUIMessages)) { @@ -926,13 +957,9 @@ int do_record(int c) } else { msg(""); } - p = get_recorded(); if (p == NULL) { retval = FAIL; } else { - // Remove escaping for CSI and K_SPECIAL in multi-byte chars. - vim_unescape_csi(p); - // We don't want to change the default register here, so save and // restore the current register name. old_y_previous = y_previous; @@ -1084,7 +1111,7 @@ int do_execreg(int regname, int colon, int addcr, int silent) return FAIL; } } - escaped = vim_strsave_escape_csi(reg->y_array[i]); + escaped = vim_strsave_escape_ks(reg->y_array[i]); retval = ins_typebuf(escaped, remap, 0, true, silent); xfree(escaped); if (retval == FAIL) { @@ -1126,7 +1153,7 @@ static void put_reedit_in_typebuf(int silent) /// Insert register contents "s" into the typeahead buffer, so that it will be /// executed again. /// -/// @param esc when true then it is to be taken literally: Escape CSI +/// @param esc when true then it is to be taken literally: Escape K_SPECIAL /// characters and no remapping. /// @param colon add ':' before the line static int put_in_typebuf(char_u *s, bool esc, bool colon, int silent) @@ -1141,7 +1168,7 @@ static int put_in_typebuf(char_u *s, bool esc, bool colon, int silent) char_u *p; if (esc) { - p = vim_strsave_escape_csi(s); + p = vim_strsave_escape_ks(s); } else { p = s; } @@ -1420,6 +1447,11 @@ int op_delete(oparg_T *oap) return FAIL; } + if (VIsual_select && oap->is_VIsual) { + // Use the register given with CTRL_R, defaults to zero + oap->regname = VIsual_select_reg; + } + mb_adjust_opend(oap); /* @@ -1716,13 +1748,15 @@ int op_delete(oparg_T *oap) msgmore(curbuf->b_ml.ml_line_count - old_lcount); setmarks: - if (oap->motion_type == kMTBlockWise) { - curbuf->b_op_end.lnum = oap->end.lnum; - curbuf->b_op_end.col = oap->start.col; - } else { - curbuf->b_op_end = oap->start; + if (!cmdmod.lockmarks) { + if (oap->motion_type == kMTBlockWise) { + curbuf->b_op_end.lnum = oap->end.lnum; + curbuf->b_op_end.col = oap->start.col; + } else { + curbuf->b_op_end = oap->start; + } + curbuf->b_op_start = oap->start; } - curbuf->b_op_start = oap->start; return OK; } @@ -1927,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 { @@ -1987,9 +2024,11 @@ static int op_replace(oparg_T *oap, int c) check_cursor(); changed_lines(oap->start.lnum, oap->start.col, oap->end.lnum + 1, 0L, true); - // Set "'[" and "']" marks. - curbuf->b_op_start = oap->start; - curbuf->b_op_end = oap->end; + if (!cmdmod.lockmarks) { + // Set "'[" and "']" marks. + curbuf->b_op_start = oap->start; + curbuf->b_op_end = oap->end; + } return OK; } @@ -2058,11 +2097,11 @@ void op_tilde(oparg_T *oap) redraw_curbuf_later(INVERTED); } - /* - * Set '[ and '] marks. - */ - curbuf->b_op_start = oap->start; - curbuf->b_op_end = oap->end; + if (!cmdmod.lockmarks) { + // Set '[ and '] marks. + curbuf->b_op_start = oap->start; + curbuf->b_op_end = oap->end; + } if (oap->line_count > p_report) { smsg(NGETTEXT("%" PRId64 " line changed", @@ -2181,19 +2220,22 @@ void op_insert(oparg_T *oap, long count1) // doing block_prep(). When only "block" is used, virtual edit is // already disabled, but still need it when calling // coladvance_force(). + // coladvance_force() uses get_ve_flags() to get the 'virtualedit' + // state for the current window. To override that state, we need to + // set the window-local value of ve_flags rather than the global value. if (curwin->w_cursor.coladd > 0) { - unsigned old_ve_flags = ve_flags; + unsigned old_ve_flags = curwin->w_ve_flags; - ve_flags = VE_ALL; if (u_save_cursor() == FAIL) { return; } + curwin->w_ve_flags = VE_ALL; coladvance_force(oap->op_type == OP_APPEND ? oap->end_vcol + 1 : getviscol()); if (oap->op_type == OP_APPEND) { --curwin->w_cursor.col; } - ve_flags = old_ve_flags; + curwin->w_ve_flags = old_ve_flags; } // Get the info about the block before entering the text block_prep(oap, &bd, oap->start.lnum, true); @@ -2241,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 @@ -2275,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; @@ -2339,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 @@ -2751,17 +2801,15 @@ static void op_yank_reg(oparg_T *oap, bool message, yankreg_T *reg, bool append) } } - /* - * Set "'[" and "']" marks. - */ - curbuf->b_op_start = oap->start; - curbuf->b_op_end = oap->end; - if (yank_type == kMTLineWise) { - curbuf->b_op_start.col = 0; - curbuf->b_op_end.col = MAXCOL; + if (!cmdmod.lockmarks) { + // Set "'[" and "']" marks. + curbuf->b_op_start = oap->start; + curbuf->b_op_end = oap->end; + if (yank_type == kMTLineWise) { + curbuf->b_op_start.col = 0; + curbuf->b_op_end.col = MAXCOL; + } } - - return; } // Copy a block range into a register. @@ -2786,7 +2834,7 @@ static void yank_copy_line(yankreg_T *reg, struct block_def *bd, size_t y_idx, if (exclude_trailing_space) { int s = bd->textlen + bd->endspaces; - while (ascii_iswhite(*(bd->textstart + s - 1)) && s > 0) { + while (s > 0 && ascii_iswhite(*(bd->textstart + s - 1))) { s = s - utf_head_off(bd->textstart, bd->textstart + s - 1) - 1; pnew--; } @@ -2891,6 +2939,9 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) char_u *insert_string = NULL; bool allocated = false; long cnt; + const pos_T orig_start = curbuf->b_op_start; + const pos_T orig_end = curbuf->b_op_end; + unsigned int cur_ve_flags = get_ve_flags(); if (flags & PUT_FIXINDENT) { orig_indent = get_indent(); @@ -2961,7 +3012,7 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) eol = (*(cursor_pos + utfc_ptr2len(cursor_pos)) == NUL); } - bool ve_allows = (ve_flags == VE_ALL || ve_flags == VE_ONEMORE); + bool ve_allows = (cur_ve_flags == VE_ALL || cur_ve_flags == VE_ONEMORE); bool eof = curbuf->b_ml.ml_line_count == curwin->w_cursor.lnum && one_past_line; if (ve_allows || !(eol || eof)) { @@ -3137,13 +3188,14 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) yanklen = (int)STRLEN(y_array[0]); - if (ve_flags == VE_ALL && y_type == kMTCharWise) { + if (cur_ve_flags == VE_ALL && y_type == kMTCharWise) { if (gchar_cursor() == TAB) { - /* Don't need to insert spaces when "p" on the last position of a - * tab or "P" on the first position. */ int viscol = getviscol(); + long ts = curbuf->b_p_ts; + // Don't need to insert spaces when "p" on the last position of a + // tab or "P" on the first position. if (dir == FORWARD - ? tabstop_padding(viscol, curbuf->b_p_ts, curbuf->b_p_vts_array) != 1 + ? tabstop_padding(viscol, ts, curbuf->b_p_vts_array) != 1 : curwin->w_cursor.coladd > 0) { coladvance_force(viscol); } else { @@ -3165,7 +3217,7 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) colnr_T endcol2 = 0; if (dir == FORWARD && c != NUL) { - if (ve_flags == VE_ALL) { + if (cur_ve_flags == VE_ALL) { getvcol(curwin, &curwin->w_cursor, &col, NULL, &endcol2); } else { getvcol(curwin, &curwin->w_cursor, NULL, NULL, &col); @@ -3179,9 +3231,8 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) } col += curwin->w_cursor.coladd; - if (ve_flags == VE_ALL - && (curwin->w_cursor.coladd > 0 - || endcol2 == curwin->w_cursor.col)) { + if (cur_ve_flags == VE_ALL + && (curwin->w_cursor.coladd > 0 || endcol2 == curwin->w_cursor.col)) { if (dir == FORWARD && c == NUL) { col++; } @@ -3263,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); @@ -3288,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); @@ -3379,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, @@ -3393,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++) { @@ -3419,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 @@ -3440,6 +3511,9 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) curwin->w_cursor.col -= first_byte_off; } } else { + linenr_T new_lnum = new_cursor.lnum; + size_t len; + // Insert at least one line. When y_type is kMTCharWise, break the first // line in two. for (cnt = 1; cnt <= count; cnt++) { @@ -3456,6 +3530,7 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) STRCAT(newp, ptr); // insert second line ml_append(lnum, newp, (colnr_T)0, false); + new_lnum++; xfree(newp); oldp = ml_get(lnum); @@ -3471,10 +3546,11 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) } for (; i < y_size; i++) { - if ((y_type != kMTCharWise || i < y_size - 1) - && ml_append(lnum, y_array[i], (colnr_T)0, false) - == FAIL) { - goto error; + if ((y_type != kMTCharWise || i < y_size - 1)) { + if (ml_append(lnum, y_array[i], (colnr_T)0, false) == FAIL) { + goto error; + } + new_lnum++; } lnum++; ++nr_lines; @@ -3524,6 +3600,10 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) extmark_splice(curbuf, (int)new_cursor.lnum-1, col + 1, 0, 0, 0, (int)y_size+1, 0, totsize+2, kExtmarkUndo); } + + if (cnt == 1) { + new_lnum = lnum; + } } error: @@ -3551,11 +3631,12 @@ error: // Put the '] mark on the first byte of the last inserted character. // Correct the length for change in indent. - curbuf->b_op_end.lnum = lnum; - col = (colnr_T)STRLEN(y_array[y_size - 1]) - lendiff; + curbuf->b_op_end.lnum = new_lnum; + len = STRLEN(y_array[y_size - 1]); + col = (colnr_T)len - lendiff; if (col > 1) { curbuf->b_op_end.col = col - 1 - utf_head_off(y_array[y_size - 1], - y_array[y_size - 1] + col - 1); + y_array[y_size - 1] + len - 1); } else { curbuf->b_op_end.col = 0; } @@ -3574,8 +3655,12 @@ error: } curwin->w_cursor.col = 0; } else { - curwin->w_cursor.lnum = lnum; + curwin->w_cursor.lnum = new_lnum; curwin->w_cursor.col = col; + curbuf->b_op_end = curwin->w_cursor; + if (col > 1) { + curbuf->b_op_end.col = col - 1; + } } } else if (y_type == kMTLineWise) { // put cursor on first non-blank in first inserted line @@ -3594,6 +3679,10 @@ error: curwin->w_set_curswant = TRUE; end: + if (cmdmod.lockmarks) { + curbuf->b_op_start = orig_start; + curbuf->b_op_end = orig_end; + } if (allocated) { xfree(insert_string); } @@ -3613,14 +3702,16 @@ end: */ void adjust_cursor_eol(void) { + unsigned int cur_ve_flags = get_ve_flags(); + if (curwin->w_cursor.col > 0 && gchar_cursor() == NUL - && (ve_flags & VE_ONEMORE) == 0 + && (cur_ve_flags & VE_ONEMORE) == 0 && !(restart_edit || (State & INSERT))) { // Put the cursor on the last character in the line. dec_cursor(); - if (ve_flags == VE_ALL) { + if (cur_ve_flags == VE_ALL) { colnr_T scol, ecol; // Coladd is set to the width of the last character. @@ -3674,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; @@ -3687,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) { @@ -3718,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); } @@ -3938,7 +4028,7 @@ int do_join(size_t count, int insert_space, int save_undo, int use_formatoptions // and setup the array of space strings lengths for (t = 0; t < (linenr_T)count; t++) { curr = curr_start = ml_get((linenr_T)(curwin->w_cursor.lnum + t)); - if (t == 0 && setmark) { + if (t == 0 && setmark && !cmdmod.lockmarks) { // Set the '[ mark. curwin->w_buffer->b_op_start.lnum = curwin->w_cursor.lnum; curwin->w_buffer->b_op_start.col = (colnr_T)STRLEN(curr); @@ -4059,7 +4149,7 @@ int do_join(size_t count, int insert_space, int save_undo, int use_formatoptions ml_replace(curwin->w_cursor.lnum, newp, false); - if (setmark) { + if (setmark && !cmdmod.lockmarks) { // Set the '] mark. curwin->w_buffer->b_op_end.lnum = curwin->w_cursor.lnum; curwin->w_buffer->b_op_end.col = sumsize; @@ -4123,7 +4213,7 @@ static int same_leader(linenr_T lnum, int leader1_len, char_u *leader1_flags, in * If first leader has 'f' flag, the lines can be joined only if the * second line does not have a leader. * If first leader has 'e' flag, the lines can never be joined. - * If fist leader has 's' flag, the lines can only be joined if there is + * If first leader has 's' flag, the lines can only be joined if there is * some text after it and the second line has the 'm' flag. */ if (leader1_flags != NULL) { @@ -4197,8 +4287,10 @@ static void op_format(oparg_T *oap, int keep_cursor) redraw_curbuf_later(INVERTED); } - // Set '[ mark at the start of the formatted area - curbuf->b_op_start = oap->start; + if (!cmdmod.lockmarks) { + // Set '[ mark at the start of the formatted area + curbuf->b_op_start = oap->start; + } // For "gw" remember the cursor position and put it back below (adjusted // for joined and split lines). @@ -4220,8 +4312,10 @@ static void op_format(oparg_T *oap, int keep_cursor) old_line_count = curbuf->b_ml.ml_line_count - old_line_count; msgmore(old_line_count); - // put '] mark on the end of the formatted area - curbuf->b_op_end = curwin->w_cursor; + if (!cmdmod.lockmarks) { + // put '] mark on the end of the formatted area + curbuf->b_op_end = curwin->w_cursor; + } if (keep_cursor) { curwin->w_cursor = saved_cursor; @@ -4308,7 +4402,7 @@ void format_lines(linenr_T line_count, int avoid_fex) int leader_len = 0; // leader len of current line int next_leader_len; // leader len of next line char_u *leader_flags = NULL; // flags for leader of current line - char_u *next_leader_flags; // flags for leader of next line + char_u *next_leader_flags = NULL; // flags for leader of next line bool advance = true; int second_indent = -1; // indent for second line (comment aware) bool first_par_line = true; @@ -4425,7 +4519,14 @@ void format_lines(linenr_T line_count, int avoid_fex) leader_len, leader_flags, next_leader_len, next_leader_flags)) { - is_end_par = true; + // Special case: If the next line starts with a line comment + // and this line has a line comment after some text, the + // paragraph doesn't really end. + if (next_leader_flags == NULL + || STRNCMP(next_leader_flags, "://", 3) != 0 + || check_linecomment(get_cursor_line_ptr()) == MAXCOL) { + is_end_par = true; + } } /* @@ -4819,7 +4920,7 @@ void op_addsub(oparg_T *oap, linenr_T Prenum1, bool g_cmd) // Set '[ mark if something changed. Keep the last end // position from do_addsub(). - if (change_cnt > 0) { + if (change_cnt > 0 && !cmdmod.lockmarks) { curbuf->b_op_start = startpos; } @@ -5173,11 +5274,13 @@ int do_addsub(int op_type, pos_T *pos, int length, linenr_T Prenum1) } } - // set the '[ and '] marks - curbuf->b_op_start = startpos; - curbuf->b_op_end = endpos; - if (curbuf->b_op_end.col > 0) { - curbuf->b_op_end.col--; + if (!cmdmod.lockmarks) { + // set the '[ and '] marks + curbuf->b_op_start = startpos; + curbuf->b_op_end = endpos; + if (curbuf->b_op_end.col > 0) { + curbuf->b_op_end.col--; + } } theend: @@ -5608,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; @@ -5990,6 +6095,8 @@ static void op_function(const oparg_T *oap) { const TriState save_virtual_op = virtual_op; const bool save_finish_op = finish_op; + const pos_T orig_start = curbuf->b_op_start; + const pos_T orig_end = curbuf->b_op_end; if (*p_opfunc == NUL) { emsg(_("E774: 'operatorfunc' is empty")); @@ -6023,6 +6130,10 @@ static void op_function(const oparg_T *oap) virtual_op = save_virtual_op; finish_op = save_finish_op; + if (cmdmod.lockmarks) { + curbuf->b_op_start = orig_start; + curbuf->b_op_end = orig_end; + } } } diff --git a/src/nvim/option.c b/src/nvim/option.c index 65adda3c01..9068c90dd1 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -281,7 +281,7 @@ typedef struct vimoption { # include "options.generated.h" #endif -#define PARAM_COUNT ARRAY_SIZE(options) +#define OPTION_COUNT ARRAY_SIZE(options) static char *(p_ambw_values[]) = { "single", "double", NULL }; static char *(p_bg_values[]) = { "light", "dark", NULL }; @@ -931,6 +931,21 @@ void set_title_defaults(void) } } +void ex_set(exarg_T *eap) +{ + int flags = 0; + + if (eap->cmdidx == CMD_setlocal) { + flags = OPT_LOCAL; + } else if (eap->cmdidx == CMD_setglobal) { + flags = OPT_GLOBAL; + } + if (eap->forceit) { + flags |= OPT_ONECOLUMN; + } + (void)do_set(eap->arg, flags); +} + /// Parse 'arg' for option settings. /// /// 'arg' may be IObuff, but only when no errors can be present and option @@ -1345,7 +1360,7 @@ int do_set(char_u *arg, int opt_flags) if (nextchar == '&') { // set to default val newval = options[opt_idx].def_val; - // expand environment variables and ~ (since the + // expand environment variables and ~ since the // default value was already expanded, only // required when an environment variable was set // later @@ -1766,7 +1781,7 @@ static char *illegal_char(char *errbuf, size_t errbuflen, int c) if (errbuf == NULL) { return ""; } - vim_snprintf((char *)errbuf, errbuflen, _("E539: Illegal character <%s>"), + vim_snprintf(errbuf, errbuflen, _("E539: Illegal character <%s>"), (char *)transchar(c)); return errbuf; } @@ -1969,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. @@ -1993,9 +2007,9 @@ static void didset_options2(void) // Parse default for 'wildmode'. check_opt_wim(); xfree(curbuf->b_p_vsts_array); - tabstop_set(curbuf->b_p_vsts, &curbuf->b_p_vsts_array); + (void)tabstop_set(curbuf->b_p_vsts, &curbuf->b_p_vsts_array); xfree(curbuf->b_p_vts_array); - tabstop_set(curbuf->b_p_vts, &curbuf->b_p_vts_array); + (void)tabstop_set(curbuf->b_p_vts, &curbuf->b_p_vts_array); } /// Check for string options that are NULL (normally only termcap options). @@ -2269,12 +2283,12 @@ static char *set_string_option(const int opt_idx, const char *const value, const *varp = s; char *const saved_oldval = xstrdup(oldval); - char *const saved_oldval_l = (oldval_l != NULL) ? xstrdup((char *)oldval_l) : 0; - char *const saved_oldval_g = (oldval_g != NULL) ? xstrdup((char *)oldval_g) : 0; + char *const saved_oldval_l = (oldval_l != NULL) ? xstrdup(oldval_l) : 0; + char *const saved_oldval_g = (oldval_g != NULL) ? xstrdup(oldval_g) : 0; char *const saved_newval = xstrdup(s); int value_checked = false; - char *const r = did_set_string_option(opt_idx, (char_u **)varp, (int)true, + char *const r = did_set_string_option(opt_idx, (char_u **)varp, true, (char_u *)oldval, NULL, 0, opt_flags, &value_checked); if (r == NULL) { @@ -2762,7 +2776,7 @@ ambw_end: if (!ascii_isdigit(*(s - 1))) { if (errbuf != NULL) { - vim_snprintf((char *)errbuf, errbuflen, + vim_snprintf(errbuf, errbuflen, _("E526: Missing number after <%s>"), transchar_byte(*(s - 1))); errmsg = errbuf; @@ -2953,7 +2967,7 @@ ambw_end: } } else { if (errbuf != NULL) { - vim_snprintf((char *)errbuf, errbuflen, + vim_snprintf(errbuf, errbuflen, _("E535: Illegal character after <%c>"), *--s); errmsg = errbuf; @@ -3084,14 +3098,27 @@ ambw_end: if (foldmethodIsIndent(curwin)) { foldUpdateAll(curwin); } - } else if (varp == &p_ve) { // 'virtualedit' - if (opt_strings_flags(p_ve, p_ve_values, &ve_flags, true) != OK) { - errmsg = e_invarg; - } else if (STRCMP(p_ve, oldval) != 0) { - // Recompute cursor position in case the new 've' setting - // changes something. - validate_virtcol(); - coladvance(curwin->w_virtcol); + } else if (gvarp == &p_ve) { // 'virtualedit' + char_u *ve = p_ve; + unsigned int *flags = &ve_flags; + + if (opt_flags & OPT_LOCAL) { + ve = curwin->w_p_ve; + flags = &curwin->w_ve_flags; + } + + if ((opt_flags & OPT_LOCAL) && *ve == NUL) { + // make the local value empty: use the global value + *flags = 0; + } else { + if (opt_strings_flags(ve, p_ve_values, flags, true) != OK) { + errmsg = e_invarg; + } else if (STRCMP(p_ve, oldval) != 0) { + // Recompute cursor position in case the new 've' setting + // changes something. + validate_virtcol(); + coladvance(curwin->w_virtcol); + } } } else if (varp == &p_csqf) { if (p_csqf != NULL) { @@ -3150,10 +3177,7 @@ ambw_end: char_u *cp; if (!(*varp)[0] || ((*varp)[0] == '0' && !(*varp)[1])) { - if (curbuf->b_p_vsts_array) { - xfree(curbuf->b_p_vsts_array); - curbuf->b_p_vsts_array = 0; - } + XFREE_CLEAR(curbuf->b_p_vsts_array); } else { for (cp = *varp; *cp; cp++) { if (ascii_isdigit(*cp)) { @@ -3178,10 +3202,7 @@ ambw_end: char_u *cp; if (!(*varp)[0] || ((*varp)[0] == '0' && !(*varp)[1])) { - if (curbuf->b_p_vts_array) { - xfree(curbuf->b_p_vts_array); - curbuf->b_p_vts_array = NULL; - } + XFREE_CLEAR(curbuf->b_p_vts_array); } else { for (cp = *varp; *cp; cp++) { if (ascii_isdigit(*cp)) { @@ -3857,6 +3878,7 @@ static void set_option_sctx_idx(int opt_idx, int opt_flags, sctx_T script_ctx) { int both = (opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0; int indir = (int)options[opt_idx].indir; + nlua_set_sctx(&script_ctx); const LastSet last_set = { .script_ctx = { script_ctx.sc_sid, @@ -4132,7 +4154,7 @@ static char *set_bool_option(const int opt_idx, char_u *const varp, const int va } } - // Arabic requires a utf-8 encoding, inform the user if its not + // Arabic requires a utf-8 encoding, inform the user if it's not // set. if (STRCMP(p_enc, "utf-8") != 0) { static char *w_arabic = N_("W17: Arabic requires UTF-8, do ':set encoding=utf-8'"); @@ -4256,7 +4278,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); @@ -4321,6 +4343,12 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, char *errbuf, } else if (value > 10000) { errmsg = e_invarg; } + } else if (pp == &p_pyx) { + if (value == 0) { + value = 3; + } else if (value != 3) { + errmsg = e_invarg; + } } else if (pp == &p_re) { if (value < 0 || value > 2) { errmsg = e_invarg; @@ -4386,6 +4414,8 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, char *errbuf, } else if (pp == &curbuf->b_p_ts || pp == &p_ts) { if (value < 1) { errmsg = e_positive; + } else if (value > TABSTOP_MAX) { + errmsg = e_invarg; } } else if (pp == &curbuf->b_p_tw || pp == &p_tw) { if (value < 0) { @@ -4399,7 +4429,7 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, char *errbuf, // Don't change the value and return early if validation failed. if (errmsg != NULL) { - return (char *)errmsg; + return errmsg; } *pp = value; @@ -4495,10 +4525,6 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, char *errbuf, if (pum_drawn()) { pum_redraw(); } - } else if (pp == &p_pyx) { - if (p_pyx != 0 && p_pyx != 2 && p_pyx != 3) { - errmsg = e_invarg; - } } else if (pp == &p_ul || pp == &curbuf->b_p_ul) { // sync undo before 'undolevels' changes // use the old value, otherwise u_sync() may not work properly @@ -4527,7 +4553,7 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, char *errbuf, // Check the (new) bounds for Rows and Columns here. if (p_lines < min_rows() && full_screen) { if (errbuf != NULL) { - vim_snprintf((char *)errbuf, errbuflen, + vim_snprintf(errbuf, errbuflen, _("E593: Need at least %d lines"), min_rows()); errmsg = errbuf; } @@ -4535,7 +4561,7 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, char *errbuf, } if (p_columns < MIN_COLUMNS && full_screen) { if (errbuf != NULL) { - vim_snprintf((char *)errbuf, errbuflen, + vim_snprintf(errbuf, errbuflen, _("E594: Need at least %d columns"), MIN_COLUMNS); errmsg = errbuf; } @@ -4651,7 +4677,7 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, char *errbuf, } check_redraw(options[opt_idx].flags); - return (char *)errmsg; + return errmsg; } /// Trigger the OptionSet autocommand. @@ -5184,7 +5210,7 @@ static void showoptions(int all, int opt_flags) #define INC 20 #define GAP 3 - vimoption_T **items = xmalloc(sizeof(vimoption_T *) * PARAM_COUNT); + vimoption_T **items = xmalloc(sizeof(vimoption_T *) * OPTION_COUNT); // Highlight title if (opt_flags & OPT_GLOBAL) { @@ -5198,6 +5224,7 @@ static void showoptions(int all, int opt_flags) // Do the loop two times: // 1. display the short items // 2. display the long items (only strings and numbers) + // When "opt_flags" has OPT_ONECOLUMN do everything in run 2. for (run = 1; run <= 2 && !got_int; run++) { // collect the items in items[] item_count = 0; @@ -5208,7 +5235,7 @@ static void showoptions(int all, int opt_flags) } varp = NULL; - if (opt_flags != 0) { + if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) != 0) { if (p->indir != PV_NONE) { varp = get_varp_scope(p, opt_flags); } @@ -5217,8 +5244,10 @@ static void showoptions(int all, int opt_flags) } if (varp != NULL && (all == 1 || (all == 0 && !optval_default(p, varp)))) { - if (p->flags & P_BOOL) { - len = 1; // a toggle option fits always + if (opt_flags & OPT_ONECOLUMN) { + len = Columns; + } else if (p->flags & P_BOOL) { + len = 1; // a toggle option fits always } else { option_value2string(p, opt_flags); len = (int)STRLEN(p->fullname) + vim_strsize(NameBuff) + 1; @@ -5748,6 +5777,10 @@ void unset_global_local_option(char *name, void *from) set_chars_option((win_T *)from, &((win_T *)from)->w_p_fcs, true); redraw_later((win_T *)from, NOT_VALID); break; + case PV_VE: + clear_string_option(&((win_T *)from)->w_p_ve); + ((win_T *)from)->w_ve_flags = 0; + break; } } @@ -5814,6 +5847,8 @@ static char_u *get_varp_scope(vimoption_T *p, int opt_flags) return (char_u *)&(curwin->w_p_fcs); case PV_LCS: return (char_u *)&(curwin->w_p_lcs); + case PV_VE: + return (char_u *)&(curwin->w_p_ve); } return NULL; // "cannot happen" } @@ -5908,6 +5943,9 @@ static char_u *get_varp(vimoption_T *p) case PV_LCS: return *curwin->w_p_lcs != NUL ? (char_u *)&(curwin->w_p_lcs) : p->var; + case PV_VE: + return *curwin->w_p_ve != NUL + ? (char_u *)&curwin->w_p_ve : p->var; case PV_ARAB: return (char_u *)&(curwin->w_p_arab); @@ -6136,6 +6174,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. @@ -6148,6 +6187,8 @@ void copy_winopt(winopt_T *from, winopt_T *to) to->wo_list = from->wo_list; to->wo_nu = from->wo_nu; to->wo_rnu = from->wo_rnu; + to->wo_ve = vim_strsave(from->wo_ve); + to->wo_ve_flags = from->wo_ve_flags; to->wo_nuw = from->wo_nuw; to->wo_rl = from->wo_rl; to->wo_rlc = vim_strsave(from->wo_rlc); @@ -6192,6 +6233,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 } @@ -6224,6 +6268,7 @@ static void check_winopt(winopt_T *wop) check_string_option(&wop->wo_winhl); check_string_option(&wop->wo_fcs); check_string_option(&wop->wo_lcs); + check_string_option(&wop->wo_ve); } /// Free the allocated memory inside a winopt_T. @@ -6248,6 +6293,7 @@ void clear_winopt(winopt_T *wop) clear_string_option(&wop->wo_winhl); clear_string_option(&wop->wo_fcs); clear_string_option(&wop->wo_lcs); + clear_string_option(&wop->wo_ve); } void didset_window_options(win_T *wp) @@ -6262,11 +6308,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. @@ -6302,11 +6367,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; @@ -6338,80 +6404,129 @@ 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) { - tabstop_set(p_vsts, &buf->b_p_vsts_array); + (void)tabstop_set(p_vsts, &buf->b_p_vsts_array); } else { - buf->b_p_vsts_array = 0; + buf->b_p_vsts_array = NULL; } buf->b_p_vsts_nopaste = p_vsts_nopaste ? 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 @@ -6431,11 +6546,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; @@ -6448,17 +6566,20 @@ void buf_copy_options(buf_T *buf, int flags) if (dont_do_help) { buf->b_p_isk = save_p_isk; if (p_vts && p_vts != empty_option && !buf->b_p_vts_array) { - tabstop_set(p_vts, &buf->b_p_vts_array); + (void)tabstop_set(p_vts, &buf->b_p_vts_array); } else { buf->b_p_vts_array = NULL; } } else { buf->b_p_isk = vim_strsave(p_isk); + 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) { - tabstop_set(p_vts, &buf->b_p_vts_array); + (void)tabstop_set(p_vts, &buf->b_p_vts_array); } else { buf->b_p_vts_array = NULL; } @@ -6467,6 +6588,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); } } @@ -7107,10 +7229,7 @@ static void paste_option_changed(void) free_string_option(buf->b_p_vsts); } buf->b_p_vsts = empty_option; - if (buf->b_p_vsts_array) { - xfree(buf->b_p_vsts_array); - } - buf->b_p_vsts_array = 0; + XFREE_CLEAR(buf->b_p_vsts_array); } // set global options @@ -7147,13 +7266,11 @@ static void paste_option_changed(void) buf->b_p_vsts = buf->b_p_vsts_nopaste ? vim_strsave(buf->b_p_vsts_nopaste) : empty_option; - if (buf->b_p_vsts_array) { - xfree(buf->b_p_vsts_array); - } + xfree(buf->b_p_vsts_array); if (buf->b_p_vsts && buf->b_p_vsts != empty_option) { - tabstop_set(buf->b_p_vsts, &buf->b_p_vsts_array); + (void)tabstop_set(buf->b_p_vsts, &buf->b_p_vsts_array); } else { - buf->b_p_vsts_array = 0; + buf->b_p_vsts_array = NULL; } } @@ -7469,6 +7586,7 @@ int check_ff_value(char_u *p) // Set the integer values corresponding to the string setting of 'vartabstop'. // "array" will be set, caller must free it if needed. +// Return false for an error. bool tabstop_set(char_u *var, long **array) { long valcount = 1; @@ -7488,7 +7606,7 @@ bool tabstop_set(char_u *var, long **array) if (cp != end) { emsg(_(e_positive)); } else { - emsg(_(e_invarg)); + semsg(_(e_invarg2), cp); } return false; } @@ -7501,7 +7619,7 @@ bool tabstop_set(char_u *var, long **array) valcount++; continue; } - emsg(_(e_invarg)); + semsg(_(e_invarg2), var); return false; } @@ -7510,7 +7628,15 @@ bool tabstop_set(char_u *var, long **array) t = 1; for (cp = var; *cp != NUL;) { - (*array)[t++] = atoi((char *)cp); + int n = atoi((char *)cp); + + // Catch negative values, overflow and ridiculous big values. + if (n <= 0 || n > TABSTOP_MAX) { + semsg(_(e_invarg2), cp); + XFREE_CLEAR(*array); + return false; + } + (*array)[t++] = n; while (*cp != NUL && *cp != ',') { cp++; } @@ -7815,6 +7941,12 @@ unsigned int get_bkc_value(buf_T *buf) return buf->b_bkc_flags ? buf->b_bkc_flags : bkc_flags; } +/// Get the local or global value of the 'virtualedit' flags. +unsigned int get_ve_flags(void) +{ + return (curwin->w_ve_flags ? curwin->w_ve_flags : ve_flags) & ~(VE_NONE | VE_NONEU); +} + /// Get the local or global value of 'showbreak'. /// /// @param win If not NULL, the window to get the local option from; global @@ -7994,7 +8126,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) { @@ -8007,7 +8138,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)) { @@ -8023,6 +8153,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'; @@ -8033,6 +8165,7 @@ int win_signcol_configured(win_T *wp, int *is_fixed) } } + int needed_signcols = buf_signcols(wp->w_buffer, maximum); int ret = MAX(minimum, MIN(maximum, needed_signcols)); assert(ret <= SIGN_SHOW_MAX); return ret; diff --git a/src/nvim/option.h b/src/nvim/option.h index f7dbaafeec..9321dd5454 100644 --- a/src/nvim/option.h +++ b/src/nvim/option.h @@ -13,16 +13,16 @@ /// When OPT_GLOBAL and OPT_LOCAL are both missing, set both local and global /// values, get local value. typedef enum { - OPT_FREE = 1, ///< Free old value if it was allocated. - OPT_GLOBAL = 2, ///< Use global value. - OPT_LOCAL = 4, ///< Use local value. - OPT_MODELINE = 8, ///< Option in modeline. - OPT_WINONLY = 16, ///< Only set window-local options. - OPT_NOWIN = 32, ///< Don’t set window-local options. - OPT_ONECOLUMN = 64, ///< list options one per line - OPT_NO_REDRAW = 128, ///< ignore redraw flags on option - OPT_SKIPRTP = 256, ///< "skiprtp" in 'sessionoptions' - OPT_CLEAR = 512, ///< Clear local value of an option. + OPT_FREE = 0x01, ///< Free old value if it was allocated. + OPT_GLOBAL = 0x02, ///< Use global value. + OPT_LOCAL = 0x04, ///< Use local value. + OPT_MODELINE = 0x08, ///< Option in modeline. + OPT_WINONLY = 0x10, ///< Only set window-local options. + OPT_NOWIN = 0x20, ///< Don’t set window-local options. + OPT_ONECOLUMN = 0x40, ///< list options one per line + OPT_NO_REDRAW = 0x80, ///< ignore redraw flags on option + OPT_SKIPRTP = 0x100, ///< "skiprtp" in 'sessionoptions' + OPT_CLEAR = 0x200, ///< Clear local value of an option. } OptionFlags; #ifdef INCLUDE_GENERATED_DECLARATIONS diff --git a/src/nvim/option_defs.h b/src/nvim/option_defs.h index 09c3bf3800..d88cd6b9b9 100644 --- a/src/nvim/option_defs.h +++ b/src/nvim/option_defs.h @@ -705,12 +705,14 @@ EXTERN int p_vb; ///< 'visualbell' EXTERN char_u *p_ve; ///< 'virtualedit' EXTERN unsigned ve_flags; #ifdef IN_OPTION_C -static char *(p_ve_values[]) = { "block", "insert", "all", "onemore", NULL }; +static char *(p_ve_values[]) = { "block", "insert", "all", "onemore", "none", "NONE", NULL }; #endif -#define VE_BLOCK 5 // includes "all" -#define VE_INSERT 6 // includes "all" -#define VE_ALL 4 -#define VE_ONEMORE 8 +#define VE_BLOCK 5U // includes "all" +#define VE_INSERT 6U // includes "all" +#define VE_ALL 4U +#define VE_ONEMORE 8U +#define VE_NONE 16U // "none" +#define VE_NONEU 32U // "NONE" EXTERN long p_verbose; // 'verbose' #ifdef IN_OPTION_C char_u *p_vfile = (char_u *)""; // used before options are initialized @@ -869,6 +871,7 @@ enum { WV_LBR, WV_NU, WV_RNU, + WV_VE, WV_NUW, WV_PVW, WV_RL, @@ -900,6 +903,8 @@ enum { #define SB_MAX 100000 // Maximum 'scrollback' value. +#define TABSTOP_MAX 9999 + /// Stores an identifier of a script or channel that last set an option. typedef struct { sctx_T script_ctx; /// script context where the option was last set diff --git a/src/nvim/options.lua b/src/nvim/options.lua index 28b4eb9fe2..e665ffd346 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -411,7 +411,7 @@ return { }, { full_name='compatible', abbreviation='cp', - short_desc=N_("No description"), + short_desc=N_("No description"), type='bool', scope={'global'}, redraw={'all_windows'}, varname='p_force_off', @@ -665,14 +665,14 @@ return { }, { full_name='edcompatible', abbreviation='ed', - short_desc=N_("No description"), + short_desc=N_("No description"), type='bool', scope={'global'}, varname='p_force_off', defaults={if_true=false} }, { full_name='emoji', abbreviation='emo', - short_desc=N_("No description"), + short_desc=N_("No description"), type='bool', scope={'global'}, redraw={'all_windows', 'ui_option'}, varname='p_emoji', @@ -1184,7 +1184,7 @@ return { }, { full_name='inccommand', abbreviation='icm', - short_desc=N_("Live preview of substitution"), + short_desc=N_("Live preview of substitution"), type='string', scope={'global'}, redraw={'all_windows'}, varname='p_icm', @@ -1846,7 +1846,7 @@ return { type='number', scope={'global'}, secure=true, varname='p_pyx', - defaults={if_true=0} + defaults={if_true=3} }, { full_name='quickfixtextfunc', abbreviation='qftf', @@ -2499,7 +2499,7 @@ return { }, { full_name='termencoding', abbreviation='tenc', - short_desc=N_("Terminal encodig"), + short_desc=N_("Terminal encoding"), type='string', scope={'global'}, defaults={if_true=""} }, @@ -2622,7 +2622,7 @@ return { }, { full_name='ttyfast', abbreviation='tf', - short_desc=N_("No description"), + short_desc=N_("No description"), type='bool', scope={'global'}, no_mkrc=true, varname='p_force_on', @@ -2736,7 +2736,7 @@ return { { full_name='virtualedit', abbreviation='ve', short_desc=N_("when to use virtual editing"), - type='string', list='onecomma', scope={'global'}, + type='string', list='onecomma', scope={'global', 'window'}, deny_duplicates=true, redraw={'curswant'}, varname='p_ve', diff --git a/src/nvim/os/env.c b/src/nvim/os/env.c index e9f44d2775..e9868d6b61 100644 --- a/src/nvim/os/env.c +++ b/src/nvim/os/env.c @@ -1111,10 +1111,9 @@ size_t home_replace(const buf_T *const buf, const char_u *src, char_u *const dst *dst_p++ = '~'; } - // If it's just the home directory, add "/". - if (!vim_ispathsep(src[0]) && --dstlen > 0) { - *dst_p++ = '/'; - } + // Do not add directory separator into dst, because dst is + // expected to just return the directory name without the + // directory separator '/'. break; } if (p == homedir_env_mod) { diff --git a/src/nvim/os/fs.c b/src/nvim/os/fs.c index 24c7678633..daf974ee74 100644 --- a/src/nvim/os/fs.c +++ b/src/nvim/os/fs.c @@ -40,8 +40,10 @@ bool did_try_to_free = false; \ uv_call_start: {} \ uv_fs_t req; \ + fs_loop_lock(); \ ret = func(&fs_loop, &req, __VA_ARGS__); \ uv_fs_req_cleanup(&req); \ + fs_loop_unlock(); \ if (ret == UV_ENOMEM && !did_try_to_free) { \ try_to_free_memory(); \ did_try_to_free = true; \ @@ -52,12 +54,27 @@ uv_call_start: {} \ // Many fs functions from libuv return that value on success. static const int kLibuvSuccess = 0; static uv_loop_t fs_loop; +static uv_mutex_t fs_loop_mutex; // Initialize the fs module void fs_init(void) { uv_loop_init(&fs_loop); + uv_mutex_init_recursive(&fs_loop_mutex); +} + +/// TODO(bfredl): some of these operations should +/// be possible to do the private libuv loop of the +/// thread, instead of contending the global fs loop +void fs_loop_lock(void) +{ + uv_mutex_lock(&fs_loop_mutex); +} + +void fs_loop_unlock(void) +{ + uv_mutex_unlock(&fs_loop_mutex); } @@ -98,9 +115,12 @@ bool os_isrealdir(const char *name) FUNC_ATTR_NONNULL_ALL { uv_fs_t request; + fs_loop_lock(); if (uv_fs_lstat(&fs_loop, &request, name, NULL) != kLibuvSuccess) { + fs_loop_unlock(); return false; } + fs_loop_unlock(); if (S_ISLNK(request.statbuf.st_mode)) { return false; } else { @@ -738,7 +758,9 @@ static int os_stat(const char *name, uv_stat_t *statbuf) return UV_EINVAL; } uv_fs_t request; + fs_loop_lock(); int result = uv_fs_stat(&fs_loop, &request, name, NULL); + fs_loop_unlock(); if (result == kLibuvSuccess) { *statbuf = request.statbuf; } @@ -935,9 +957,11 @@ int os_mkdtemp(const char *template, char *path) FUNC_ATTR_NONNULL_ALL { uv_fs_t request; + fs_loop_lock(); int result = uv_fs_mkdtemp(&fs_loop, &request, template, NULL); + fs_loop_unlock(); if (result == kLibuvSuccess) { - STRNCPY(path, request.path, TEMP_FILE_PATH_MAXLEN); + xstrlcpy(path, request.path, TEMP_FILE_PATH_MAXLEN); } uv_fs_req_cleanup(&request); return result; @@ -962,7 +986,9 @@ int os_rmdir(const char *path) bool os_scandir(Directory *dir, const char *path) FUNC_ATTR_NONNULL_ALL { + fs_loop_lock(); int r = uv_fs_scandir(&fs_loop, &dir->request, path, 0, NULL); + fs_loop_unlock(); if (r < 0) { os_closedir(dir); } @@ -1023,7 +1049,9 @@ bool os_fileinfo_link(const char *path, FileInfo *file_info) return false; } uv_fs_t request; + fs_loop_lock(); bool ok = uv_fs_lstat(&fs_loop, &request, path, NULL) == kLibuvSuccess; + fs_loop_unlock(); if (ok) { file_info->stat = request.statbuf; } @@ -1041,6 +1069,7 @@ bool os_fileinfo_fd(int file_descriptor, FileInfo *file_info) { uv_fs_t request; memset(file_info, 0, sizeof(*file_info)); + fs_loop_lock(); bool ok = uv_fs_fstat(&fs_loop, &request, file_descriptor, @@ -1049,6 +1078,7 @@ bool os_fileinfo_fd(int file_descriptor, FileInfo *file_info) file_info->stat = request.statbuf; } uv_fs_req_cleanup(&request); + fs_loop_unlock(); return ok; } @@ -1165,6 +1195,7 @@ char *os_realpath(const char *name, char *buf) FUNC_ATTR_NONNULL_ARG(1) { uv_fs_t request; + fs_loop_lock(); int result = uv_fs_realpath(&fs_loop, &request, name, NULL); if (result == kLibuvSuccess) { if (buf == NULL) { @@ -1173,6 +1204,7 @@ char *os_realpath(const char *name, char *buf) xstrlcpy(buf, request.ptr, MAXPATHL + 1); } uv_fs_req_cleanup(&request); + fs_loop_unlock(); return result == kLibuvSuccess ? buf : NULL; } diff --git a/src/nvim/os/input.c b/src/nvim/os/input.c index 3790eba212..54cfaee80a 100644 --- a/src/nvim/os/input.c +++ b/src/nvim/os/input.c @@ -234,9 +234,9 @@ size_t input_enqueue(String keys) while (rbuffer_space(input_buffer) >= 19 && ptr < end) { // A "<x>" form occupies at least 1 characters, and produces up // to 19 characters (1 + 5 * 3 for the char and 3 for a modifier). - // In the case of K_SPECIAL(0x80) or CSI(0x9B), 3 bytes are escaped and - // needed, but since the keys are UTF-8, so the first byte cannot be - // K_SPECIAL(0x80) or CSI(0x9B). + // In the case of K_SPECIAL(0x80), 3 bytes are escaped and needed, + // but since the keys are UTF-8, so the first byte cannot be + // K_SPECIAL(0x80). uint8_t buf[19] = { 0 }; unsigned int new_size = trans_special((const uint8_t **)&ptr, (size_t)(end - ptr), buf, true, @@ -263,12 +263,8 @@ size_t input_enqueue(String keys) continue; } - // copy the character, escaping CSI and K_SPECIAL - if ((uint8_t)*ptr == CSI) { - rbuffer_write(input_buffer, (char *)&(uint8_t){ K_SPECIAL }, 1); - rbuffer_write(input_buffer, (char *)&(uint8_t){ KS_EXTRA }, 1); - rbuffer_write(input_buffer, (char *)&(uint8_t){ KE_CSI }, 1); - } else if ((uint8_t)*ptr == K_SPECIAL) { + // copy the character, escaping K_SPECIAL + if ((uint8_t)(*ptr) == K_SPECIAL) { rbuffer_write(input_buffer, (char *)&(uint8_t){ K_SPECIAL }, 1); rbuffer_write(input_buffer, (char *)&(uint8_t){ KS_SPECIAL }, 1); rbuffer_write(input_buffer, (char *)&(uint8_t){ KE_FILLER }, 1); diff --git a/src/nvim/path.c b/src/nvim/path.c index 674d67e21a..d3aa5e5bf2 100644 --- a/src/nvim/path.c +++ b/src/nvim/path.c @@ -269,16 +269,17 @@ int vim_ispathlistsep(int c) #endif } -/* - * Shorten the path of a file from "~/foo/../.bar/fname" to "~/f/../.b/fname" - * It's done in-place. - */ -char_u *shorten_dir(char_u *str) +/// Shorten the path of a file from "~/foo/../.bar/fname" to "~/f/../.b/fname" +/// "trim_len" specifies how many characters to keep for each directory. +/// Must be 1 or more. +/// It's done in-place. +void shorten_dir_len(char_u *str, int trim_len) { char_u *tail = path_tail(str); char_u *d = str; bool skip = false; - for (char_u *s = str;; ++s) { + int dirchunk_len = 0; + for (char_u *s = str;; s++) { if (s >= tail) { // copy the whole tail *d++ = *s; if (*s == NUL) { @@ -287,10 +288,16 @@ char_u *shorten_dir(char_u *str) } else if (vim_ispathsep(*s)) { // copy '/' and next char *d++ = *s; skip = false; + dirchunk_len = 0; } else if (!skip) { *d++ = *s; // copy next char if (*s != '~' && *s != '.') { // and leading "~" and "." - skip = true; + dirchunk_len++; // only count word chars for the size + // keep copying chars until we have our preferred length (or + // until the above if/else branches move us along) + if (dirchunk_len >= trim_len) { + skip = true; + } } int l = utfc_ptr2len(s); while (--l > 0) { @@ -298,7 +305,13 @@ char_u *shorten_dir(char_u *str) } } } - return str; +} + +/// Shorten the path of a file from "~/foo/../.bar/fname" to "~/f/../.b/fname" +/// It's done in-place. +void shorten_dir(char_u *str) +{ + shorten_dir_len(str, 1); } /* @@ -629,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; @@ -1508,7 +1520,7 @@ void simplify_filename(char_u *filename) p = filename; #ifdef BACKSLASH_IN_FILENAME - if (p[1] == ':') { // skip "x:" + if (p[0] != NUL && p[1] == ':') { // skip "x:" p += 2; } #endif @@ -1682,6 +1694,10 @@ char_u *find_file_name_in_path(char_u *ptr, size_t len, int options, long count, char_u *file_name; char_u *tofree = NULL; + if (len == 0) { + return NULL; + } + if ((options & FNAME_INCL) && *curbuf->b_p_inex != NUL) { tofree = (char_u *)eval_includeexpr((char *)ptr, len); if (tofree != NULL) { @@ -1743,14 +1759,32 @@ int path_is_url(const char *p) return 0; } -/// Check if "fname" starts with "name://". Return URL_SLASH if it does. +/// Check if "fname" starts with "name://" or "name:\\". /// /// @param fname is the filename to test -/// @return URL_BACKSLASH for "name:\\", zero otherwise. +/// @return URL_SLASH for "name://", URL_BACKSLASH for "name:\\", zero otherwise. int path_with_url(const char *fname) { const char *p; - for (p = fname; isalpha(*p); p++) {} + + // We accept alphabetic characters and a dash in scheme part. + // RFC 3986 allows for more, but it increases the risk of matching + // non-URL text. + + // first character must be alpha + if (!isalpha(*fname)) { + return 0; + } + + // check body: alpha or dash + for (p = fname; (isalpha(*p) || (*p == '-')); p++) {} + + // check last char is not a dash + if (p[-1] == '-') { + return 0; + } + + // "://" or ":\\" must follow return path_is_url(p); } @@ -2367,9 +2401,11 @@ static int path_to_absolute(const char_u *fname, char_u *buf, size_t len, int fo int path_is_absolute(const char_u *fname) { #ifdef WIN32 + if (*fname == NUL) { + return false; + } // A name like "d:/foo" and "//server/share" is absolute - return ((isalpha(fname[0]) && fname[1] == ':' - && vim_ispathsep_nocolon(fname[2])) + return ((isalpha(fname[0]) && fname[1] == ':' && vim_ispathsep_nocolon(fname[2])) || (vim_ispathsep_nocolon(fname[0]) && fname[0] == fname[1])); #else // UNIX: This just checks if the file name starts with '/' or '~'. diff --git a/src/nvim/plines.c b/src/nvim/plines.c index a061f76f34..a572f747df 100644 --- a/src/nvim/plines.c +++ b/src/nvim/plines.c @@ -83,7 +83,7 @@ int plines_win_nofill(win_T *wp, linenr_T lnum, bool winheight) return 1; } - // A folded lines is handled just like an empty line. + // Folded lines are handled just like an empty line. if (lineFolded(wp, lnum)) { return 1; } diff --git a/src/nvim/po/ja.euc-jp.po b/src/nvim/po/ja.euc-jp.po index 5dda7c59f5..9633bec9f2 100644 --- a/src/nvim/po/ja.euc-jp.po +++ b/src/nvim/po/ja.euc-jp.po @@ -1644,15 +1644,6 @@ msgstr " [w]" msgid " written" msgstr " " -msgid "E205: Patchmode: can't save original file" -msgstr "E205: patchmode: ܥե¸Ǥޤ" - -msgid "E206: patchmode: can't touch empty original file" -msgstr "E206: patchmode: θܥեtouchǤޤ" - -msgid "E207: Can't delete backup file" -msgstr "E207: Хååץեäޤ" - msgid "" "\n" "WARNING: Original file may be lost or damaged\n" diff --git a/src/nvim/po/ja.po b/src/nvim/po/ja.po index a169bd3589..c363c00fa6 100644 --- a/src/nvim/po/ja.po +++ b/src/nvim/po/ja.po @@ -1644,15 +1644,6 @@ msgstr " [w]" msgid " written" msgstr " 書込み" -msgid "E205: Patchmode: can't save original file" -msgstr "E205: patchmode: 原本ファイルを保存できません" - -msgid "E206: patchmode: can't touch empty original file" -msgstr "E206: patchmode: 空の原本ファイルをtouchできません" - -msgid "E207: Can't delete backup file" -msgstr "E207: バックアップファイルを消せません" - msgid "" "\n" "WARNING: Original file may be lost or damaged\n" diff --git a/src/nvim/popupmnu.c b/src/nvim/popupmnu.c index da2ada791f..d7726409b5 100644 --- a/src/nvim/popupmnu.c +++ b/src/nvim/popupmnu.c @@ -291,7 +291,7 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, i } else { assert(Columns - pum_col - pum_scrollbar >= INT_MIN && Columns - pum_col - pum_scrollbar <= INT_MAX); - pum_width = (int)(Columns - pum_col - pum_scrollbar); + pum_width = Columns - pum_col - pum_scrollbar; } if ((pum_width > max_width + pum_kind_width + pum_extra_width + 1) @@ -352,12 +352,12 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, i // not enough room, will use what we have if (pum_rl) { assert(Columns - 1 >= INT_MIN); - pum_col = (int)(Columns - 1); + pum_col = Columns - 1; } else { pum_col = 0; } assert(Columns - 1 >= INT_MIN); - pum_width = (int)(Columns - 1); + pum_width = Columns - 1; } else { if (max_width > p_pw) { // truncate @@ -369,7 +369,7 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, i } else { assert(Columns - max_width >= INT_MIN && Columns - max_width <= INT_MAX); - pum_col = (int)(Columns - max_width); + pum_col = Columns - max_width; } pum_width = max_width - pum_scrollbar; } 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 32d0ebe8eb..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; } @@ -3947,7 +3994,7 @@ static int qf_buf_add_line(qf_list_T *qfl, buf_T *buf, linenr_T lnum, const qfli int len; buf_T *errbuf; - // If the 'quickfixtextfunc' function returned an non-empty custom string + // If the 'quickfixtextfunc' function returned a non-empty custom string // for this entry, then use it. if (qftf_str != NULL && *qftf_str != NUL) { STRLCPY(IObuff, qftf_str, IOSIZE); @@ -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(®match, 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, ®match, &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/regexp.c b/src/nvim/regexp.c index 45e580dbee..6a6c915094 100644 --- a/src/nvim/regexp.c +++ b/src/nvim/regexp.c @@ -2901,18 +2901,14 @@ static int peekchr(void) { int c = regparse[1]; - if (c == NUL) - curchr = '\\'; /* trailing '\' */ - else if ( - c <= '~' && META_flags[c] - ) { - /* - * META contains everything that may be magic sometimes, - * except ^ and $ ("\^" and "\$" are only magic after - * "\V"). We now fetch the next character and toggle its - * magicness. Therefore, \ is so meta-magic that it is - * not in META. - */ + if (c == NUL) { + curchr = '\\'; // trailing '\' + } else if (c <= '~' && META_flags[c]) { + // META contains everything that may be magic sometimes, + // except ^ and $ ("\^" and "\$" are only magic after + // "\V"). We now fetch the next character and toggle its + // magicness. Therefore, \ is so meta-magic that it is + // not in META. curchr = -1; prev_at_start = at_start; at_start = false; // be able to say "/\*ptr" @@ -3232,7 +3228,7 @@ typedef struct { // The current match-position is remembered with these variables: linenr_T lnum; ///< line number, relative to first line char_u *line; ///< start of current line - char_u *input; ///< current input, points into "regline" + char_u *input; ///< current input, points into "line" int need_clear_subexpr; ///< subexpressions still need to be cleared int need_clear_zsubexpr; ///< extmatch subexpressions still need to be @@ -6538,11 +6534,16 @@ char_u *regtilde(char_u *source, int magic) } } - xfree(reg_prev_sub); - if (newsub != source) /* newsub was allocated, just keep it */ - reg_prev_sub = newsub; - else /* no ~ found, need to save newsub */ - reg_prev_sub = vim_strsave(newsub); + // Only change reg_prev_sub when not previewing. + if (!(State & CMDPREVIEW)) { + xfree(reg_prev_sub); + if (newsub != source) { // newsub was allocated, just keep it + reg_prev_sub = newsub; + } else { // no ~ found, need to save newsub + reg_prev_sub = vim_strsave(newsub); + } + } + return newsub; } diff --git a/src/nvim/regexp_nfa.c b/src/nvim/regexp_nfa.c index 41c927eaa6..133858f113 100644 --- a/src/nvim/regexp_nfa.c +++ b/src/nvim/regexp_nfa.c @@ -2013,7 +2013,7 @@ static int nfa_regpiece(void) // will emit NFA_STAR. // Bail out if we can use the other engine, but only, when the // pattern does not need the NFA engine like (e.g. [[:upper:]]\{2,\} - // does not work with with characters > 8 bit with the BT engine) + // does not work with characters > 8 bit with the BT engine) if ((nfa_re_flags & RE_AUTO) && (maxval > 500 || maxval > minval + 200) && (maxval != MAX_LIMIT && minval < 200) @@ -4367,7 +4367,7 @@ static regsubs_T *addstate_here( // First add the state(s) at the end, so that we know how many there are. // Pass the listidx as offset (avoids adding another argument to - // addstate(). + // addstate()). regsubs_T *r = addstate(l, state, subs, pim, -listidx - ADDSTATE_HERE_OFFSET); if (r == NULL) { return NULL; @@ -6071,8 +6071,15 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, case NFA_MARK_GT: case NFA_MARK_LT: { + size_t col = rex.input - rex.line; pos_T *pos = getmark_buf(rex.reg_buf, t->state->val, false); + // Line may have been freed, get it again. + if (REG_MULTI) { + rex.line = reg_getline(rex.lnum); + rex.input = rex.line + col; + } + // Compare the mark position to the match position, if the mark // exists and mark is set in reg_buf. if (pos != NULL && pos->lnum > 0) { diff --git a/src/nvim/runtime.c b/src/nvim/runtime.c index 1c04cb16b3..1102096b32 100644 --- a/src/nvim/runtime.c +++ b/src/nvim/runtime.c @@ -24,6 +24,13 @@ static bool runtime_search_path_valid = false; static int *runtime_search_path_ref = NULL; static RuntimeSearchPath runtime_search_path; +static RuntimeSearchPath runtime_search_path_thread; +static uv_mutex_t runtime_search_path_mutex; + +void runtime_init(void) +{ + uv_mutex_init(&runtime_search_path_mutex); +} /// ":runtime [what] {name}" void ex_runtime(exarg_T *eap) @@ -146,7 +153,7 @@ int do_in_path(char_u *path, char *name, int flags, DoInRuntimepathCB callback, if (flags & DIP_ERR) { semsg(_(e_dirnotf), basepath, name); - } else if (p_verbose > 0) { + } else if (p_verbose > 1) { verbose_enter(); smsg(_("not found in '%s': \"%s\""), basepath, name); verbose_leave(); @@ -172,6 +179,17 @@ RuntimeSearchPath runtime_search_path_get_cached(int *ref) return runtime_search_path; } +RuntimeSearchPath copy_runtime_search_path(const RuntimeSearchPath src) +{ + RuntimeSearchPath dst = KV_INITIAL_VALUE; + for (size_t j = 0; j < kv_size(src); j++) { + SearchPathItem src_item = kv_A(src, j); + kv_push(dst, ((SearchPathItem){ xstrdup(src_item.path), src_item.after, src_item.has_lua })); + } + + return dst; +} + void runtime_search_path_unref(RuntimeSearchPath path, int *ref) FUNC_ATTR_NONNULL_ALL { @@ -268,7 +286,7 @@ int do_in_cached_path(char_u *name, int flags, DoInRuntimepathCB callback, void if (!did_one && name != NULL) { if (flags & DIP_ERR) { semsg(_(e_dirnotf), "runtime path", name); - } else if (p_verbose > 0) { + } else if (p_verbose > 1) { verbose_enter(); smsg(_("not found in runtime path: \"%s\""), name); verbose_leave(); @@ -302,15 +320,35 @@ ArrayOf(String) runtime_get_named(bool lua, Array pat, bool all) { int ref; RuntimeSearchPath path = runtime_search_path_get_cached(&ref); - ArrayOf(String) rv = ARRAY_DICT_INIT; static char buf[MAXPATHL]; + ArrayOf(String) rv = runtime_get_named_common(lua, pat, all, path, buf, sizeof buf); + + runtime_search_path_unref(path, &ref); + return rv; +} + +ArrayOf(String) runtime_get_named_thread(bool lua, Array pat, bool all) +{ + // TODO(bfredl): avoid contention between multiple worker threads? + uv_mutex_lock(&runtime_search_path_mutex); + static char buf[MAXPATHL]; + ArrayOf(String) rv = runtime_get_named_common(lua, pat, all, runtime_search_path_thread, + buf, sizeof buf); + uv_mutex_unlock(&runtime_search_path_mutex); + return rv; +} + +ArrayOf(String) runtime_get_named_common(bool lua, Array pat, bool all, + RuntimeSearchPath path, char *buf, size_t buf_len) +{ + ArrayOf(String) rv = ARRAY_DICT_INIT; for (size_t i = 0; i < kv_size(path); i++) { SearchPathItem *item = &kv_A(path, i); if (lua) { if (item->has_lua == kNone) { - size_t size = (size_t)snprintf(buf, sizeof buf, "%s/lua/", item->path); - item->has_lua = (size < sizeof buf && os_isdir((char_u *)buf)) ? kTrue : kFalse; + size_t size = (size_t)snprintf(buf, buf_len, "%s/lua/", item->path); + item->has_lua = (size < buf_len && os_isdir((char_u *)buf)); } if (item->has_lua == kFalse) { continue; @@ -320,9 +358,9 @@ ArrayOf(String) runtime_get_named(bool lua, Array pat, bool all) for (size_t j = 0; j < pat.size; j++) { Object pat_item = pat.items[j]; if (pat_item.type == kObjectTypeString) { - size_t size = (size_t)snprintf(buf, sizeof buf, "%s/%s", + size_t size = (size_t)snprintf(buf, buf_len, "%s/%s", item->path, pat_item.data.string.data); - if (size < sizeof buf) { + if (size < buf_len) { if (os_file_is_readable(buf)) { ADD(rv, STRING_OBJ(cstr_to_string(buf))); if (!all) { @@ -333,9 +371,7 @@ ArrayOf(String) runtime_get_named(bool lua, Array pat, bool all) } } } - done: - runtime_search_path_unref(path, &ref); return rv; } @@ -569,6 +605,10 @@ void runtime_search_path_validate(void) runtime_search_path = runtime_search_path_build(); runtime_search_path_valid = true; runtime_search_path_ref = NULL; // initially unowned + uv_mutex_lock(&runtime_search_path_mutex); + runtime_search_path_free(runtime_search_path_thread); + runtime_search_path_thread = copy_runtime_search_path(runtime_search_path); + uv_mutex_unlock(&runtime_search_path_mutex); } } diff --git a/src/nvim/screen.c b/src/nvim/screen.c index e62e3ca7bc..7fafe3dd6e 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -326,6 +326,18 @@ void redraw_buf_status_later(buf_T *buf) } } +void redraw_win_signcol(win_T *wp) +{ + // 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. + int scwidth = wp->w_scwidth; + wp->w_scwidth = win_signcol_count(wp); + if (wp->w_scwidth != scwidth) { + changed_line_abv_curs_win(wp); + } +} + /// Redraw the parts of the screen that is marked for redraw. /// /// Most code shouldn't call this directly, rather use redraw_later() and @@ -790,12 +802,6 @@ static void win_update(win_T *wp, Providers *providers) linenr_T mod_bot = 0; int save_got_int; - - // 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); - type = wp->w_redr_type; if (type >= NOT_VALID) { @@ -817,6 +823,8 @@ static void win_update(win_T *wp, Providers *providers) return; } + redraw_win_signcol(wp); + init_search_hl(wp); /* Force redraw when width of 'number' or 'relativenumber' column @@ -1215,19 +1223,40 @@ static void win_update(win_T *wp, Providers *providers) */ if (VIsual_mode == Ctrl_V) { colnr_T fromc, toc; - int save_ve_flags = ve_flags; + unsigned int save_ve_flags = curwin->w_ve_flags; if (curwin->w_p_lbr) { - ve_flags = VE_ALL; + curwin->w_ve_flags = VE_ALL; } getvcols(wp, &VIsual, &curwin->w_cursor, &fromc, &toc); - ve_flags = save_ve_flags; toc++; + curwin->w_ve_flags = save_ve_flags; // Highlight to the end of the line, unless 'virtualedit' has // "block". - if (curwin->w_curswant == MAXCOL && !(ve_flags & VE_BLOCK)) { - toc = MAXCOL; + if (curwin->w_curswant == MAXCOL) { + if (get_ve_flags() & VE_BLOCK) { + pos_T pos; + int cursor_above = curwin->w_cursor.lnum < VIsual.lnum; + + // Need to find the longest line. + toc = 0; + pos.coladd = 0; + for (pos.lnum = curwin->w_cursor.lnum; + cursor_above ? pos.lnum <= VIsual.lnum : pos.lnum >= VIsual.lnum; + pos.lnum += cursor_above ? 1 : -1) { + colnr_T t; + + pos.col = STRLEN(ml_get_buf(wp->w_buffer, pos.lnum, false)); + getvvcol(wp, &pos, NULL, NULL, &t); + if (toc < t) { + toc = t; + } + } + toc++; + } else { + toc = MAXCOL; + } } if (fromc != wp->w_old_cursor_fcol @@ -1825,7 +1854,7 @@ static void win_draw_end(win_T *wp, int c1, int c2, bool draw_margin, int row, i win_hl_attr(wp, HLF_FC)); } // draw the sign column - int count = win_signcol_count(wp); + int count = wp->w_scwidth; if (count > 0) { n = win_fill_end(wp, ' ', ' ', n, win_signcol_width(wp) * count, row, endrow, win_hl_attr(wp, HLF_SC)); @@ -1960,7 +1989,7 @@ static size_t fill_foldcolumn(char_u *p, win_T *wp, foldinfo_T foldinfo, linenr_ level = foldinfo.fi_level; // If the column is too narrow, we start at the lowest level that - // fits and use numbers to indicated the depth. + // fits and use numbers to indicate the depth. first_level = level - fdc - closed + 1; if (first_level < 1) { first_level = 1; @@ -2771,10 +2800,9 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool noc draw_state = WL_SIGN; /* Show the sign column when there are any signs in this * buffer or when using Netbeans. */ - int count = win_signcol_count(wp); - if (count > 0) { + if (wp->w_scwidth > 0) { get_sign_display_info(false, wp, lnum, sattrs, row, - startrow, filler_lines, filler_todo, count, + startrow, filler_lines, filler_todo, &c_extra, &c_final, extra, sizeof(extra), &p_extra, &n_extra, &char_attr, &draw_state, &sign_idx); @@ -2792,10 +2820,9 @@ 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) { - int count = win_signcol_count(wp); + && num_signs > 0 && sign_get_attr(SIGN_TEXT, sattrs, 0, 1)) { get_sign_display_info(true, wp, lnum, sattrs, row, - startrow, filler_lines, filler_todo, count, + startrow, filler_lines, filler_todo, &c_extra, &c_final, extra, sizeof(extra), &p_extra, &n_extra, &char_attr, &draw_state, &sign_idx); @@ -3720,10 +3747,13 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool noc tab_len += n_extra - tab_len; } - // if n_extra > 0, it gives the number of chars + // If n_extra > 0, it gives the number of chars // to use for a tab, else we need to calculate the width - // for a tab + // for a tab. int len = (tab_len * utf_char2len(wp->w_p_lcs_chars.tab2)); + if (wp->w_p_lcs_chars.tab3) { + len += utf_char2len(wp->w_p_lcs_chars.tab3); + } if (n_extra > 0) { len += n_extra - tab_len; } @@ -3740,13 +3770,11 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool noc } int lcs = wp->w_p_lcs_chars.tab2; - // if tab3 is given, need to change the char - // for tab + // 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; } - utf_char2bytes(lcs, p); - p += utf_char2len(lcs); + p += utf_char2bytes(lcs, p); n_extra += utf_char2len(lcs) - (saved_nextra > 0 ? 1 : 0); } p_extra = p_extra_free; @@ -3771,7 +3799,7 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool noc // Make sure, the highlighting for the tab char will be // correctly set further below (effectively reverts the - // FIX_FOR_BOGSUCOLS macro. + // FIX_FOR_BOGSUCOLS macro). if (n_extra == tab_len + vc_saved && wp->w_p_list && wp->w_p_lcs_chars.tab1) { tab_len += vc_saved; @@ -4130,7 +4158,7 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool noc if (((wp->w_p_cuc && (int)wp->w_virtcol >= VCOL_HLC - eol_hl_off && (int)wp->w_virtcol < - grid->Columns * (row - startrow + 1) + v + (long)grid->Columns * (row - startrow + 1) + v && lnum != wp->w_cursor.lnum) || draw_color_col || line_attr_lowprio || line_attr || diff_hlf != (hlf_T)0 || has_virttext)) { @@ -4232,6 +4260,7 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool noc // Show "extends" character from 'listchars' if beyond the line end and // 'list' is set. if (wp->w_p_lcs_chars.ext != NUL + && draw_state == WL_LINE && wp->w_p_list && !wp->w_p_wrap && filler_todo <= 0 @@ -4294,7 +4323,7 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool noc // Store the character. // if (wp->w_p_rl && utf_char2cells(mb_c) > 1) { - // A double-wide character is: put first halve in left cell. + // A double-wide character is: put first half in left cell. off--; col--; } @@ -4427,7 +4456,8 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool noc */ if ((wp->w_p_rl ? (col < 0) : (col >= grid->Columns)) && foldinfo.fi_lines == 0 - && (*ptr != NUL + && (draw_state != WL_LINE + || *ptr != NUL || filler_todo > 0 || (wp->w_p_list && wp->w_p_lcs_chars.eol != NUL && p_extra != at_end_str) @@ -4637,8 +4667,8 @@ void screen_adjust_grid(ScreenGrid **grid, int *row_off, int *col_off) static bool use_cursor_line_sign(win_T *wp, linenr_T lnum) { return wp->w_p_cul - && lnum == wp->w_cursor.lnum - && (wp->w_p_culopt_flags & CULOPT_NBR); + && lnum == wp->w_cursor.lnum + && (wp->w_p_culopt_flags & CULOPT_NBR); } // Get information needed to display the sign in line 'lnum' in window 'wp'. @@ -4650,10 +4680,11 @@ static bool use_cursor_line_sign(win_T *wp, linenr_T lnum) // @param[in, out] sign_idxp Index of the displayed sign static void get_sign_display_info(bool nrcol, win_T *wp, linenr_T lnum, sign_attrs_T sattrs[], int row, int startrow, int filler_lines, int filler_todo, - int count, int *c_extrap, int *c_finalp, char_u *extra, + int *c_extrap, int *c_finalp, char_u *extra, size_t extra_size, char_u **pp_extra, int *n_extrap, int *char_attrp, int *draw_statep, int *sign_idxp) { + int count = wp->w_scwidth; // Draw cells with the sign value or blank. *c_extrap = ' '; *c_finalp = NUL; @@ -4834,9 +4865,9 @@ static void grid_put_linebuf(ScreenGrid *grid, int row, int coloff, int endcol, end_dirty = col + char_cells; // When writing a single-width character over a double-width // character and at the end of the redrawn text, need to clear out - // the right halve of the old character. - // Also required when writing the right halve of a double-width - // char over the left halve of an existing one + // the right half of the old character. + // Also required when writing the right half of a double-width + // char over the left half of an existing one if (col + char_cells == endcol && ((char_cells == 1 && grid_off2cells(grid, off_to, max_off_to) > 1) @@ -5887,8 +5918,8 @@ void grid_puts_len(ScreenGrid *grid, char_u *text, int textlen, int row, int col } off = grid->line_offset[row] + col; - /* When drawing over the right halve of a double-wide char clear out the - * left halve. Only needed in a terminal. */ + // When drawing over the right half of a double-wide char clear out the + // left half. Only needed in a terminal. if (grid != &default_grid && col == 0 && grid_invalid_row(grid, row)) { // redraw the previous cell, make it empty put_dirty_first = -1; @@ -5950,9 +5981,9 @@ void grid_puts_len(ScreenGrid *grid, char_u *text, int textlen, int row, int col if (need_redraw) { // When at the end of the text and overwriting a two-cell // character with a one-cell character, need to clear the next - // cell. Also when overwriting the left halve of a two-cell char - // with the right halve of a two-cell char. Do this only once - // (utf8_off2cells() may return 2 on the right halve). + // cell. Also when overwriting the left half of a two-cell char + // with the right half of a two-cell char. Do this only once + // (utf8_off2cells() may return 2 on the right half). if (clear_next_cell) { clear_next_cell = false; } else if ((len < 0 ? ptr[mbyte_blen] == NUL @@ -6351,9 +6382,9 @@ void grid_fill(ScreenGrid *grid, int start_row, int end_row, int start_col, int } for (int row = start_row; row < end_row; row++) { - // When drawing over the right halve of a double-wide char clear - // out the left halve. When drawing over the left halve of a - // double wide-char clear out the right halve. Only needed in a + // When drawing over the right half of a double-wide char clear + // out the left half. When drawing over the left half of a + // double wide-char clear out the right half. Only needed in a // terminal. if (start_col > 0 && grid_fix_col(grid, start_col, row) != start_col) { grid_puts_len(grid, (char_u *)" ", 1, row, start_col - 1, 0); @@ -6762,7 +6793,7 @@ void grid_clear_line(ScreenGrid *grid, unsigned off, int width, bool valid) void grid_invalidate(ScreenGrid *grid) { - (void)memset(grid->attrs, -1, grid->Rows * grid->Columns * sizeof(sattr_T)); + (void)memset(grid->attrs, -1, sizeof(sattr_T) * grid->Rows * grid->Columns); } bool grid_invalid_row(ScreenGrid *grid, int row) @@ -6891,8 +6922,6 @@ void grid_ins_lines(ScreenGrid *grid, int row, int line_count, int end, int col, if (!grid->throttled) { ui_call_grid_scroll(grid->handle, row, end, col, col+width, -line_count, 0); } - - return; } /// delete lines on the screen and move lines up. @@ -6943,8 +6972,6 @@ void grid_del_lines(ScreenGrid *grid, int row, int line_count, int end, int col, if (!grid->throttled) { ui_call_grid_scroll(grid->handle, row, end, col, col+width, line_count, 0); } - - return; } @@ -7326,7 +7353,7 @@ void draw_tabline(void) if (room > 0) { // Get buffer name in NameBuff[] get_trans_bufname(cwp->w_buffer); - (void)shorten_dir(NameBuff); + shorten_dir(NameBuff); len = vim_strsize(NameBuff); p = NameBuff; while (len > room) { diff --git a/src/nvim/search.c b/src/nvim/search.c index 906c9a6f47..682fa417a9 100644 --- a/src/nvim/search.c +++ b/src/nvim/search.c @@ -26,6 +26,7 @@ #include "nvim/func_attr.h" #include "nvim/getchar.h" #include "nvim/indent.h" +#include "nvim/indent_c.h" #include "nvim/main.h" #include "nvim/mark.h" #include "nvim/mbyte.h" @@ -2313,12 +2314,9 @@ pos_T *findmatchlimit(oparg_T *oap, int initc, int flags, int64_t maxtravel) return (pos_T *)NULL; // never found it } -/* - * Check if line[] contains a / / comment. - * Return MAXCOL if not, otherwise return the column. - * TODO: skip strings. - */ -static int check_linecomment(const char_u *line) +/// Check if line[] contains a / / comment. +/// @returns MAXCOL if not, otherwise return the column. +int check_linecomment(const char_u *line) { const char_u *p = line; // scan from start // skip Lispish one-line comments @@ -2338,7 +2336,8 @@ static int check_linecomment(const char_u *line) in_str = true; } } else if (!in_str && ((p - line) < 2 - || (*(p - 1) != '\\' && *(p - 2) != '#'))) { + || (*(p - 1) != '\\' && *(p - 2) != '#')) + && !is_pos_in_string(line, (colnr_T)(p - line))) { break; // found! } p++; @@ -2348,9 +2347,11 @@ static int check_linecomment(const char_u *line) } } else { while ((p = vim_strchr(p, '/')) != NULL) { - // accept a double /, unless it's preceded with * and followed by *, - // because * / / * is an end and start of a C comment - if (p[1] == '/' && (p == line || p[-1] != '*' || p[2] != '*')) { + // Accept a double /, unless it's preceded with * and followed by *, + // because * / / * is an end and start of a C comment. + // Only accept the position if it is not inside a string. + if (p[1] == '/' && (p == line || p[-1] != '*' || p[2] != '*') + && !is_pos_in_string(line, (colnr_T)(p - line))) { break; } ++p; @@ -4763,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/shada.c b/src/nvim/shada.c index e75a244031..b909888783 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -831,7 +831,7 @@ static int shada_read_file(const char *const file, const int flags) ShaDaReadDef sd_reader; const int of_ret = open_shada_file_for_reading(fname, &sd_reader); - if (p_verbose > 0) { + if (p_verbose > 1) { verbose_enter(); smsg(_("Reading ShaDa file \"%s\"%s%s%s%s"), fname, @@ -3036,7 +3036,7 @@ shada_write_file_nomerge: {} return FAIL; } - if (p_verbose > 0) { + if (p_verbose > 1) { verbose_enter(); smsg(_("Writing ShaDa file \"%s\""), fname); verbose_leave(); diff --git a/src/nvim/sign.c b/src/nvim/sign.c index a308df07d1..8b41781c98 100644 --- a/src/nvim/sign.c +++ b/src/nvim/sign.c @@ -1501,28 +1501,28 @@ static void sign_getinfo(sign_T *sp, dict_T *retdict) if (p == NULL) { p = "NONE"; } - tv_dict_add_str(retdict, S_LEN("linehl"), (char *)p); + tv_dict_add_str(retdict, S_LEN("linehl"), p); } if (sp->sn_text_hl > 0) { p = get_highlight_name_ext(NULL, sp->sn_text_hl - 1, false); if (p == NULL) { p = "NONE"; } - tv_dict_add_str(retdict, S_LEN("texthl"), (char *)p); + tv_dict_add_str(retdict, S_LEN("texthl"), p); } if (sp->sn_cul_hl > 0) { p = get_highlight_name_ext(NULL, sp->sn_cul_hl - 1, false); if (p == NULL) { p = "NONE"; } - tv_dict_add_str(retdict, S_LEN("culhl"), (char *)p); + tv_dict_add_str(retdict, S_LEN("culhl"), p); } if (sp->sn_num_hl > 0) { p = get_highlight_name_ext(NULL, sp->sn_num_hl - 1, false); if (p == NULL) { p = "NONE"; } - tv_dict_add_str(retdict, S_LEN("numhl"), (char *)p); + tv_dict_add_str(retdict, S_LEN("numhl"), p); } } diff --git a/src/nvim/spell.c b/src/nvim/spell.c index bd31e98faa..1296d410f6 100644 --- a/src/nvim/spell.c +++ b/src/nvim/spell.c @@ -219,7 +219,7 @@ typedef struct { #define SCORE_THRES3 100 // word count threshold for COMMON3 // When trying changed soundfold words it becomes slow when trying more than -// two changes. With less then two changes it's slightly faster but we miss a +// two changes. With less than two changes it's slightly faster but we miss a // few good suggestions. In rare cases we need to try three of four changes. #define SCORE_SFMAX1 200 // maximum score for first try #define SCORE_SFMAX2 300 // maximum score for second try @@ -1628,7 +1628,7 @@ size_t spell_move_to(win_T *wp, int dir, bool allwords, bool curline, hlf_T *att } // For spell checking: concatenate the start of the following line "line" into -// "buf", blanking-out special characters. Copy less then "maxlen" bytes. +// "buf", blanking-out special characters. Copy less than "maxlen" bytes. // Keep the blanks at the start of the next line, this is used in win_line() // to skip those bytes if the word was OK. void spell_cat_line(char_u *buf, char_u *line, int maxlen) @@ -4082,7 +4082,7 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so // char, e.g., "thes," -> "these". p = fword + sp->ts_fidx; MB_PTR_BACK(fword, p); - if (!spell_iswordp(p, curwin)) { + if (!spell_iswordp(p, curwin) && *preword != NUL) { p = preword + STRLEN(preword); MB_PTR_BACK(preword, p); if (spell_iswordp(p, curwin)) { @@ -6106,7 +6106,7 @@ static void spell_soundfold_wsal(slang_T *slang, char_u *inword, char_u *res) for (; ((ws = smp[n].sm_lead_w)[0] & 0xff) == (c & 0xff) && ws[0] != NUL; ++n) { // Quickly skip entries that don't match the word. Most - // entries are less then three chars, optimize for that. + // entries are less than three chars, optimize for that. if (c != ws[0]) { continue; } @@ -7057,7 +7057,7 @@ void spell_dump_compl(char_u *pat, int ic, Direction *dir, int dumpflags_arg) arridx[depth] = idxs[n]; curi[depth] = 1; - // Check if this characters matches with the pattern. + // Check if this character matches with the pattern. // If not skip the whole tree below it. // Always ignore case here, dump_word() will check // proper case later. This isn't exactly right when diff --git a/src/nvim/spellfile.c b/src/nvim/spellfile.c index 42bb3c61a5..d7b220b3f6 100644 --- a/src/nvim/spellfile.c +++ b/src/nvim/spellfile.c @@ -4395,7 +4395,7 @@ static int write_vim_spell(spellinfo_T *spin, char_u *fname) // // The table with character flags and the table for case folding. // This makes sure the same characters are recognized as word characters - // when generating an when using a spell file. + // when generating and when using a spell file. // Skip this for ASCII, the table may conflict with the one used for // 'encoding'. // Also skip this for an .add.spl file, the main spell file must contain @@ -5122,7 +5122,7 @@ static int sug_filltable(spellinfo_T *spin, wordnode_T *node, int startwordnr, g wordnr++; // Remove extra NUL entries, we no longer need them. We don't - // bother freeing the nodes, the won't be reused anyway. + // bother freeing the nodes, they won't be reused anyway. while (p->wn_sibling != NULL && p->wn_sibling->wn_byte == NUL) { p->wn_sibling = p->wn_sibling->wn_sibling; } @@ -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/state.c b/src/nvim/state.c index 68bc76660d..f9a3aaab7f 100644 --- a/src/nvim/state.c +++ b/src/nvim/state.c @@ -12,6 +12,7 @@ #include "nvim/lib/kvec.h" #include "nvim/log.h" #include "nvim/main.h" +#include "nvim/option.h" #include "nvim/option_defs.h" #include "nvim/os/input.h" #include "nvim/state.h" @@ -38,10 +39,16 @@ void state_enter(VimState *s) int key; getkey: - if (char_avail() || using_script() || input_available()) { - // Don't block for events if there's a character already available for - // processing. Characters can come from mappings, scripts and other - // sources, so this scenario is very common. + // Expand mappings first by calling vpeekc() directly. + // - If vpeekc() returns non-NUL, there is a character already available for processing, so + // don't block for events. vgetc() may still block, in case of an incomplete UTF-8 sequence. + // - If vpeekc() returns NUL, vgetc() will block, and there are three cases: + // - There is no input available. + // - All of available input maps to an empty string. + // - There is an incomplete mapping. + // A blocking wait for a character should only be done in the third case, which is the only + // case of the three where typebuf.tb_len > 0 after vpeekc() returns NUL. + if (vpeekc() != NUL || typebuf.tb_len > 0) { key = safe_vgetc(); } else if (!multiqueue_empty(main_loop.events)) { // Event was made available after the last multiqueue_process_events call @@ -54,9 +61,11 @@ getkey: // mapping engine. (void)os_inchar(NULL, 0, -1, 0, main_loop.events); // If an event was put into the queue, we send K_EVENT directly. - key = !multiqueue_empty(main_loop.events) - ? K_EVENT - : safe_vgetc(); + if (!multiqueue_empty(main_loop.events)) { + key = K_EVENT; + } else { + goto getkey; + } } if (key == K_EVENT) { @@ -107,15 +116,17 @@ void state_handle_k_event(void) /// Return true if in the current mode we need to use virtual. bool virtual_active(void) { + unsigned int cur_ve_flags = get_ve_flags(); + // While an operator is being executed we return "virtual_op", because // VIsual_active has already been reset, thus we can't check for "block" // being used. if (virtual_op != kNone) { return virtual_op; } - return ve_flags == VE_ALL - || ((ve_flags & VE_BLOCK) && VIsual_active && VIsual_mode == Ctrl_V) - || ((ve_flags & VE_INSERT) && (State & INSERT)); + return cur_ve_flags == VE_ALL + || ((cur_ve_flags & VE_BLOCK) && VIsual_active && VIsual_mode == Ctrl_V) + || ((cur_ve_flags & VE_INSERT) && (State & INSERT)); } /// VISUAL, SELECTMODE and OP_PENDING State are never set, they are equal to @@ -180,7 +191,7 @@ char *get_mode(void) buf[1] = 'x'; } } - } else if (State & CMDLINE) { + } else if ((State & CMDLINE) || exmode_active) { buf[0] = 'c'; if (exmode_active) { buf[1] = 'v'; diff --git a/src/nvim/strings.c b/src/nvim/strings.c index e2a8108c45..291138ef23 100644 --- a/src/nvim/strings.c +++ b/src/nvim/strings.c @@ -1001,22 +1001,20 @@ int vim_vsnprintf_typval(char *str, size_t str_m, const char *fmt, va_list ap, t - str_arg); } if (fmt_spec == 'S') { - if (min_field_width != 0) { - min_field_width += (strlen(str_arg) - - mb_string2cells((char_u *)str_arg)); - } - if (precision) { - char_u *p1; - size_t i = 0; - - for (p1 = (char_u *)str_arg; *p1; - p1 += utfc_ptr2len(p1)) { - i += (size_t)utf_ptr2cells(p1); - if (i > precision) { - break; - } + char_u *p1; + size_t i; + + for (i = 0, p1 = (char_u *)str_arg; *p1; p1 += utfc_ptr2len(p1)) { + size_t cell = (size_t)utf_ptr2cells(p1); + if (precision_specified && i + cell > precision) { + break; } - str_arg_l = (size_t)(p1 - (char_u *)str_arg); + i += cell; + } + + str_arg_l = (size_t)(p1 - (char_u *)str_arg); + if (min_field_width != 0) { + min_field_width += str_arg_l - i; } } break; diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c index a9447165c2..db10b71d38 100644 --- a/src/nvim/syntax.c +++ b/src/nvim/syntax.c @@ -27,6 +27,7 @@ #include "nvim/highlight.h" #include "nvim/indent_c.h" #include "nvim/keymap.h" +#include "nvim/lua/executor.h" #include "nvim/macros.h" #include "nvim/mbyte.h" #include "nvim/memline.h" @@ -3112,9 +3113,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 +3142,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 +3169,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 +3210,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 +3246,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")); @@ -6714,6 +6715,100 @@ int lookup_color(const int idx, const bool foreground, TriState *const boldp) return color; } +void set_hl_group(int id, HlAttrs attrs, Dict(highlight) *dict, int link_id) +{ + int idx = id - 1; // Index is ID minus one. + + bool is_default = attrs.rgb_ae_attr & HL_DEFAULT; + + // Return if "default" was used and the group already has settings + if (is_default && hl_has_settings(idx, true)) { + return; + } + + HlGroup *g = &HL_TABLE()[idx]; + + if (link_id > 0) { + g->sg_cleared = false; + g->sg_link = link_id; + g->sg_script_ctx = current_sctx; + g->sg_script_ctx.sc_lnum += sourcing_lnum; + g->sg_set |= SG_LINK; + if (is_default) { + g->sg_deflink = link_id; + g->sg_deflink_sctx = current_sctx; + g->sg_deflink_sctx.sc_lnum += sourcing_lnum; + } + return; + } + + g->sg_cleared = false; + g->sg_link = 0; + g->sg_gui = attrs.rgb_ae_attr; + + g->sg_rgb_fg = attrs.rgb_fg_color; + g->sg_rgb_bg = attrs.rgb_bg_color; + g->sg_rgb_sp = attrs.rgb_sp_color; + + struct { + char **dest; RgbValue val; Object name; + } cattrs[] = { + { &g->sg_rgb_fg_name, g->sg_rgb_fg, HAS_KEY(dict->fg) ? dict->fg : dict->foreground }, + { &g->sg_rgb_bg_name, g->sg_rgb_bg, HAS_KEY(dict->bg) ? dict->bg : dict->background }, + { &g->sg_rgb_sp_name, g->sg_rgb_sp, HAS_KEY(dict->sp) ? dict->sp : dict->special }, + { NULL, -1, NIL }, + }; + + char hex_name[8]; + char *name; + + for (int j = 0; cattrs[j].dest; j++) { + 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); + *cattrs[j].dest = xstrdup(name); + } + } + + g->sg_cterm = attrs.cterm_ae_attr; + g->sg_cterm_bg = attrs.cterm_bg_color; + g->sg_cterm_fg = attrs.cterm_fg_color; + g->sg_cterm_bold = g->sg_cterm & HL_BOLD; + g->sg_blend = attrs.hl_blend; + + g->sg_script_ctx = current_sctx; + g->sg_script_ctx.sc_lnum += sourcing_lnum; + + // 'Normal' is special + if (STRCMP(g->sg_name_u, "NORMAL") == 0) { + cterm_normal_fg_color = g->sg_cterm_fg; + cterm_normal_bg_color = g->sg_cterm_bg; + normal_fg = g->sg_rgb_fg; + normal_bg = g->sg_rgb_bg; + normal_sp = g->sg_rgb_sp; + ui_default_colors_set(); + } else { + g->sg_attr = hl_get_syn_attr(0, id, attrs); + + // a cursor style uses this syn_id, make sure its attribute is updated. + if (cursor_mode_uses_syn_id(id)) { + ui_mode_info_set(); + } + } +} + /// Handle ":highlight" command /// @@ -6825,6 +6920,7 @@ void do_highlight(const char *line, const bool forceit, const bool init) hlgroup->sg_deflink = to_id; hlgroup->sg_deflink_sctx = current_sctx; hlgroup->sg_deflink_sctx.sc_lnum += sourcing_lnum; + nlua_set_sctx(&hlgroup->sg_deflink_sctx); } } @@ -6845,6 +6941,7 @@ void do_highlight(const char *line, const bool forceit, const bool init) hlgroup->sg_link = to_id; hlgroup->sg_script_ctx = current_sctx; hlgroup->sg_script_ctx.sc_lnum += sourcing_lnum; + nlua_set_sctx(&hlgroup->sg_script_ctx); hlgroup->sg_cleared = false; redraw_all_later(SOME_VALID); @@ -7225,6 +7322,7 @@ void do_highlight(const char *line, const bool forceit, const bool init) } HL_TABLE()[idx].sg_script_ctx = current_sctx; HL_TABLE()[idx].sg_script_ctx.sc_lnum += sourcing_lnum; + nlua_set_sctx(&HL_TABLE()[idx].sg_script_ctx); } xfree(key); xfree(arg); @@ -8765,6 +8863,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 a10a2a0c32..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; } } @@ -3408,7 +3408,7 @@ static void tagstack_push_items(win_T *wp, list_T *l) if ((di = tv_dict_find(itemdict, "from", -1)) == NULL) { continue; } - if (list2fpos(&di->di_tv, &mark, &fnum, NULL) != OK) { + if (list2fpos(&di->di_tv, &mark, &fnum, NULL, false) != OK) { continue; } if ((tagname = (char_u *)tv_dict_get_string(itemdict, "tagname", true)) diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c index 6add541ad4..1c26e46a21 100644 --- a/src/nvim/terminal.c +++ b/src/nvim/terminal.c @@ -135,7 +135,6 @@ struct terminal { int row, col; bool visible; } cursor; - int pressed_button; // which mouse button is pressed bool pending_resize; // pending width/height bool color_set[16]; @@ -173,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) @@ -196,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; @@ -529,6 +534,10 @@ static int terminal_execute(VimState *state, int key) do_cmdline(NULL, getcmdkeycmd, NULL, 0); break; + case K_LUA: + map_execute_lua(); + break; + case Ctrl_N: if (s->got_bsl) { return 0; @@ -633,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 @@ -664,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) @@ -690,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) @@ -1209,21 +1207,12 @@ static VTermKey convert_key(int key, VTermModifier *statep) } } -static void mouse_action(Terminal *term, int button, int row, int col, bool drag, VTermModifier mod) +static void mouse_action(Terminal *term, int button, int row, int col, bool pressed, + VTermModifier mod) { - if (term->pressed_button && (term->pressed_button != button || !drag)) { - // release the previous button - vterm_mouse_button(term->vt, term->pressed_button, 0, mod); - term->pressed_button = 0; - } - - // move the mouse vterm_mouse_move(term->vt, row, col, mod); - - if (!term->pressed_button) { - // press the button if not already pressed - vterm_mouse_button(term->vt, button, 1, mod); - term->pressed_button = button; + if (button) { + vterm_mouse_button(term->vt, button, pressed, mod); } } @@ -1242,35 +1231,35 @@ static bool send_mouse_event(Terminal *term, int c) // event in the terminal window and mouse events was enabled by the // program. translate and forward the event int button; - bool drag = false; + bool pressed = false; switch (c) { case K_LEFTDRAG: - drag = true; FALLTHROUGH; case K_LEFTMOUSE: + pressed = true; FALLTHROUGH; + case K_LEFTRELEASE: button = 1; break; case K_MOUSEMOVE: - drag = true; button = 0; break; + button = 0; break; case K_MIDDLEDRAG: - drag = true; FALLTHROUGH; case K_MIDDLEMOUSE: + pressed = true; FALLTHROUGH; + case K_MIDDLERELEASE: button = 2; break; case K_RIGHTDRAG: - drag = true; FALLTHROUGH; case K_RIGHTMOUSE: + pressed = true; FALLTHROUGH; + case K_RIGHTRELEASE: button = 3; break; case K_MOUSEDOWN: - button = 4; break; + pressed = true; button = 4; break; case K_MOUSEUP: - button = 5; break; + pressed = true; button = 5; break; default: return false; } - mouse_action(term, button, row, col - offset, drag, 0); - size_t len = vterm_output_read(term->vt, term->textbuf, - sizeof(term->textbuf)); - terminal_send(term, term->textbuf, len); + mouse_action(term, button, row, col - offset, pressed, 0); return false; } @@ -1302,7 +1291,7 @@ static bool send_mouse_event(Terminal *term, int c) } end: - ins_char_typebuf(c); + ins_char_typebuf(c, mod_mask); return true; } diff --git a/src/nvim/testdir/check.vim b/src/nvim/testdir/check.vim index 14bab33a2f..883f036fe1 100644 --- a/src/nvim/testdir/check.vim +++ b/src/nvim/testdir/check.vim @@ -12,9 +12,9 @@ endfunc " Command to check for the absence of a feature. command -nargs=1 CheckNotFeature call CheckNotFeature(<f-args>) func CheckNotFeature(name) - if !has(a:name, 1) - throw 'Checking for non-existent feature ' .. a:name - endif + " if !has(a:name, 1) + " throw 'Checking for non-existent feature ' .. a:name + " endif if has(a:name) throw 'Skipped: ' .. a:name .. ' feature present' endif @@ -63,6 +63,15 @@ func CheckUnix() endif endfunc +" Command to check for not running on a BSD system. +" TODO: using this checks should not be needed +command CheckNotBSD call CheckNotBSD() +func CheckNotBSD() + if has('bsd') + throw 'Skipped: does not work on BSD' + endif +endfunc + " Command to check that making screendumps is supported. " Caller must source screendump.vim command CheckScreendump call CheckScreendump() @@ -104,6 +113,14 @@ func CheckNotGui() endif endfunc +" Command to check that test is not running as root +command CheckNotRoot call CheckNotRoot() +func CheckNotRoot() + if IsRoot() + throw 'Skipped: cannot run test as root' + endif +endfunc + " Command to check that the current language is English command CheckEnglish call CheckEnglish() func CheckEnglish() diff --git a/src/nvim/testdir/runnvim.sh b/src/nvim/testdir/runnvim.sh index 25cb8437b4..72d88f9f93 100755 --- a/src/nvim/testdir/runnvim.sh +++ b/src/nvim/testdir/runnvim.sh @@ -65,9 +65,6 @@ main() {( fi fi fi - if test "$FAILED" = 1 ; then - ci_fold start "$NVIM_TEST_CURRENT_SUITE/$test_name" - fi valgrind_check . if test -n "$LOG_DIR" ; then check_sanitizer "$LOG_DIR" @@ -78,9 +75,6 @@ main() {( fi rm -f "$tlog" if test "$FAILED" = 1 ; then - ci_fold end "$NVIM_TEST_CURRENT_SUITE/$test_name" - fi - if test "$FAILED" = 1 ; then echo "Test $test_name failed, see output above and summary for more details" >> test.log # When Neovim crashed/aborted it might not have created messages. # test.log itself is used as an indicator to exit non-zero in the Makefile. 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/shared.vim b/src/nvim/testdir/shared.vim index f456ff4250..c2809844ac 100644 --- a/src/nvim/testdir/shared.vim +++ b/src/nvim/testdir/shared.vim @@ -343,6 +343,15 @@ func RunVimPiped(before, after, arguments, pipecmd) return 1 endfunc +func IsRoot() + if !has('unix') + return v:false + elseif $USER == 'root' || system('id -un') =~ '\<root\>' + return v:true + endif + return v:false +endfunc + " Get all messages but drop the maintainer entry. func GetMessages() redir => result diff --git a/src/nvim/testdir/test_alot.vim b/src/nvim/testdir/test_alot.vim index cc767a9bcf..5a3d1d56bb 100644 --- a/src/nvim/testdir/test_alot.vim +++ b/src/nvim/testdir/test_alot.vim @@ -27,6 +27,7 @@ source test_join.vim source test_jumps.vim source test_fileformat.vim source test_filetype.vim +source test_filetype_lua.vim source test_lambda.vim source test_menu.vim source test_messages.vim @@ -38,7 +39,8 @@ source test_put.vim source test_rename.vim source test_scroll_opt.vim source test_shift.vim -source test_sort.vim +" Test fails on windows CI when using the MSVC compiler. +" source test_sort.vim source test_sha256.vim source test_suspend.vim source test_syn_attr.vim diff --git a/src/nvim/testdir/test_alot_utf8.vim b/src/nvim/testdir/test_alot_utf8.vim index 70f14320a6..77f5ede4c8 100644 --- a/src/nvim/testdir/test_alot_utf8.vim +++ b/src/nvim/testdir/test_alot_utf8.vim @@ -6,7 +6,6 @@ source test_charsearch_utf8.vim source test_expr_utf8.vim -source test_matchadd_conceal_utf8.vim source test_mksession_utf8.vim source test_regexp_utf8.vim source test_source_utf8.vim 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 45285b69a1..c39546b9ea 100644 --- a/src/nvim/testdir/test_autocmd.vim +++ b/src/nvim/testdir/test_autocmd.vim @@ -4,7 +4,7 @@ source shared.vim source check.vim source term_util.vim -func! s:cleanup_buffers() abort +func s:cleanup_buffers() abort for bnr in range(1, bufnr('$')) if bufloaded(bnr) && bufnr('%') != bnr execute 'bd! ' . bnr @@ -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() @@ -2380,6 +2392,40 @@ func Test_autocmd_was_using_freed_memory() pclose endfunc +func Test_BufWrite_lockmarks() + edit! Xtest + call setline(1, ['a', 'b', 'c', 'd']) + + " :lockmarks preserves the marks + call SetChangeMarks(2, 3) + lockmarks write + call assert_equal([2, 3], [line("'["), line("']")]) + + " *WritePre autocmds get the correct line range, but lockmarks preserves the + " original values for the user + augroup lockmarks + au! + au BufWritePre,FilterWritePre * call assert_equal([1, 4], [line("'["), line("']")]) + au FileWritePre * call assert_equal([3, 4], [line("'["), line("']")]) + augroup END + + lockmarks write + call assert_equal([2, 3], [line("'["), line("']")]) + + if executable('cat') + lockmarks %!cat + call assert_equal([2, 3], [line("'["), line("']")]) + endif + + lockmarks 3,4write Xtest2 + call assert_equal([2, 3], [line("'["), line("']")]) + + au! lockmarks + augroup! lockmarks + call delete('Xtest') + call delete('Xtest2') +endfunc + " FileChangedShell tested in test_filechanged.vim func LogACmd() @@ -2509,6 +2555,16 @@ func Test_close_autocmd_tab() %bwipe! endfunc +func Test_Visual_doautoall_redraw() + call setline(1, ['a', 'b']) + new + wincmd p + call feedkeys("G\<C-V>", 'txn') + autocmd User Explode ++once redraw + doautoall User Explode + %bwipe! +endfunc + func Test_autocmd_closes_window() au BufNew,BufWinLeave * e %e file yyy 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_buffer.vim b/src/nvim/testdir/test_buffer.vim index 40111fdf06..a31cdbb49a 100644 --- a/src/nvim/testdir/test_buffer.vim +++ b/src/nvim/testdir/test_buffer.vim @@ -1,5 +1,7 @@ " Tests for Vim buffer +source check.vim + func Test_buffer_error() new foo1 new foo2 @@ -30,4 +32,33 @@ func Test_balt() call assert_equal('OtherBuffer', bufname()) endfunc +" Test for buffer match URL(scheme) check +" scheme is alpha and inner hyphen only. +func Test_buffer_scheme() + CheckMSWindows + + set noshellslash + %bwipe! + let bufnames = [ + \ #{id: 'b0', name: 'test://xyz/foo/b0' , match: 1}, + \ #{id: 'b1', name: 'test+abc://xyz/foo/b1', match: 0}, + \ #{id: 'b2', name: 'test_abc://xyz/foo/b2', match: 0}, + \ #{id: 'b3', name: 'test-abc://xyz/foo/b3', match: 1}, + \ #{id: 'b4', name: '-test://xyz/foo/b4' , match: 0}, + \ #{id: 'b5', name: 'test-://xyz/foo/b5' , match: 0}, + \] + for buf in bufnames + new `=buf.name` + if buf.match + call assert_equal(buf.name, getbufinfo(buf.id)[0].name) + else + " slashes will have become backslashes + call assert_notequal(buf.name, getbufinfo(buf.id)[0].name) + endif + bwipe + endfor + + set shellslash& +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_cd.vim b/src/nvim/testdir/test_cd.vim index 76a2620be0..c364babd65 100644 --- a/src/nvim/testdir/test_cd.vim +++ b/src/nvim/testdir/test_cd.vim @@ -44,6 +44,15 @@ func Test_cd_minus() cd - call assert_equal(path, getcwd()) + " Test for :cd - after a failed :cd + " v8.2.1183 is not ported yet + " call assert_fails('cd /nonexistent', 'E344:') + call assert_fails('cd /nonexistent', 'E472:') + call assert_equal(path, getcwd()) + cd - + call assert_equal(path_dotdot, getcwd()) + cd - + " Test for :cd - without a previous directory let lines =<< trim [SCRIPT] call assert_fails('cd -', 'E186:') 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_charsearch_utf8.vim b/src/nvim/testdir/test_charsearch_utf8.vim index 09341a90b0..82a807ac5b 100644 --- a/src/nvim/testdir/test_charsearch_utf8.vim +++ b/src/nvim/testdir/test_charsearch_utf8.vim @@ -1,7 +1,7 @@ " Tests for related f{char} and t{char} using utf-8. " Test for t,f,F,T movement commands -function! Test_search_cmds() +func Test_search_cmds() new! call setline(1, "・最初から最後まで最強のVimは最高") 1 diff --git a/src/nvim/testdir/test_cindent.vim b/src/nvim/testdir/test_cindent.vim index 562867f548..4b702bf2b8 100644 --- a/src/nvim/testdir/test_cindent.vim +++ b/src/nvim/testdir/test_cindent.vim @@ -1,6 +1,5 @@ " Test for cinoptions and cindent " -" TODO: rewrite test3.in into this new style test func Test_cino_hash() " Test that curbuf->b_ind_hash_comment is correctly reset @@ -128,12 +127,5187 @@ func Test_cindent_func() bwipe! endfunc +func Test_cindent_1() + new + setl cindent ts=4 sw=4 + setl cino& sts& + + let code =<< trim [CODE] + /* start of AUTO matically checked vim: set ts=4 : */ + { + if (test) + cmd1; + cmd2; + } + + { + if (test) + cmd1; + else + cmd2; + } + + { + if (test) + { + cmd1; + cmd2; + } + } + + { + if (test) + { + cmd1; + else + } + } + + { + while (this) + if (test) + cmd1; + cmd2; + } + + { + while (this) + if (test) + cmd1; + else + cmd2; + } + + { + if (test) + { + cmd; + } + + if (test) + cmd; + } + + { + if (test) { + cmd; + } + + if (test) cmd; + } + + { + cmd1; + for (blah) + while (this) + if (test) + cmd2; + cmd3; + } + + { + cmd1; + for (blah) + while (this) + if (test) + cmd2; + cmd3; + + if (test) + { + cmd1; + cmd2; + cmd3; + } + } + + + /* Test for 'cindent' do/while mixed with if/else: */ + + { + do + if (asdf) + asdfasd; + while (cond); + + do + if (asdf) + while (asdf) + asdf; + while (asdf); + } + + /* Test for 'cindent' with two ) on a continuation line */ + { + if (asdfasdf;asldkfj asdlkfj as;ldkfj sal;d + aal;sdkjf ( ;asldfkja;sldfk + al;sdjfka ;slkdf ) sa;ldkjfsa dlk;) + line up here; + } + + + /* C++ tests: */ + + // foo() these three lines should remain in column 0 + // { + // } + + /* Test for continuation and unterminated lines: */ + { + i = 99 + 14325 + + 21345 + + 21345 + + 21345 + ( 21345 + + 21345) + + 2345 + + 1234; + c = 1; + } + + /* + testje for indent with empty line + + here */ + + { + if (testing && + not a joke || + line up here) + hay; + if (testing && + (not a joke || testing + )line up here) + hay; + if (testing && + (not a joke || testing + line up here)) + hay; + } + + + { + switch (c) + { + case xx: + do + if (asdf) + do + asdfasdf; + while (asdf); + else + asdfasdf; + while (cond); + case yy: + case xx: + case zz: + testing; + } + } + + { + if (cond) { + foo; + } + else + { + bar; + } + } + + { + if (alskdfj ;alsdkfjal;skdjf (;sadlkfsa ;dlkf j;alksdfj ;alskdjf + alsdkfj (asldk;fj + awith cino=(0 ;lf this one goes to below the paren with == + ;laksjfd ;lsakdjf ;alskdf asd) + asdfasdf;))) + asdfasdf; + } + + int + func(a, b) + int a; + int c; + { + if (c1 && (c2 || + c3)) + foo; + if (c1 && + (c2 || c3) + ) + } + + { + while (asd) + { + if (asdf) + if (test) + if (that) + { + if (asdf) + do + cdasd; + while (as + df); + } + else + if (asdf) + asdf; + else + asdf; + asdf; + } + } + + { + s = "/*"; b = ';' + s = "/*"; b = ';'; + a = b; + } + + { + switch (a) + { + case a: + switch (t) + { + case 1: + cmd; + break; + case 2: + cmd; + break; + } + cmd; + break; + case b: + { + int i; + cmd; + } + break; + case c: { + int i; + cmd; + } + case d: if (cond && + test) { /* this line doesn't work right */ + int i; + cmd; + } + break; + } + } + + { + if (!(vim_strchr(p_cpo, CPO_BUFOPTGLOB) != NULL && entering) && + (bp_to->b_p_initialized || + (!entering && vim_strchr(p_cpo, CPO_BUFOPT) != NULL))) + return; + label : + asdf = asdf ? + asdf : asdf; + asdf = asdf ? + asdf: asdf; + } + + /* Special Comments : This function has the added complexity (compared */ + /* : to addtolist) of having to check for a detail */ + /* : texture and add that to the list first. */ + + char *(array[100]) = { + "testje", + "foo", + "bar", + } + + enum soppie + { + yes = 0, + no, + maybe + }; + + typedef enum soppie + { + yes = 0, + no, + maybe + }; + + static enum + { + yes = 0, + no, + maybe + } soppie; + + public static enum + { + yes = 0, + no, + maybe + } soppie; + + static private enum + { + yes = 0, + no, + maybe + } soppie; + + { + int a, + b; + } + + { + struct Type + { + int i; + char *str; + } var[] = + { + 0, "zero", + 1, "one", + 2, "two", + 3, "three" + }; + + float matrix[3][3] = + { + { + 0, + 1, + 2 + }, + { + 3, + 4, + 5 + }, + { + 6, + 7, + 8 + } + }; + } + + { + /* blah ( blah */ + /* where does this go? */ + + /* blah ( blah */ + cmd; + + func(arg1, + /* comment */ + arg2); + a; + { + b; + { + c; /* Hey, NOW it indents?! */ + } + } + + { + func(arg1, + arg2, + arg3); + /* Hey, what am I doing here? Is this coz of the ","? */ + } + } + + main () + { + if (cond) + { + a = b; + } + if (cond) { + a = c; + } + if (cond) + a = d; + return; + } + + { + case 2: if (asdf && + asdfasdf) + aasdf; + a = 9; + case 3: if (asdf) + aasdf; + a = 9; + case 4: x = 1; + y = 2; + + label: if (asdf) + here; + + label: if (asdf && + asdfasdf) + { + } + + label: if (asdf && + asdfasdf) { + there; + } + + label: if (asdf && + asdfasdf) + there; + } + + { + /* + hello with ":set comments= cino=c5" + */ + + /* + hello with ":set comments= cino=" + */ + } + + + { + if (a < b) { + a = a + 1; + } else + a = a + 2; + + if (a) + do { + testing; + } while (asdfasdf); + a = b + 1; + asdfasdf + } + + { + for ( int i = 0; + i < 10; i++ ) + { + } + i = 0; + } + + class bob + { + int foo() {return 1;} + int bar; + } + + main() + { + while(1) + if (foo) + { + bar; + } + else { + asdf; + } + misplacedline; + } + + { + if (clipboard.state == SELECT_DONE + && ((row == clipboard.start.lnum + && col >= clipboard.start.col) + || row > clipboard.start.lnum)) + } + + { + if (1) {i += 4;} + where_am_i; + return 0; + } + + { + { + } // sdf(asdf + if (asdf) + asd; + } + + { + label1: + label2: + } + + { + int fooRet = foo(pBar1, false /*fKB*/, + true /*fPTB*/, 3 /*nT*/, false /*fDF*/); + f() { + for ( i = 0; + i < m; + /* c */ i++ ) { + a = b; + } + } + } + + { + f1(/*comment*/); + f2(); + } + + { + do { + if (foo) { + } else + ; + } while (foo); + foo(); // was wrong + } + + int x; // no extra indent because of the ; + void func() + { + } + + char *tab[] = {"aaa", + "};", /* }; */ NULL} + int indented; + {} + + char *a[] = {"aaa", "bbb", + "ccc", NULL}; + // here + + char *tab[] = {"aaa", + "xx", /* xx */}; /* asdf */ + int not_indented; + + { + do { + switch (bla) + { + case 1: if (foo) + bar; + } + } while (boo); + wrong; + } + + int foo, + bar; + int foo; + + #if defined(foo) \ + && defined(bar) + char * xx = "asdf\ + foo\ + bor"; + int x; + + char *foo = "asdf\ + asdf\ + asdf", + *bar; + + void f() + { + #if defined(foo) \ + && defined(bar) + char *foo = "asdf\ + asdf\ + asdf", + *bar; + { + int i; + char *foo = "asdf\ + asdf\ + asdf", + *bar; + } + #endif + } + #endif + + int y; // comment + // comment + + // comment + + { + Constructor(int a, + int b ) : BaseClass(a) + { + } + } + + void foo() + { + char one, + two; + struct bla piet, + jan; + enum foo kees, + jannie; + static unsigned sdf, + krap; + unsigned int piet, + jan; + int + kees, + jan; + } + + { + t(int f, + int d); // ) + d(); + } + + Constructor::Constructor(int a, + int b + ) : + BaseClass(a, + b, + c), + mMember(b), + { + } + + Constructor::Constructor(int a, + int b ) : + BaseClass(a) + { + } + + Constructor::Constructor(int a, + int b ) /*x*/ : /*x*/ BaseClass(a), + member(b) + { + } + + A::A(int a, int b) + : aa(a), + bb(b), + cc(c) + { + } + + class CAbc : + public BaseClass1, + protected BaseClass2 + { + int Test() { return FALSE; } + int Test1() { return TRUE; } + + CAbc(int a, int b ) : + BaseClass(a) + { + switch(xxx) + { + case abc: + asdf(); + break; + + case 999: + baer(); + break; + } + } + + public: // <-- this was incorrectly indented before!! + void testfall(); + protected: + void testfall(); + }; + + class CAbc : public BaseClass1, + protected BaseClass2 + { + }; + + static struct + { + int a; + int b; + } variable[COUNT] = + { + { + 123, + 456 + }, + { + 123, + 456 + } + }; + + static struct + { + int a; + int b; + } variable[COUNT] = + { + { 123, 456 }, + { 123, 456 } + }; + + void asdf() /* ind_maxparen may cause trouble here */ + { + if ((0 + && 1 + && 1 + && 1 + && 1 + && 1 + && 1 + && 1 + && 1 + && 1 + && 1 + && 1 + && 1 + && 1 + && 1 + && 1 + && 1 + && 1 + && 1 + && 1 + && 1 + && 1 + && 1 + && 1 + && 1 + && 1)) break; + } + + foo() + { + a = cond ? foo() : asdf + + asdf; + + a = cond ? + foo() : asdf + + asdf; + } + + int main(void) + { + if (a) + if (b) + 2; + else 3; + next_line_of_code(); + } + + barry() + { + Foo::Foo (int one, + int two) + : something(4) + {} + } + + barry() + { + Foo::Foo (int one, int two) + : something(4) + {} + } + + Constructor::Constructor(int a, + int b + ) : + BaseClass(a, + b, + c), + mMember(b) + { + } + int main () + { + if (lala) + do + ++(*lolo); + while (lili + && lele); + lulu; + } + + int main () + { + switch (c) + { + case 'c': if (cond) + { + } + } + } + + main() + { + (void) MyFancyFuasdfadsfnction( + argument); + } + + main() + { + char foo[] = "/*"; + /* as + df */ + hello + } + + /* valid namespaces with normal indent */ + namespace + { + { + 111111111111; + } + } + namespace /* test */ + { + 11111111111111111; + } + namespace // test + { + 111111111111111111; + } + namespace + { + 111111111111111111; + } + namespace test + { + 111111111111111111; + } + namespace{ + 111111111111111111; + } + namespace test{ + 111111111111111111; + } + namespace { + 111111111111111111; + } + namespace test { + 111111111111111111; + namespace test2 { + 22222222222222222; + } + } + inline namespace { + 111111111111111111; + } + inline /* test */ namespace { + 111111111111111111; + } + inline/* test */namespace { + 111111111111111111; + } + + /* invalid namespaces use block indent */ + namespace test test2 { + 111111111111111111111; + } + namespace11111111111 { + 111111111111; + } + namespace() { + 1111111111111; + } + namespace() + { + 111111111111111111; + } + namespace test test2 + { + 1111111111111111111; + } + namespace111111111 + { + 111111111111111111; + } + inlinenamespace { + 111111111111111111; + } + + void getstring() { + /* Raw strings */ + const char* s = R"( + test { + # comment + field: 123 + } + )"; + } + + void getstring() { + const char* s = R"foo( + test { + # comment + field: 123 + } + )foo"; + } + + { + int a[4] = { + [0] = 0, + [1] = 1, + [2] = 2, + [3] = 3, + }; + } + + { + a = b[2] + + 3; + } + + { + if (1) + /* aaaaa + * bbbbb + */ + a = 1; + } + + void func() + { + switch (foo) + { + case (bar): + if (baz()) + quux(); + break; + case (shmoo): + if (!bar) + { + } + case (foo1): + switch (bar) + { + case baz: + baz_f(); + break; + } + break; + default: + baz(); + baz(); + break; + } + } + + /* end of AUTO */ + [CODE] + + call append(0, code) + normal gg + call search('start of AUTO') + exe "normal =/end of AUTO\<CR>" + + let expected =<< trim [CODE] + /* start of AUTO matically checked vim: set ts=4 : */ + { + if (test) + cmd1; + cmd2; + } + + { + if (test) + cmd1; + else + cmd2; + } + + { + if (test) + { + cmd1; + cmd2; + } + } + + { + if (test) + { + cmd1; + else + } + } + + { + while (this) + if (test) + cmd1; + cmd2; + } + + { + while (this) + if (test) + cmd1; + else + cmd2; + } + + { + if (test) + { + cmd; + } + + if (test) + cmd; + } + + { + if (test) { + cmd; + } + + if (test) cmd; + } + + { + cmd1; + for (blah) + while (this) + if (test) + cmd2; + cmd3; + } + + { + cmd1; + for (blah) + while (this) + if (test) + cmd2; + cmd3; + + if (test) + { + cmd1; + cmd2; + cmd3; + } + } + + + /* Test for 'cindent' do/while mixed with if/else: */ + + { + do + if (asdf) + asdfasd; + while (cond); + + do + if (asdf) + while (asdf) + asdf; + while (asdf); + } + + /* Test for 'cindent' with two ) on a continuation line */ + { + if (asdfasdf;asldkfj asdlkfj as;ldkfj sal;d + aal;sdkjf ( ;asldfkja;sldfk + al;sdjfka ;slkdf ) sa;ldkjfsa dlk;) + line up here; + } + + + /* C++ tests: */ + + // foo() these three lines should remain in column 0 + // { + // } + + /* Test for continuation and unterminated lines: */ + { + i = 99 + 14325 + + 21345 + + 21345 + + 21345 + ( 21345 + + 21345) + + 2345 + + 1234; + c = 1; + } + + /* + testje for indent with empty line + + here */ + + { + if (testing && + not a joke || + line up here) + hay; + if (testing && + (not a joke || testing + )line up here) + hay; + if (testing && + (not a joke || testing + line up here)) + hay; + } + + + { + switch (c) + { + case xx: + do + if (asdf) + do + asdfasdf; + while (asdf); + else + asdfasdf; + while (cond); + case yy: + case xx: + case zz: + testing; + } + } + + { + if (cond) { + foo; + } + else + { + bar; + } + } + + { + if (alskdfj ;alsdkfjal;skdjf (;sadlkfsa ;dlkf j;alksdfj ;alskdjf + alsdkfj (asldk;fj + awith cino=(0 ;lf this one goes to below the paren with == + ;laksjfd ;lsakdjf ;alskdf asd) + asdfasdf;))) + asdfasdf; + } + + int + func(a, b) + int a; + int c; + { + if (c1 && (c2 || + c3)) + foo; + if (c1 && + (c2 || c3) + ) + } + + { + while (asd) + { + if (asdf) + if (test) + if (that) + { + if (asdf) + do + cdasd; + while (as + df); + } + else + if (asdf) + asdf; + else + asdf; + asdf; + } + } + + { + s = "/*"; b = ';' + s = "/*"; b = ';'; + a = b; + } + + { + switch (a) + { + case a: + switch (t) + { + case 1: + cmd; + break; + case 2: + cmd; + break; + } + cmd; + break; + case b: + { + int i; + cmd; + } + break; + case c: { + int i; + cmd; + } + case d: if (cond && + test) { /* this line doesn't work right */ + int i; + cmd; + } + break; + } + } + + { + if (!(vim_strchr(p_cpo, CPO_BUFOPTGLOB) != NULL && entering) && + (bp_to->b_p_initialized || + (!entering && vim_strchr(p_cpo, CPO_BUFOPT) != NULL))) + return; + label : + asdf = asdf ? + asdf : asdf; + asdf = asdf ? + asdf: asdf; + } + + /* Special Comments : This function has the added complexity (compared */ + /* : to addtolist) of having to check for a detail */ + /* : texture and add that to the list first. */ + + char *(array[100]) = { + "testje", + "foo", + "bar", + } + + enum soppie + { + yes = 0, + no, + maybe + }; + + typedef enum soppie + { + yes = 0, + no, + maybe + }; + + static enum + { + yes = 0, + no, + maybe + } soppie; + + public static enum + { + yes = 0, + no, + maybe + } soppie; + + static private enum + { + yes = 0, + no, + maybe + } soppie; + + { + int a, + b; + } + + { + struct Type + { + int i; + char *str; + } var[] = + { + 0, "zero", + 1, "one", + 2, "two", + 3, "three" + }; + + float matrix[3][3] = + { + { + 0, + 1, + 2 + }, + { + 3, + 4, + 5 + }, + { + 6, + 7, + 8 + } + }; + } + + { + /* blah ( blah */ + /* where does this go? */ + + /* blah ( blah */ + cmd; + + func(arg1, + /* comment */ + arg2); + a; + { + b; + { + c; /* Hey, NOW it indents?! */ + } + } + + { + func(arg1, + arg2, + arg3); + /* Hey, what am I doing here? Is this coz of the ","? */ + } + } + + main () + { + if (cond) + { + a = b; + } + if (cond) { + a = c; + } + if (cond) + a = d; + return; + } + + { + case 2: if (asdf && + asdfasdf) + aasdf; + a = 9; + case 3: if (asdf) + aasdf; + a = 9; + case 4: x = 1; + y = 2; + + label: if (asdf) + here; + + label: if (asdf && + asdfasdf) + { + } + + label: if (asdf && + asdfasdf) { + there; + } + + label: if (asdf && + asdfasdf) + there; + } + + { + /* + hello with ":set comments= cino=c5" + */ + + /* + hello with ":set comments= cino=" + */ + } + + + { + if (a < b) { + a = a + 1; + } else + a = a + 2; + + if (a) + do { + testing; + } while (asdfasdf); + a = b + 1; + asdfasdf + } + + { + for ( int i = 0; + i < 10; i++ ) + { + } + i = 0; + } + + class bob + { + int foo() {return 1;} + int bar; + } + + main() + { + while(1) + if (foo) + { + bar; + } + else { + asdf; + } + misplacedline; + } + + { + if (clipboard.state == SELECT_DONE + && ((row == clipboard.start.lnum + && col >= clipboard.start.col) + || row > clipboard.start.lnum)) + } + + { + if (1) {i += 4;} + where_am_i; + return 0; + } + + { + { + } // sdf(asdf + if (asdf) + asd; + } + + { + label1: + label2: + } + + { + int fooRet = foo(pBar1, false /*fKB*/, + true /*fPTB*/, 3 /*nT*/, false /*fDF*/); + f() { + for ( i = 0; + i < m; + /* c */ i++ ) { + a = b; + } + } + } + + { + f1(/*comment*/); + f2(); + } + + { + do { + if (foo) { + } else + ; + } while (foo); + foo(); // was wrong + } + + int x; // no extra indent because of the ; + void func() + { + } + + char *tab[] = {"aaa", + "};", /* }; */ NULL} + int indented; + {} + + char *a[] = {"aaa", "bbb", + "ccc", NULL}; + // here + + char *tab[] = {"aaa", + "xx", /* xx */}; /* asdf */ + int not_indented; + + { + do { + switch (bla) + { + case 1: if (foo) + bar; + } + } while (boo); + wrong; + } + + int foo, + bar; + int foo; + + #if defined(foo) \ + && defined(bar) + char * xx = "asdf\ + foo\ + bor"; + int x; + + char *foo = "asdf\ + asdf\ + asdf", + *bar; + + void f() + { + #if defined(foo) \ + && defined(bar) + char *foo = "asdf\ + asdf\ + asdf", + *bar; + { + int i; + char *foo = "asdf\ + asdf\ + asdf", + *bar; + } + #endif + } + #endif + + int y; // comment + // comment + + // comment + + { + Constructor(int a, + int b ) : BaseClass(a) + { + } + } + + void foo() + { + char one, + two; + struct bla piet, + jan; + enum foo kees, + jannie; + static unsigned sdf, + krap; + unsigned int piet, + jan; + int + kees, + jan; + } + + { + t(int f, + int d); // ) + d(); + } + + Constructor::Constructor(int a, + int b + ) : + BaseClass(a, + b, + c), + mMember(b), + { + } + + Constructor::Constructor(int a, + int b ) : + BaseClass(a) + { + } + + Constructor::Constructor(int a, + int b ) /*x*/ : /*x*/ BaseClass(a), + member(b) + { + } + + A::A(int a, int b) + : aa(a), + bb(b), + cc(c) + { + } + + class CAbc : + public BaseClass1, + protected BaseClass2 + { + int Test() { return FALSE; } + int Test1() { return TRUE; } + + CAbc(int a, int b ) : + BaseClass(a) + { + switch(xxx) + { + case abc: + asdf(); + break; + + case 999: + baer(); + break; + } + } + + public: // <-- this was incorrectly indented before!! + void testfall(); + protected: + void testfall(); + }; + + class CAbc : public BaseClass1, + protected BaseClass2 + { + }; + + static struct + { + int a; + int b; + } variable[COUNT] = + { + { + 123, + 456 + }, + { + 123, + 456 + } + }; + + static struct + { + int a; + int b; + } variable[COUNT] = + { + { 123, 456 }, + { 123, 456 } + }; + + void asdf() /* ind_maxparen may cause trouble here */ + { + if ((0 + && 1 + && 1 + && 1 + && 1 + && 1 + && 1 + && 1 + && 1 + && 1 + && 1 + && 1 + && 1 + && 1 + && 1 + && 1 + && 1 + && 1 + && 1 + && 1 + && 1 + && 1 + && 1 + && 1 + && 1 + && 1)) break; + } + + foo() + { + a = cond ? foo() : asdf + + asdf; + + a = cond ? + foo() : asdf + + asdf; + } + + int main(void) + { + if (a) + if (b) + 2; + else 3; + next_line_of_code(); + } + + barry() + { + Foo::Foo (int one, + int two) + : something(4) + {} + } + + barry() + { + Foo::Foo (int one, int two) + : something(4) + {} + } + + Constructor::Constructor(int a, + int b + ) : + BaseClass(a, + b, + c), + mMember(b) + { + } + int main () + { + if (lala) + do + ++(*lolo); + while (lili + && lele); + lulu; + } + + int main () + { + switch (c) + { + case 'c': if (cond) + { + } + } + } + + main() + { + (void) MyFancyFuasdfadsfnction( + argument); + } + + main() + { + char foo[] = "/*"; + /* as + df */ + hello + } + + /* valid namespaces with normal indent */ + namespace + { + { + 111111111111; + } + } + namespace /* test */ + { + 11111111111111111; + } + namespace // test + { + 111111111111111111; + } + namespace + { + 111111111111111111; + } + namespace test + { + 111111111111111111; + } + namespace{ + 111111111111111111; + } + namespace test{ + 111111111111111111; + } + namespace { + 111111111111111111; + } + namespace test { + 111111111111111111; + namespace test2 { + 22222222222222222; + } + } + inline namespace { + 111111111111111111; + } + inline /* test */ namespace { + 111111111111111111; + } + inline/* test */namespace { + 111111111111111111; + } + + /* invalid namespaces use block indent */ + namespace test test2 { + 111111111111111111111; + } + namespace11111111111 { + 111111111111; + } + namespace() { + 1111111111111; + } + namespace() + { + 111111111111111111; + } + namespace test test2 + { + 1111111111111111111; + } + namespace111111111 + { + 111111111111111111; + } + inlinenamespace { + 111111111111111111; + } + + void getstring() { + /* Raw strings */ + const char* s = R"( + test { + # comment + field: 123 + } + )"; + } + + void getstring() { + const char* s = R"foo( + test { + # comment + field: 123 + } + )foo"; + } + + { + int a[4] = { + [0] = 0, + [1] = 1, + [2] = 2, + [3] = 3, + }; + } + + { + a = b[2] + + 3; + } + + { + if (1) + /* aaaaa + * bbbbb + */ + a = 1; + } + + void func() + { + switch (foo) + { + case (bar): + if (baz()) + quux(); + break; + case (shmoo): + if (!bar) + { + } + case (foo1): + switch (bar) + { + case baz: + baz_f(); + break; + } + break; + default: + baz(); + baz(); + break; + } + } + + /* end of AUTO */ + + [CODE] + + call assert_equal(expected, getline(1, '$')) + enew! | close +endfunc + +func Test_cindent_2() + new + setl cindent ts=4 sw=4 + setl tw=0 noai fo=croq + let &wm = &columns - 20 + + let code =<< trim [CODE] + { + + /* this is + * a real serious important big + * comment + */ + /* insert " about life, the universe, and the rest" after "serious" */ + } + [CODE] + + call append(0, code) + normal gg + call search('serious', 'e') + normal a about life, the universe, and the rest + + let expected =<< trim [CODE] + { + + /* this is + * a real serious + * about life, the + * universe, and the + * rest important big + * comment + */ + /* insert " about life, the universe, and the rest" after "serious" */ + } + + [CODE] + + call assert_equal(expected, getline(1, '$')) + set wm& + enew! | close +endfunc + +func Test_cindent_3() + new + setl nocindent ts=4 sw=4 + + let code =<< trim [CODE] + { + /* + * Testing for comments, without 'cin' set + */ + + /* + * what happens here? + */ + + /* + the end of the comment, try inserting a line below */ + + /* how about + this one */ + } + [CODE] + + call append(0, code) + normal gg + call search('comments') + normal joabout life + call search('happens') + normal jothere + call search('below') + normal oline + call search('this') + normal Ohello + + let expected =<< trim [CODE] + { + /* + * Testing for comments, without 'cin' set + */ + about life + + /* + * what happens here? + */ + there + + /* + the end of the comment, try inserting a line below */ + line + + /* how about + hello + this one */ + } + + [CODE] + + call assert_equal(expected, getline(1, '$')) + enew! | close +endfunc + +func Test_cindent_4() + new + setl cindent ts=4 sw=4 + + let code =<< trim [CODE] + { + var = this + that + vec[0] * vec[0] + + vec[1] * vec[1] + + vec2[2] * vec[2]; + } + [CODE] + + call append(0, code) + normal gg + call search('vec2') + normal == + + let expected =<< trim [CODE] + { + var = this + that + vec[0] * vec[0] + + vec[1] * vec[1] + + vec2[2] * vec[2]; + } + + [CODE] + + call assert_equal(expected, getline(1, '$')) + enew! | close +endfunc + +func Test_cindent_5() + new + setl cindent ts=4 sw=4 + setl cino=}4 + + let code =<< trim [CODE] + { + asdf asdflkajds f; + if (tes & ting) { + asdf asdf asdf ; + asdfa sdf asdf; + } + testing1; + if (tes & ting) + { + asdf asdf asdf ; + asdfa sdf asdf; + } + testing2; + } + [CODE] + + call append(0, code) + normal gg + call search('testing1') + exe "normal k2==/testing2\<CR>" + normal k2== + + let expected =<< trim [CODE] + { + asdf asdflkajds f; + if (tes & ting) { + asdf asdf asdf ; + asdfa sdf asdf; + } + testing1; + if (tes & ting) + { + asdf asdf asdf ; + asdfa sdf asdf; + } + testing2; + } + + [CODE] + + call assert_equal(expected, getline(1, '$')) + enew! | close +endfunc + +func Test_cindent_6() + new + setl cindent ts=4 sw=4 + setl cino=(0,)20 + + let code =<< trim [CODE] + main ( int first_par, /* + * Comment for + * first par + */ + int second_par /* + * Comment for + * second par + */ + ) + { + func( first_par, /* + * Comment for + * first par + */ + second_par /* + * Comment for + * second par + */ + ); + + } + [CODE] + + call append(0, code) + normal gg + call search('main') + normal =][ + + let expected =<< trim [CODE] + main ( int first_par, /* + * Comment for + * first par + */ + int second_par /* + * Comment for + * second par + */ + ) + { + func( first_par, /* + * Comment for + * first par + */ + second_par /* + * Comment for + * second par + */ + ); + + } + + [CODE] + + call assert_equal(expected, getline(1, '$')) + enew! | close +endfunc + +func Test_cindent_7() + new + setl cindent ts=4 sw=4 + setl cino=es,n0s + + let code =<< trim [CODE] + main(void) + { + /* Make sure that cino=X0s is not parsed like cino=Xs. */ + if (cond) + foo(); + else + { + bar(); + } + } + [CODE] + + call append(0, code) + normal gg + call search('main') + normal =][ + + let expected =<< trim [CODE] + main(void) + { + /* Make sure that cino=X0s is not parsed like cino=Xs. */ + if (cond) + foo(); + else + { + bar(); + } + } + + [CODE] + + call assert_equal(expected, getline(1, '$')) + enew! | close +endfunc + +func Test_cindent_8() + new + setl cindent ts=4 sw=4 + setl cino= + + let code =<< trim [CODE] + + { + do + { + if () + { + if () + asdf; + else + asdf; + } + } while (); + cmd; /* this should go under the } */ + } + [CODE] + + call append(0, code) + normal gg + normal ]]=][ + + let expected =<< trim [CODE] + + { + do + { + if () + { + if () + asdf; + else + asdf; + } + } while (); + cmd; /* this should go under the } */ + } + + [CODE] + + call assert_equal(expected, getline(1, '$')) + enew! | close +endfunc + +func Test_cindent_9() + new + setl cindent ts=4 sw=4 + + let code =<< trim [CODE] + + void f() + { + if ( k() ) { + l(); + + } else { /* Start (two words) end */ + m(); + } + + n(); + } + [CODE] + + call append(0, code) + normal gg + normal ]]=][ + + let expected =<< trim [CODE] + + void f() + { + if ( k() ) { + l(); + + } else { /* Start (two words) end */ + m(); + } + + n(); + } + + [CODE] + + call assert_equal(expected, getline(1, '$')) + enew! | close +endfunc + +func Test_cindent_10() + new + setl cindent ts=4 sw=4 + setl cino={s,e-s + + let code =<< trim [CODE] + + void f() + { + if ( k() ) + { + l(); + } else { /* Start (two words) end */ + m(); + } + n(); /* should be under the if () */ + } + [CODE] + + call append(0, code) + normal gg + normal ]]=][ + + let expected =<< trim [CODE] + + void f() + { + if ( k() ) + { + l(); + } else { /* Start (two words) end */ + m(); + } + n(); /* should be under the if () */ + } + + [CODE] + + call assert_equal(expected, getline(1, '$')) + enew! | close +endfunc + +func Test_cindent_11() + new + setl cindent ts=4 sw=4 + setl cino={s,fs + + let code =<< trim [CODE] + void bar(void) + { + static array[2][2] = + { + { 1, 2 }, + { 3, 4 }, + } + + while (a) + { + foo(&a); + } + + { + int a; + { + a = a + 1; + } + } + b = a; + } + + void func(void) + { + a = 1; + { + b = 2; + } + c = 3; + d = 4; + } + /* foo */ + [CODE] + + call append(0, code) + normal gg + exe "normal ]]=/ foo\<CR>" + + let expected =<< trim [CODE] + void bar(void) + { + static array[2][2] = + { + { 1, 2 }, + { 3, 4 }, + } + + while (a) + { + foo(&a); + } + + { + int a; + { + a = a + 1; + } + } + b = a; + } + + void func(void) + { + a = 1; + { + b = 2; + } + c = 3; + d = 4; + } + /* foo */ + + [CODE] + + call assert_equal(expected, getline(1, '$')) + enew! | close +endfunc + +func Test_cindent_12() + new + setl cindent ts=4 sw=4 + setl cino= + + let code =<< trim [CODE] + a() + { + do { + a = a + + a; + } while ( a ); /* add text under this line */ + if ( a ) + a; + } + [CODE] + + call append(0, code) + normal gg + call search('while') + normal ohere + + let expected =<< trim [CODE] + a() + { + do { + a = a + + a; + } while ( a ); /* add text under this line */ + here + if ( a ) + a; + } + + [CODE] + + call assert_equal(expected, getline(1, '$')) + enew! | close +endfunc + +func Test_cindent_13() + new + setl cindent ts=4 sw=4 + setl cino= com= + + let code =<< trim [CODE] + a() + { + label1: + /* hmm */ + // comment + } + [CODE] + + call append(0, code) + normal gg + call search('comment') + exe "normal olabel2: b();\rlabel3 /* post */:\r/* pre */ label4:\r" . + \ "f(/*com*/);\rif (/*com*/)\rcmd();" + + let expected =<< trim [CODE] + a() + { + label1: + /* hmm */ + // comment + label2: b(); + label3 /* post */: + /* pre */ label4: + f(/*com*/); + if (/*com*/) + cmd(); + } + + [CODE] + + call assert_equal(expected, getline(1, '$')) + enew! | close +endfunc + +func Test_cindent_14() + new + setl cindent ts=4 sw=4 + setl comments& comments^=s:/*,m:**,ex:*/ + + let code =<< trim [CODE] + /* + * A simple comment + */ + + /* + ** A different comment + */ + [CODE] + + call append(0, code) + normal gg + call search('simple') + normal =5j + + let expected =<< trim [CODE] + /* + * A simple comment + */ + + /* + ** A different comment + */ + + [CODE] + + call assert_equal(expected, getline(1, '$')) + enew! | close +endfunc + +func Test_cindent_15() + new + setl cindent ts=4 sw=4 + setl cino=c0 + setl comments& comments-=s1:/* comments^=s0:/* + + let code =<< trim [CODE] + void f() + { + + /********* + A comment. + *********/ + } + [CODE] + + call append(0, code) + normal gg + normal ]]=][ + + let expected =<< trim [CODE] + void f() + { + + /********* + A comment. + *********/ + } + + [CODE] + + call assert_equal(expected, getline(1, '$')) + enew! | close +endfunc + +func Test_cindent_16() + new + setl cindent ts=4 sw=4 + setl cino=c0,C1 + setl comments& comments-=s1:/* comments^=s0:/* + + let code =<< trim [CODE] + void f() + { + + /********* + A comment. + *********/ + } + [CODE] + + call append(0, code) + normal gg + normal ]]=][ + + let expected =<< trim [CODE] + void f() + { + + /********* + A comment. + *********/ + } + + [CODE] + + call assert_equal(expected, getline(1, '$')) + enew! | close +endfunc + +func Test_cindent_17() + new + setl cindent ts=4 sw=4 + setl cino= + + let code =<< trim [CODE] + void f() + { + c = c1 && + ( + c2 || + c3 + ) && c4; + } + [CODE] + + call append(0, code) + normal gg + normal ]]=][ + + let expected =<< trim [CODE] + void f() + { + c = c1 && + ( + c2 || + c3 + ) && c4; + } + + [CODE] + + call assert_equal(expected, getline(1, '$')) + enew! | close +endfunc + +func Test_cindent_18() + new + setl cindent ts=4 sw=4 + setl cino=(s + + let code =<< trim [CODE] + void f() + { + c = c1 && + ( + c2 || + c3 + ) && c4; + } + [CODE] + + call append(0, code) + normal gg + normal ]]=][ + + let expected =<< trim [CODE] + void f() + { + c = c1 && + ( + c2 || + c3 + ) && c4; + } + + [CODE] + + call assert_equal(expected, getline(1, '$')) + enew! | close +endfunc + +func Test_cindent_19() + new + setl cindent ts=4 sw=4 + set cino=(s,U1 + + let code =<< trim [CODE] + void f() + { + c = c1 && + ( + c2 || + c3 + ) && c4; + } + [CODE] + + call append(0, code) + normal gg + normal ]]=][ + + let expected =<< trim [CODE] + void f() + { + c = c1 && + ( + c2 || + c3 + ) && c4; + } + + [CODE] + + call assert_equal(expected, getline(1, '$')) + enew! | close +endfunc + +func Test_cindent_20() + new + setl cindent ts=4 sw=4 + setl cino=(0 + + let code =<< trim [CODE] + void f() + { + if ( c1 + && ( c2 + || c3)) + foo; + } + [CODE] + + call append(0, code) + normal gg + normal ]]=][ + + let expected =<< trim [CODE] + void f() + { + if ( c1 + && ( c2 + || c3)) + foo; + } + + [CODE] + + call assert_equal(expected, getline(1, '$')) + enew! | close +endfunc + +func Test_cindent_21() + new + setl cindent ts=4 sw=4 + setl cino=(0,w1 + + let code =<< trim [CODE] + void f() + { + if ( c1 + && ( c2 + || c3)) + foo; + } + [CODE] + + call append(0, code) + normal gg + normal ]]=][ + + let expected =<< trim [CODE] + void f() + { + if ( c1 + && ( c2 + || c3)) + foo; + } + + [CODE] + + call assert_equal(expected, getline(1, '$')) + enew! | close +endfunc + +func Test_cindent_22() + new + setl cindent ts=4 sw=4 + setl cino=(s + + let code =<< trim [CODE] + void f() + { + c = c1 && ( + c2 || + c3 + ) && c4; + if ( + c1 && c2 + ) + foo; + } + [CODE] + + call append(0, code) + normal gg + normal ]]=][ + + let expected =<< trim [CODE] + void f() + { + c = c1 && ( + c2 || + c3 + ) && c4; + if ( + c1 && c2 + ) + foo; + } + + [CODE] + + call assert_equal(expected, getline(1, '$')) + enew! | close +endfunc + +func Test_cindent_23() + new + setl cindent ts=4 sw=4 + setl cino=(s,m1 + + let code =<< trim [CODE] + void f() + { + c = c1 && ( + c2 || + c3 + ) && c4; + if ( + c1 && c2 + ) + foo; + } + [CODE] + + call append(0, code) + normal gg + normal ]]=][ + + let expected =<< trim [CODE] + void f() + { + c = c1 && ( + c2 || + c3 + ) && c4; + if ( + c1 && c2 + ) + foo; + } + + [CODE] + + call assert_equal(expected, getline(1, '$')) + enew! | close +endfunc + +func Test_cindent_24() + new + setl cindent ts=4 sw=4 + setl cino=b1 + + let code =<< trim [CODE] + void f() + { + switch (x) + { + case 1: + a = b; + break; + default: + a = 0; + break; + } + } + [CODE] + + call append(0, code) + normal gg + normal ]]=][ + + let expected =<< trim [CODE] + void f() + { + switch (x) + { + case 1: + a = b; + break; + default: + a = 0; + break; + } + } + + [CODE] + + call assert_equal(expected, getline(1, '$')) + enew! | close +endfunc + +func Test_cindent_25() + new + setl cindent ts=4 sw=4 + setl cino=(0,W5 + + let code =<< trim [CODE] + void f() + { + invokeme( + argu, + ment); + invokeme( + argu, + ment + ); + invokeme(argu, + ment + ); + } + [CODE] + + call append(0, code) + normal gg + normal ]]=][ + + let expected =<< trim [CODE] + void f() + { + invokeme( + argu, + ment); + invokeme( + argu, + ment + ); + invokeme(argu, + ment + ); + } + + [CODE] + + call assert_equal(expected, getline(1, '$')) + enew! | close +endfunc + +func Test_cindent_26() + new + setl cindent ts=4 sw=4 + setl cino=/6 + + let code =<< trim [CODE] + void f() + { + statement; + // comment 1 + // comment 2 + } + [CODE] + + call append(0, code) + normal gg + normal ]]=][ + + let expected =<< trim [CODE] + void f() + { + statement; + // comment 1 + // comment 2 + } + + [CODE] + + call assert_equal(expected, getline(1, '$')) + enew! | close +endfunc + +func Test_cindent_27() + new + setl cindent ts=4 sw=4 + setl cino= + + let code =<< trim [CODE] + void f() + { + statement; + // comment 1 + // comment 2 + } + [CODE] + + call append(0, code) + normal gg + exe "normal ]]/comment 1/+1\<CR>==" + + let expected =<< trim [CODE] + void f() + { + statement; + // comment 1 + // comment 2 + } + + [CODE] + + call assert_equal(expected, getline(1, '$')) + enew! | close +endfunc + +func Test_cindent_28() + new + setl cindent ts=4 sw=4 + setl cino=g0 + + let code =<< trim [CODE] + class CAbc + { + int Test() { return FALSE; } + + public: // comment + void testfall(); + protected: + void testfall(); + }; + [CODE] + + call append(0, code) + normal gg + normal ]]=][ + + let expected =<< trim [CODE] + class CAbc + { + int Test() { return FALSE; } + + public: // comment + void testfall(); + protected: + void testfall(); + }; + + [CODE] + + call assert_equal(expected, getline(1, '$')) + enew! | close +endfunc + +func Test_cindent_29() + new + setl cindent ts=4 sw=4 + setl cino=(0,gs,hs + + let code =<< trim [CODE] + class Foo : public Bar + { + public: + virtual void method1(void) = 0; + virtual void method2(int arg1, + int arg2, + int arg3) = 0; + }; + [CODE] + + call append(0, code) + normal gg + normal ]]=][ + + let expected =<< trim [CODE] + class Foo : public Bar + { + public: + virtual void method1(void) = 0; + virtual void method2(int arg1, + int arg2, + int arg3) = 0; + }; + + [CODE] + + call assert_equal(expected, getline(1, '$')) + enew! | close +endfunc + +func Test_cindent_30() + new + setl cindent ts=4 sw=4 + setl cino=+20 + + let code =<< [CODE] + void +foo() +{ + if (a) + { + } else + asdf; +} +[CODE] + + call append(0, code) + normal gg + normal ]]=][ + + let expected =<< [CODE] + void +foo() +{ + if (a) + { + } else + asdf; +} + +[CODE] + + call assert_equal(expected, getline(1, '$')) + enew! | close +endfunc + +func Test_cindent_31() + new + setl cindent ts=4 sw=4 + setl cino=(0,W2s + + let code =<< trim [CODE] + + { + averylongfunctionnamelongfunctionnameaverylongfunctionname()->asd( + asdasdf, + func(asdf, + asdfadsf), + asdfasdf + ); + + /* those are ugly, but consequent */ + + func()->asd(asdasdf, + averylongfunctionname( + abc, + dec)->averylongfunctionname( + asdfadsf, + asdfasdf, + asdfasdf, + ), + func(asdfadf, + asdfasdf + ), + asdasdf + ); + + averylongfunctionnameaverylongfunctionnameavery()->asd(fasdf( + abc, + dec)->asdfasdfasdf( + asdfadsf, + asdfasdf, + asdfasdf, + ), + func(asdfadf, + asdfasdf), + asdasdf + ); + } + [CODE] + + call append(0, code) + normal gg + normal ]]=][ + + let expected =<< trim [CODE] + + { + averylongfunctionnamelongfunctionnameaverylongfunctionname()->asd( + asdasdf, + func(asdf, + asdfadsf), + asdfasdf + ); + + /* those are ugly, but consequent */ + + func()->asd(asdasdf, + averylongfunctionname( + abc, + dec)->averylongfunctionname( + asdfadsf, + asdfasdf, + asdfasdf, + ), + func(asdfadf, + asdfasdf + ), + asdasdf + ); + + averylongfunctionnameaverylongfunctionnameavery()->asd(fasdf( + abc, + dec)->asdfasdfasdf( + asdfadsf, + asdfasdf, + asdfasdf, + ), + func(asdfadf, + asdfasdf), + asdasdf + ); + } + + [CODE] + + call assert_equal(expected, getline(1, '$')) + enew! | close +endfunc + +func Test_cindent_32() + new + setl cindent ts=4 sw=4 + setl cino=M1 + + let code =<< trim [CODE] + int main () + { + if (cond1 && + cond2 + ) + foo; + } + [CODE] + + call append(0, code) + normal gg + normal ]]=][ + + let expected =<< trim [CODE] + int main () + { + if (cond1 && + cond2 + ) + foo; + } + + [CODE] + + call assert_equal(expected, getline(1, '$')) + enew! | close +endfunc + +func Test_cindent_33() + new + setl cindent ts=4 sw=4 + setl cino=(0,ts + + let code =<< trim [CODE] + void func(int a + #if defined(FOO) + , int b + , int c + #endif + ) + { + } + [CODE] + + call append(0, code) + normal gg + normal 2j=][ + + let expected =<< trim [CODE] + void func(int a + #if defined(FOO) + , int b + , int c + #endif + ) + { + } + + [CODE] + + call assert_equal(expected, getline(1, '$')) + enew! | close +endfunc + +func Test_cindent_34() + new + setl cindent ts=4 sw=4 + setl cino=(0 + + let code =<< trim [CODE] + + void + func(int a + #if defined(FOO) + , int b + , int c + #endif + ) + { + } + [CODE] + + call append(0, code) + normal gg + normal =][ + + let expected =<< trim [CODE] + + void + func(int a + #if defined(FOO) + , int b + , int c + #endif + ) + { + } + + [CODE] + + call assert_equal(expected, getline(1, '$')) + enew! | close +endfunc + +func Test_cindent_35() + new + setl cindent ts=4 sw=4 + setl cino& + + let code =<< trim [CODE] + void func(void) + { + if(x==y) + if(y==z) + foo=1; + else { bar=1; + baz=2; + } + printf("Foo!\n"); + } + + void func1(void) + { + char* tab[] = {"foo", "bar", + "baz", "quux", + "this line used", "to be indented incorrectly"}; + foo(); + } + + void func2(void) + { + int tab[] = + {1, 2, + 3, 4, + 5, 6}; + + printf("This line used to be indented incorrectly.\n"); + } + + int foo[] + #ifdef BAR + + = { 1, 2, 3, + 4, 5, 6 } + + #endif + ; + int baz; + + void func3(void) + { + int tab[] = { + 1, 2, + 3, 4, + 5, 6}; + + printf("Don't you dare indent this line incorrectly!\n"); + } + + void + func4(a, b, + c) + int a; + int b; + int c; + { + } + + void + func5( + int a, + int b) + { + } + + void + func6( + int a) + { + } + [CODE] + + call append(0, code) + normal gg + normal ]]=7][ + + let expected =<< trim [CODE] + void func(void) + { + if(x==y) + if(y==z) + foo=1; + else { bar=1; + baz=2; + } + printf("Foo!\n"); + } + + void func1(void) + { + char* tab[] = {"foo", "bar", + "baz", "quux", + "this line used", "to be indented incorrectly"}; + foo(); + } + + void func2(void) + { + int tab[] = + {1, 2, + 3, 4, + 5, 6}; + + printf("This line used to be indented incorrectly.\n"); + } + + int foo[] + #ifdef BAR + + = { 1, 2, 3, + 4, 5, 6 } + + #endif + ; + int baz; + + void func3(void) + { + int tab[] = { + 1, 2, + 3, 4, + 5, 6}; + + printf("Don't you dare indent this line incorrectly!\n"); + } + + void + func4(a, b, + c) + int a; + int b; + int c; + { + } + + void + func5( + int a, + int b) + { + } + + void + func6( + int a) + { + } + + [CODE] + + call assert_equal(expected, getline(1, '$')) + enew! | close +endfunc + +func Test_cindent_36() + new + setl cindent ts=4 sw=4 + setl cino& + setl cino+=l1 + + let code =<< trim [CODE] + void func(void) + { + int tab[] = + { + 1, 2, 3, + 4, 5, 6}; + + printf("Indent this line correctly!\n"); + + switch (foo) + { + case bar: + printf("bar"); + break; + case baz: { + printf("baz"); + break; + } + case quux: + printf("But don't break the indentation of this instruction\n"); + break; + } + } + [CODE] + + call append(0, code) + normal gg + normal ]]=][ + + let expected =<< trim [CODE] + void func(void) + { + int tab[] = + { + 1, 2, 3, + 4, 5, 6}; + + printf("Indent this line correctly!\n"); + + switch (foo) + { + case bar: + printf("bar"); + break; + case baz: { + printf("baz"); + break; + } + case quux: + printf("But don't break the indentation of this instruction\n"); + break; + } + } + + [CODE] + + call assert_equal(expected, getline(1, '$')) + enew! | close +endfunc + +func Test_cindent_37() + new + setl cindent ts=4 sw=4 + setl cino& + + let code =<< trim [CODE] + void func(void) + { + cout << "a" + << "b" + << ") :" + << "c"; + } + [CODE] + + call append(0, code) + normal gg + normal ]]=][ + + let expected =<< trim [CODE] + void func(void) + { + cout << "a" + << "b" + << ") :" + << "c"; + } + + [CODE] + + call assert_equal(expected, getline(1, '$')) + enew! | close +endfunc + +func Test_cindent_38() + new + setl cindent ts=4 sw=4 + setl com=s1:/*,m:*,ex:*/ + + let code =<< trim [CODE] + void func(void) + { + /* + * This is a comment. + */ + } + [CODE] + + call append(0, code) + normal gg + normal ]]3jofoo(); + + let expected =<< trim [CODE] + void func(void) + { + /* + * This is a comment. + */ + foo(); + } + + [CODE] + + call assert_equal(expected, getline(1, '$')) + enew! | close +endfunc + +func Test_cindent_39() + new + setl cindent ts=4 sw=4 + setl cino& + + let code =<< trim [CODE] + void func(void) + { + for (int i = 0; i < 10; ++i) + if (i & 1) { + foo(1); + } else + foo(0); + baz(); + } + [CODE] + + call append(0, code) + normal gg + normal ]]=][ + + let expected =<< trim [CODE] + void func(void) + { + for (int i = 0; i < 10; ++i) + if (i & 1) { + foo(1); + } else + foo(0); + baz(); + } + + [CODE] + + call assert_equal(expected, getline(1, '$')) + enew! | close +endfunc + +func Test_cindent_40() + new + setl cindent ts=4 sw=4 + setl cino=k2s,(0 + + let code =<< trim [CODE] + void func(void) + { + if (condition1 + && condition2) + action(); + function(argument1 + && argument2); + + if (c1 && (c2 || + c3)) + foo; + if (c1 && + (c2 || c3)) + { + } + + if ( c1 + && ( c2 + || c3)) + foo; + func( c1 + && ( c2 + || c3)) + foo; + } + [CODE] + + call append(0, code) + normal gg + normal ]]=][ + + let expected =<< trim [CODE] + void func(void) + { + if (condition1 + && condition2) + action(); + function(argument1 + && argument2); + + if (c1 && (c2 || + c3)) + foo; + if (c1 && + (c2 || c3)) + { + } + + if ( c1 + && ( c2 + || c3)) + foo; + func( c1 + && ( c2 + || c3)) + foo; + } + + [CODE] + + call assert_equal(expected, getline(1, '$')) + enew! | close +endfunc + +func Test_cindent_41() + new + setl cindent ts=4 sw=4 + setl cino=k2s,(s + + let code =<< trim [CODE] + void func(void) + { + if (condition1 + && condition2) + action(); + function(argument1 + && argument2); + + if (c1 && (c2 || + c3)) + foo; + if (c1 && + (c2 || c3)) + { + } + + if ( c1 + && ( c2 + || c3)) + foo; + func( c1 + && ( c2 + || c3)) + foo; + } + [CODE] + + call append(0, code) + normal gg + normal ]]=][ + + let expected =<< trim [CODE] + void func(void) + { + if (condition1 + && condition2) + action(); + function(argument1 + && argument2); + + if (c1 && (c2 || + c3)) + foo; + if (c1 && + (c2 || c3)) + { + } + + if ( c1 + && ( c2 + || c3)) + foo; + func( c1 + && ( c2 + || c3)) + foo; + } + + [CODE] + + call assert_equal(expected, getline(1, '$')) + enew! | close +endfunc + +func Test_cindent_42() + new + setl cindent ts=4 sw=4 + setl cino=k2s,(s,U1 + + let code =<< trim [CODE] + void func(void) + { + if (condition1 + && condition2) + action(); + function(argument1 + && argument2); + + if (c1 && (c2 || + c3)) + foo; + if (c1 && + (c2 || c3)) + { + } + if (c123456789 + && (c22345 + || c3)) + printf("foo\n"); + + c = c1 && + ( + c2 || + c3 + ) && c4; + } + [CODE] + + call append(0, code) + normal gg + normal ]]=][ + + let expected =<< trim [CODE] + void func(void) + { + if (condition1 + && condition2) + action(); + function(argument1 + && argument2); + + if (c1 && (c2 || + c3)) + foo; + if (c1 && + (c2 || c3)) + { + } + if (c123456789 + && (c22345 + || c3)) + printf("foo\n"); + + c = c1 && + ( + c2 || + c3 + ) && c4; + } + + [CODE] + + call assert_equal(expected, getline(1, '$')) + enew! | close +endfunc + +func Test_cindent_43() + new + setl cindent ts=4 sw=4 + setl cino=k2s,(0,W4 + + let code =<< trim [CODE] + void func(void) + { + if (condition1 + && condition2) + action(); + function(argument1 + && argument2); + + if (c1 && (c2 || + c3)) + foo; + if (c1 && + (c2 || c3)) + { + } + if (c123456789 + && (c22345 + || c3)) + printf("foo\n"); + + if ( c1 + && ( c2 + || c3)) + foo; + + a_long_line( + argument, + argument); + a_short_line(argument, + argument); + } + [CODE] + + call append(0, code) + normal gg + normal ]]=][ + + let expected =<< trim [CODE] + void func(void) + { + if (condition1 + && condition2) + action(); + function(argument1 + && argument2); + + if (c1 && (c2 || + c3)) + foo; + if (c1 && + (c2 || c3)) + { + } + if (c123456789 + && (c22345 + || c3)) + printf("foo\n"); + + if ( c1 + && ( c2 + || c3)) + foo; + + a_long_line( + argument, + argument); + a_short_line(argument, + argument); + } + + [CODE] + + call assert_equal(expected, getline(1, '$')) + enew! | close +endfunc + +func Test_cindent_44() + new + setl cindent ts=4 sw=4 + setl cino=k2s,u2 + + let code =<< trim [CODE] + void func(void) + { + if (condition1 + && condition2) + action(); + function(argument1 + && argument2); + + if (c1 && (c2 || + c3)) + foo; + if (c1 && + (c2 || c3)) + { + } + if (c123456789 + && (c22345 + || c3)) + printf("foo\n"); + } + [CODE] + + call append(0, code) + normal gg + normal ]]=][ + + let expected =<< trim [CODE] + void func(void) + { + if (condition1 + && condition2) + action(); + function(argument1 + && argument2); + + if (c1 && (c2 || + c3)) + foo; + if (c1 && + (c2 || c3)) + { + } + if (c123456789 + && (c22345 + || c3)) + printf("foo\n"); + } + + [CODE] + + call assert_equal(expected, getline(1, '$')) + enew! | close +endfunc + +func Test_cindent_45() + new + setl cindent ts=4 sw=4 + setl cino=k2s,(0,w1 + + let code =<< trim [CODE] + void func(void) + { + if (condition1 + && condition2) + action(); + function(argument1 + && argument2); + + if (c1 && (c2 || + c3)) + foo; + if (c1 && + (c2 || c3)) + { + } + if (c123456789 + && (c22345 + || c3)) + printf("foo\n"); + + if ( c1 + && ( c2 + || c3)) + foo; + func( c1 + && ( c2 + || c3)) + foo; + } + [CODE] + + call append(0, code) + normal gg + normal ]]=][ + + let expected =<< trim [CODE] + void func(void) + { + if (condition1 + && condition2) + action(); + function(argument1 + && argument2); + + if (c1 && (c2 || + c3)) + foo; + if (c1 && + (c2 || c3)) + { + } + if (c123456789 + && (c22345 + || c3)) + printf("foo\n"); + + if ( c1 + && ( c2 + || c3)) + foo; + func( c1 + && ( c2 + || c3)) + foo; + } + + [CODE] + + call assert_equal(expected, getline(1, '$')) + enew! | close +endfunc + +func Test_cindent_46() + new + setl cindent ts=4 sw=4 + setl cino=k2,(s + + let code =<< trim [CODE] + void func(void) + { + if (condition1 + && condition2) + action(); + function(argument1 + && argument2); + + if (c1 && (c2 || + c3)) + foo; + if (c1 && + (c2 || c3)) + { + } + } + [CODE] + + call append(0, code) + normal gg + normal ]]=][ + + let expected =<< trim [CODE] + void func(void) + { + if (condition1 + && condition2) + action(); + function(argument1 + && argument2); + + if (c1 && (c2 || + c3)) + foo; + if (c1 && + (c2 || c3)) + { + } + } + + [CODE] + + call assert_equal(expected, getline(1, '$')) + enew! | close +endfunc + +func Test_cindent_47() + new + setl cindent ts=4 sw=4 + setl cino=N-s + + let code =<< trim [CODE] + NAMESPACESTART + /* valid namespaces with normal indent */ + namespace + { + { + 111111111111; + } + } + namespace /* test */ + { + 11111111111111111; + } + namespace // test + { + 111111111111111111; + } + namespace + { + 111111111111111111; + } + namespace test + { + 111111111111111111; + } + namespace test::cpp17 + { + 111111111111111111; + } + namespace ::incorrectcpp17 + { + 111111111111111111; + } + namespace test::incorrectcpp17:: + { + 111111111111111111; + } + namespace test:incorrectcpp17 + { + 111111111111111111; + } + namespace test:::incorrectcpp17 + { + 111111111111111111; + } + namespace{ + 111111111111111111; + } + namespace test{ + 111111111111111111; + } + namespace { + 111111111111111111; + } + namespace test { + 111111111111111111; + namespace test2 { + 22222222222222222; + } + } + inline namespace { + 111111111111111111; + } + inline /* test */ namespace { + 111111111111111111; + } + inline/* test */namespace { + 111111111111111111; + } + + /* invalid namespaces use block indent */ + namespace test test2 { + 111111111111111111111; + } + namespace11111111111 { + 111111111111; + } + namespace() { + 1111111111111; + } + namespace() + { + 111111111111111111; + } + namespace test test2 + { + 1111111111111111111; + } + namespace111111111 + { + 111111111111111111; + } + inlinenamespace { + 111111111111111111; + } + NAMESPACEEND + [CODE] + + call append(0, code) + normal gg + call search('^NAMESPACESTART') + exe "normal =/^NAMESPACEEND\n" + + let expected =<< trim [CODE] + NAMESPACESTART + /* valid namespaces with normal indent */ + namespace + { + { + 111111111111; + } + } + namespace /* test */ + { + 11111111111111111; + } + namespace // test + { + 111111111111111111; + } + namespace + { + 111111111111111111; + } + namespace test + { + 111111111111111111; + } + namespace test::cpp17 + { + 111111111111111111; + } + namespace ::incorrectcpp17 + { + 111111111111111111; + } + namespace test::incorrectcpp17:: + { + 111111111111111111; + } + namespace test:incorrectcpp17 + { + 111111111111111111; + } + namespace test:::incorrectcpp17 + { + 111111111111111111; + } + namespace{ + 111111111111111111; + } + namespace test{ + 111111111111111111; + } + namespace { + 111111111111111111; + } + namespace test { + 111111111111111111; + namespace test2 { + 22222222222222222; + } + } + inline namespace { + 111111111111111111; + } + inline /* test */ namespace { + 111111111111111111; + } + inline/* test */namespace { + 111111111111111111; + } + + /* invalid namespaces use block indent */ + namespace test test2 { + 111111111111111111111; + } + namespace11111111111 { + 111111111111; + } + namespace() { + 1111111111111; + } + namespace() + { + 111111111111111111; + } + namespace test test2 + { + 1111111111111111111; + } + namespace111111111 + { + 111111111111111111; + } + inlinenamespace { + 111111111111111111; + } + NAMESPACEEND + + [CODE] + + call assert_equal(expected, getline(1, '$')) + enew! | close +endfunc + +func Test_cindent_48() + new + setl cindent ts=4 sw=4 + setl cino=j1,J1 + + let code =<< trim [CODE] + JSSTART + var bar = { + foo: { + that: this, + some: ok, + }, + "bar":{ + a : 2, + b: "123abc", + x: 4, + "y": 5 + } + } + JSEND + [CODE] + + call append(0, code) + normal gg + call search('^JSSTART') + exe "normal =/^JSEND\n" + + let expected =<< trim [CODE] + JSSTART + var bar = { + foo: { + that: this, + some: ok, + }, + "bar":{ + a : 2, + b: "123abc", + x: 4, + "y": 5 + } + } + JSEND + + [CODE] + + call assert_equal(expected, getline(1, '$')) + enew! | close +endfunc + +func Test_cindent_49() + new + setl cindent ts=4 sw=4 + setl cino=j1,J1 + + let code =<< trim [CODE] + JSSTART + var foo = [ + 1, + 2, + 3 + ]; + JSEND + [CODE] + + call append(0, code) + normal gg + call search('^JSSTART') + exe "normal =/^JSEND\n" + + let expected =<< trim [CODE] + JSSTART + var foo = [ + 1, + 2, + 3 + ]; + JSEND + + [CODE] + + call assert_equal(expected, getline(1, '$')) + enew! | close +endfunc + +func Test_cindent_50() + new + setl cindent ts=4 sw=4 + setl cino=j1,J1 + + let code =<< trim [CODE] + JSSTART + function bar() { + var foo = [ + 1, + 2, + 3 + ]; + } + JSEND + [CODE] + + call append(0, code) + normal gg + call search('^JSSTART') + exe "normal =/^JSEND\n" + + let expected =<< trim [CODE] + JSSTART + function bar() { + var foo = [ + 1, + 2, + 3 + ]; + } + JSEND + + [CODE] + + call assert_equal(expected, getline(1, '$')) + enew! | close +endfunc + +func Test_cindent_51() + new + setl cindent ts=4 sw=4 + setl cino=j1,J1 + + let code =<< trim [CODE] + JSSTART + (function($){ + + if (cond && + cond) { + stmt; + } + window.something.left = + (width - 50 + offset) + "px"; + var class_name='myclass'; + + function private_method() { + } + + var public_method={ + method: function(options,args){ + private_method(); + } + } + + function init(options) { + + $(this).data(class_name+'_public',$.extend({},{ + foo: 'bar', + bar: 2, + foobar: [ + 1, + 2, + 3 + ], + callback: function(){ + return true; + } + }, options||{})); + } + + $.fn[class_name]=function() { + + var _arguments=arguments; + return this.each(function(){ + + var options=$(this).data(class_name+'_public'); + if (!options) { + init.apply(this,_arguments); + + } else { + var method=public_method[_arguments[0]]; + + if (typeof(method)!='function') { + console.log(class_name+' has no method "'+_arguments[0]+'"'); + return false; + } + _arguments[0]=options; + method.apply(this,_arguments); + } + }); + } + + })(jQuery); + JSEND + [CODE] + + call append(0, code) + normal gg + call search('^JSSTART') + exe "normal =/^JSEND\n" + + let expected =<< trim [CODE] + JSSTART + (function($){ + + if (cond && + cond) { + stmt; + } + window.something.left = + (width - 50 + offset) + "px"; + var class_name='myclass'; + + function private_method() { + } + + var public_method={ + method: function(options,args){ + private_method(); + } + } + + function init(options) { + + $(this).data(class_name+'_public',$.extend({},{ + foo: 'bar', + bar: 2, + foobar: [ + 1, + 2, + 3 + ], + callback: function(){ + return true; + } + }, options||{})); + } + + $.fn[class_name]=function() { + + var _arguments=arguments; + return this.each(function(){ + + var options=$(this).data(class_name+'_public'); + if (!options) { + init.apply(this,_arguments); + + } else { + var method=public_method[_arguments[0]]; + + if (typeof(method)!='function') { + console.log(class_name+' has no method "'+_arguments[0]+'"'); + return false; + } + _arguments[0]=options; + method.apply(this,_arguments); + } + }); + } + + })(jQuery); + JSEND + + [CODE] + + call assert_equal(expected, getline(1, '$')) + enew! | close +endfunc + +func Test_cindent_52() + new + setl cindent ts=4 sw=4 + setl cino=j1,J1 + + let code =<< trim [CODE] + JSSTART + function init(options) { + $(this).data(class_name+'_public',$.extend({},{ + foo: 'bar', + bar: 2, + foobar: [ + 1, + 2, + 3 + ], + callback: function(){ + return true; + } + }, options||{})); + } + JSEND + [CODE] + + call append(0, code) + normal gg + call search('^JSSTART') + exe "normal =/^JSEND\n" + + let expected =<< trim [CODE] + JSSTART + function init(options) { + $(this).data(class_name+'_public',$.extend({},{ + foo: 'bar', + bar: 2, + foobar: [ + 1, + 2, + 3 + ], + callback: function(){ + return true; + } + }, options||{})); + } + JSEND + + [CODE] + + call assert_equal(expected, getline(1, '$')) + enew! | close +endfunc + +func Test_cindent_53() + new + setl cindent ts=4 sw=4 + setl cino=j1,J1 + + let code =<< trim [CODE] + JSSTART + (function($){ + function init(options) { + $(this).data(class_name+'_public',$.extend({},{ + foo: 'bar', + bar: 2, + foobar: [ + 1, + 2, + 3 + ], + callback: function(){ + return true; + } + }, options||{})); + } + })(jQuery); + JSEND + [CODE] + + call append(0, code) + normal gg + call search('^JSSTART') + exe "normal =/^JSEND\n" + + let expected =<< trim [CODE] + JSSTART + (function($){ + function init(options) { + $(this).data(class_name+'_public',$.extend({},{ + foo: 'bar', + bar: 2, + foobar: [ + 1, + 2, + 3 + ], + callback: function(){ + return true; + } + }, options||{})); + } + })(jQuery); + JSEND + + [CODE] + + call assert_equal(expected, getline(1, '$')) + enew! | close +endfunc + +func Test_cindent_54() + new + setl cindent ts=4 sw=4 + setl cino=j1,J1,+2 + + let code =<< trim [CODE] + JSSTART + // Results of JavaScript indent + // 1 + (function(){ + var a = [ + 'a', + 'b', + 'c', + 'd', + 'e', + 'f', + 'g', + 'h', + 'i' + ]; + }()) + + // 2 + (function(){ + var a = [ + 0 + + 5 * + 9 * + 'a', + 'b', + 0 + + 5 * + 9 * + 'c', + 'd', + 'e', + 'f', + 'g', + 'h', + 'i' + ]; + }()) + + // 3 + (function(){ + var a = [ + 0 + + // comment 1 + 5 * + /* comment 2 */ + 9 * + 'a', + 'b', + 0 + + 5 * + 9 * + 'c', + 'd', + 'e', + 'f', + 'g', + 'h', + 'i' + ]; + }()) + + // 4 + { + var a = [ + 0, + 1 + ]; + var b; + var c; + } + + // 5 + { + var a = [ + [ + 0 + ], + 2, + 3 + ]; + } + + // 6 + { + var a = [ + [ + 0, + 1 + ], + 2, + 3 + ]; + } + + // 7 + { + var a = [ + // [ + 0, + // 1 + // ], + 2, + 3 + ]; + } + + // 8 + var x = [ + (function(){ + var a, + b, + c, + d, + e, + f, + g, + h, + i; + }) + ]; + + // 9 + var a = [ + 0 + + 5 * + 9 * + 'a', + 'b', + 0 + + 5 * + 9 * + 'c', + 'd', + 'e', + 'f', + 'g', + 'h', + 'i' + ]; + + // 10 + var a, + b, + c, + d, + e, + f, + g, + h, + i; + JSEND + [CODE] + + call append(0, code) + normal gg + call search('^JSSTART') + exe "normal =/^JSEND\n" + + let expected =<< trim [CODE] + JSSTART + // Results of JavaScript indent + // 1 + (function(){ + var a = [ + 'a', + 'b', + 'c', + 'd', + 'e', + 'f', + 'g', + 'h', + 'i' + ]; + }()) + + // 2 + (function(){ + var a = [ + 0 + + 5 * + 9 * + 'a', + 'b', + 0 + + 5 * + 9 * + 'c', + 'd', + 'e', + 'f', + 'g', + 'h', + 'i' + ]; + }()) + + // 3 + (function(){ + var a = [ + 0 + + // comment 1 + 5 * + /* comment 2 */ + 9 * + 'a', + 'b', + 0 + + 5 * + 9 * + 'c', + 'd', + 'e', + 'f', + 'g', + 'h', + 'i' + ]; + }()) + + // 4 + { + var a = [ + 0, + 1 + ]; + var b; + var c; + } + + // 5 + { + var a = [ + [ + 0 + ], + 2, + 3 + ]; + } + + // 6 + { + var a = [ + [ + 0, + 1 + ], + 2, + 3 + ]; + } + + // 7 + { + var a = [ + // [ + 0, + // 1 + // ], + 2, + 3 + ]; + } + + // 8 + var x = [ + (function(){ + var a, + b, + c, + d, + e, + f, + g, + h, + i; + }) + ]; + + // 9 + var a = [ + 0 + + 5 * + 9 * + 'a', + 'b', + 0 + + 5 * + 9 * + 'c', + 'd', + 'e', + 'f', + 'g', + 'h', + 'i' + ]; + + // 10 + var a, + b, + c, + d, + e, + f, + g, + h, + i; + JSEND + + [CODE] + + call assert_equal(expected, getline(1, '$')) + enew! | close +endfunc + +func Test_cindent_55() + new + setl cindent ts=4 sw=4 + setl cino& + + let code =<< trim [CODE] + /* start of define */ + { + } + #define AAA \ + BBB\ + CCC + + #define CNT \ + 1 + \ + 2 + \ + 4 + /* end of define */ + [CODE] + + call append(0, code) + normal gg + call search('start of define') + exe "normal =/end of define\n" + + let expected =<< trim [CODE] + /* start of define */ + { + } + #define AAA \ + BBB\ + CCC + + #define CNT \ + 1 + \ + 2 + \ + 4 + /* end of define */ + + [CODE] + + call assert_equal(expected, getline(1, '$')) + enew! | close +endfunc + +func Test_cindent_56() + new + setl cindent ts=4 sw=4 + setl cino& + + let code =<< trim [CODE] + { + a = second/*bug*/*line; + } + [CODE] + + call append(0, code) + normal gg + call search('a = second') + normal ox + + let expected =<< trim [CODE] + { + a = second/*bug*/*line; + x + } + + [CODE] + + call assert_equal(expected, getline(1, '$')) + enew! | close +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 @@ -173,4 +5347,23 @@ func Test_cindent_pragma() enew! | close endfunc +func Test_backslash_at_end_of_line() + new + exe "norm v>O'\\\<C-m>-" + exe "norm \<C-q>=" + bwipe! +endfunc + +func Test_find_brace_backwards() + " this was looking beyond the end of the line + new + norm R/* + norm o0{ + norm o// + norm V{= + call assert_equal(['/*', ' 0{', '//'], getline(1, 3)) + bwipe! +endfunc + + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_cmdline.vim b/src/nvim/testdir/test_cmdline.vim index 1672b0e840..ff4cbe544c 100644 --- a/src/nvim/testdir/test_cmdline.vim +++ b/src/nvim/testdir/test_cmdline.vim @@ -500,8 +500,16 @@ func Test_fullcommand() for [in, want] in items(tests) call assert_equal(want, fullcommand(in)) endfor + call assert_equal('', fullcommand(v:_null_string)) call assert_equal('syntax', 'syn'->fullcommand()) + + command -buffer BufferLocalCommand : + command GlobalCommand : + call assert_equal('GlobalCommand', fullcommand('GlobalCom')) + call assert_equal('BufferLocalCommand', fullcommand('BufferL')) + delcommand BufferLocalCommand + delcommand GlobalCommand endfunc func Test_shellcmd_completion() diff --git a/src/nvim/testdir/test_compiler.vim b/src/nvim/testdir/test_compiler.vim index aaa2301bca..c0c572ce65 100644 --- a/src/nvim/testdir/test_compiler.vim +++ b/src/nvim/testdir/test_compiler.vim @@ -19,6 +19,9 @@ func Test_compiler() call assert_equal('perl', b:current_compiler) call assert_fails('let g:current_compiler', 'E121:') + let verbose_efm = execute('verbose set efm') + call assert_match('Last set from .*[/\\]compiler[/\\]perl.vim ', verbose_efm) + call setline(1, ['#!/usr/bin/perl -w', 'use strict;', 'my $foo=1']) w! call feedkeys(":make\<CR>\<CR>", 'tx') diff --git a/src/nvim/testdir/test_conceal.vim b/src/nvim/testdir/test_conceal.vim index 1306dbe5cf..bffc2f49d3 100644 --- a/src/nvim/testdir/test_conceal.vim +++ b/src/nvim/testdir/test_conceal.vim @@ -4,10 +4,10 @@ source check.vim CheckFeature conceal source screendump.vim -" CheckScreendump func Test_conceal_two_windows() CheckScreendump + let code =<< trim [CODE] let lines = ["one one one one one", "two |hidden| here", "three |hidden| three"] call setline(1, lines) @@ -111,6 +111,7 @@ endfunc func Test_conceal_with_cursorline() CheckScreendump + " Opens a help window, where 'conceal' is set, switches to the other window " where 'cursorline' needs to be updated when the cursor moves. let code =<< trim [CODE] @@ -139,6 +140,7 @@ endfunc func Test_conceal_resize_term() CheckScreendump + let code =<< trim [CODE] call setline(1, '`one` `two` `three` `four` `five`, the backticks should be concealed') setl cocu=n cole=3 diff --git a/src/nvim/testdir/test_cscope.vim b/src/nvim/testdir/test_cscope.vim index cc6154af69..faf37485cd 100644 --- a/src/nvim/testdir/test_cscope.vim +++ b/src/nvim/testdir/test_cscope.vim @@ -102,7 +102,7 @@ func Test_cscopeWithCscopeConnections() for cmd in ['cs find f Xmemfile_test.c', 'cs find 7 Xmemfile_test.c'] enew let a = execute(cmd) - call assert_true(a =~ '"Xmemfile_test.c" \d\+L, \d\+C') + call assert_true(a =~ '"Xmemfile_test.c" \d\+L, \d\+B') call assert_equal('Xmemfile_test.c', @%) endfor @@ -112,7 +112,7 @@ func Test_cscopeWithCscopeConnections() let a = execute(cmd) let alines = split(a, '\n', 1) call assert_equal('', alines[0]) - call assert_true(alines[1] =~ '"Xmemfile_test.c" \d\+L, \d\+C') + call assert_true(alines[1] =~ '"Xmemfile_test.c" \d\+L, \d\+B') call assert_equal('(1 of 1): <<global>> #include <assert.h>', alines[2]) call assert_equal('#include <assert.h>', getline('.')) endfor diff --git a/src/nvim/testdir/test_cursor_func.vim b/src/nvim/testdir/test_cursor_func.vim index e8c4a952ee..57825b4551 100644 --- a/src/nvim/testdir/test_cursor_func.vim +++ b/src/nvim/testdir/test_cursor_func.vim @@ -1,4 +1,4 @@ -" Tests for cursor(). +" Tests for cursor() and other functions that get/set the cursor position func Test_wrong_arguments() call assert_fails('call cursor(1. 3)', 'E474:') @@ -24,6 +24,9 @@ func Test_move_cursor() " below last line goes to last line call cursor(9, 1) call assert_equal([4, 1, 0, 1], getcurpos()[1:]) + " pass string arguments + call cursor('3', '3') + call assert_equal([3, 3, 0, 3], getcurpos()[1:]) call setline(1, ["\<TAB>"]) call cursor(1, 1, 1) @@ -72,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"]) @@ -100,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() @@ -119,3 +122,247 @@ func Test_screenpos_number() close 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:') + call assert_equal([0, 0, 0, 0], getcharpos(0)) + new + call setline(1, ['', "01\tà4è678", 'Ⅵ', '012345678']) + + " Test for '.' and '$' + normal 1G + call assert_equal([0, 1, 1, 0], getcharpos('.')) + call assert_equal([0, 4, 1, 0], getcharpos('$')) + normal 2G6l + call assert_equal([0, 2, 7, 0], getcharpos('.')) + normal 3G$ + call assert_equal([0, 3, 1, 0], getcharpos('.')) + normal 4G$ + call assert_equal([0, 4, 9, 0], getcharpos('.')) + + " Test for a mark + normal 2G7lmmgg + call assert_equal([0, 2, 8, 0], getcharpos("'m")) + delmarks m + call assert_equal([0, 0, 0, 0], getcharpos("'m")) + + " Test for the visual start column + vnoremap <expr> <F3> SaveVisualStartCharPos() + let g:VisualStartPos = [] + exe "normal 2G6lv$\<F3>ohh\<F3>o\<F3>" + 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, 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 + +" Test for the setcharpos() function +func Test_setcharpos() + call assert_equal(-1, setcharpos('.', v:_null_list)) + new + call setline(1, ['', "01\tà4è678", 'Ⅵ', '012345678']) + call setcharpos('.', [0, 1, 1, 0]) + call assert_equal([1, 1], [line('.'), col('.')]) + call setcharpos('.', [0, 2, 7, 0]) + call assert_equal([2, 9], [line('.'), col('.')]) + call setcharpos('.', [0, 3, 4, 0]) + call assert_equal([3, 1], [line('.'), col('.')]) + call setcharpos('.', [0, 3, 1, 0]) + call assert_equal([3, 1], [line('.'), col('.')]) + call setcharpos('.', [0, 4, 0, 0]) + call assert_equal([4, 1], [line('.'), col('.')]) + call setcharpos('.', [0, 4, 20, 0]) + call assert_equal([4, 9], [line('.'), col('.')]) + + " Test for mark + delmarks m + 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])) +endfunc + +func SaveVisualStartCharCol() + call add(g:VisualStartCol, charcol('v')) + 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:') + call assert_equal(0, charcol(0)) + new + call setline(1, ['', "01\tà4è678", 'Ⅵ', '012345678']) + + " Test for '.' and '$' + normal 1G + call assert_equal(1, charcol('.')) + call assert_equal(1, charcol('$')) + normal 2G6l + call assert_equal(7, charcol('.')) + call assert_equal(10, charcol('$')) + normal 3G$ + call assert_equal(1, charcol('.')) + call assert_equal(2, charcol('$')) + normal 4G$ + call assert_equal(9, charcol('.')) + call assert_equal(10, charcol('$')) + + " Test for [lnum, '$'] + call assert_equal(1, charcol([1, '$'])) + call assert_equal(10, charcol([2, '$'])) + call assert_equal(2, charcol([3, '$'])) + call assert_equal(0, charcol([5, '$'])) + + " Test for a mark + normal 2G7lmmgg + call assert_equal(8, charcol("'m")) + delmarks m + call assert_equal(0, charcol("'m")) + + " Test for the visual start column + vnoremap <expr> <F3> SaveVisualStartCharCol() + let g:VisualStartCol = [] + exe "normal 2G6lv$\<F3>ohh\<F3>o\<F3>" + 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, 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)) + call assert_equal([0, 0, 0, 0, 0], getcursorcharpos(-1)) + call assert_equal([0, 0, 0, 0, 0], getcursorcharpos(1999)) + + new + call setline(1, ['', "01\tà4è678", 'Ⅵ', '012345678']) + normal 1G9l + call assert_equal([0, 1, 1, 0, 1], getcursorcharpos()) + normal 2G9l + call assert_equal([0, 2, 9, 0, 14], getcursorcharpos()) + normal 3G9l + call assert_equal([0, 3, 1, 0, 1], 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 + call assert_equal([0, 2, 6, 0, 11], getcursorcharpos(winid)) + %bw! +endfunc + +" Test for setcursorcharpos() +func Test_setcursorcharpos() + call assert_fails('call setcursorcharpos(v:_null_list)', 'E474:') + call assert_fails('call setcursorcharpos([1])', 'E474:') + call assert_fails('call setcursorcharpos([1, 1, 1, 1, 1])', 'E474:') + new + call setline(1, ['', "01\tà4è678", 'Ⅵ', '012345678']) + normal G + call setcursorcharpos([1, 1]) + call assert_equal([1, 1], [line('.'), col('.')]) + call setcursorcharpos([2, 7, 0]) + call assert_equal([2, 9], [line('.'), col('.')]) + call setcursorcharpos(3, 4) + call assert_equal([3, 1], [line('.'), col('.')]) + call setcursorcharpos([3, 1]) + call assert_equal([3, 1], [line('.'), col('.')]) + call setcursorcharpos([4, 0, 0, 0]) + call assert_equal([4, 1], [line('.'), col('.')]) + call setcursorcharpos([4, 20]) + call assert_equal([4, 9], [line('.'), col('.')]) + normal 1G + call setcursorcharpos([100, 100, 100, 100]) + call assert_equal([4, 9], [line('.'), col('.')]) + normal 1G + call setcursorcharpos('$', 1) + call assert_equal([4, 1], [line('.'), col('.')]) + + %bw! +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_diffmode.vim b/src/nvim/testdir/test_diffmode.vim index 3a0c615cf6..482d39056f 100644 --- a/src/nvim/testdir/test_diffmode.vim +++ b/src/nvim/testdir/test_diffmode.vim @@ -1146,6 +1146,35 @@ func Test_diff_followwrap() bwipe! endfunc +func Test_diff_maintains_change_mark() + func DiffMaintainsChangeMark() + enew! + call setline(1, ['a', 'b', 'c', 'd']) + diffthis + new + call setline(1, ['a', 'b', 'c', 'e']) + " Set '[ and '] marks + 2,3yank + call assert_equal([2, 3], [line("'["), line("']")]) + " Verify they aren't affected by the implicit diff + diffthis + call assert_equal([2, 3], [line("'["), line("']")]) + " Verify they aren't affected by an explicit diff + diffupdate + call assert_equal([2, 3], [line("'["), line("']")]) + bwipe! + bwipe! + endfunc + + set diffopt-=internal + call DiffMaintainsChangeMark() + set diffopt+=internal + call DiffMaintainsChangeMark() + + set diffopt& + delfunc DiffMaintainsChangeMark +endfunc + func Test_diff_rnu() CheckScreendump 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_display.vim b/src/nvim/testdir/test_display.vim index c2a9683f7c..9f74d0a38a 100644 --- a/src/nvim/testdir/test_display.vim +++ b/src/nvim/testdir/test_display.vim @@ -41,7 +41,7 @@ func Test_display_foldcolumn() quit! endfunc -func! Test_display_foldtext_mbyte() +func Test_display_foldtext_mbyte() CheckFeature folding call NewWindow(10, 40) diff --git a/src/nvim/testdir/test_edit.vim b/src/nvim/testdir/test_edit.vim index fc4e80f0d6..360b3aaaa0 100644 --- a/src/nvim/testdir/test_edit.vim +++ b/src/nvim/testdir/test_edit.vim @@ -213,7 +213,7 @@ func Test_edit_07() bw! endfunc -func! Test_edit_08() +func Test_edit_08() throw 'skipped: moved to test/functional/legacy/edit_spec.lua' " reset insertmode from i_ctrl-r_= let g:bufnr = bufnr('%') @@ -417,7 +417,7 @@ func Test_edit_13() bwipe! endfunc -func! Test_edit_CR() +func Test_edit_CR() " Test for <CR> in insert mode " basically only in quickfix mode ist tested, the rest " has been taken care of by other tests @@ -450,7 +450,7 @@ func! Test_edit_CR() call delete('Xqflist.txt') endfunc -func! Test_edit_CTRL_() +func Test_edit_CTRL_() " disabled for Windows builds, why? if !has("rightleft") || has("win32") return @@ -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) @@ -734,7 +734,7 @@ func Test_edit_CTRL_O() bw! endfunc -func! Test_edit_CTRL_R() +func Test_edit_CTRL_R() " Insert Register new " call test_override("ALL", 1) @@ -1006,16 +1006,14 @@ func Test_edit_DROP() endfunc func Test_edit_CTRL_V() - if has("ebcdic") - return - endif new call setline(1, ['abc']) call cursor(2, 1) + " force some redraws set showmode showcmd - "call test_override_char_avail(1) - " call test_override('ALL', 1) + " call test_override('char_avail', 1) + call feedkeys("A\<c-v>\<c-n>\<c-v>\<c-l>\<c-v>\<c-b>\<esc>", 'tnix') call assert_equal(["abc\x0e\x0c\x02"], getline(1, '$')) @@ -1028,8 +1026,19 @@ func Test_edit_CTRL_V() set norl endif - " call test_override('ALL', 0) set noshowmode showcmd + " call test_override('char_avail', 0) + + " No modifiers should be applied to the char typed using i_CTRL-V_digit. + call feedkeys(":append\<CR>\<C-V>76c\<C-V>76\<C-F2>\<C-V>u3c0j\<C-V>u3c0\<M-F3>\<CR>.\<CR>", 'tnix') + call assert_equal('LcL<C-F2>πjπ<M-F3>', getline(2)) + + if has('osx') + " A char with a modifier should not be a valid char for i_CTRL-V_digit. + call feedkeys("o\<C-V>\<D-j>\<C-V>\<D-1>\<C-V>\<D-o>\<C-V>\<D-x>\<C-V>\<D-u>", 'tnix') + call assert_equal('<D-j><D-1><D-o><D-x><D-u>', getline(3)) + endif + bw! endfunc @@ -1550,11 +1559,7 @@ endfunc func Test_edit_special_chars() new - if has("ebcdic") - let t = "o\<C-V>193\<C-V>xc2\<C-V>o303 \<C-V>90a\<C-V>xfg\<C-V>o578\<Esc>" - else - let t = "o\<C-V>65\<C-V>x42\<C-V>o103 \<C-V>33a\<C-V>xfg\<C-V>o78\<Esc>" - endif + let t = "o\<C-V>65\<C-V>x42\<C-V>o103 \<C-V>33a\<C-V>xfg\<C-V>o78\<Esc>" exe "normal " . t call assert_equal("ABC !a\<C-O>g\<C-G>8", getline(2)) diff --git a/src/nvim/testdir/test_eval_stuff.vim b/src/nvim/testdir/test_eval_stuff.vim index 883ba5de3d..95eccde35c 100644 --- a/src/nvim/testdir/test_eval_stuff.vim +++ b/src/nvim/testdir/test_eval_stuff.vim @@ -65,11 +65,9 @@ func Test_E963() endfunc func Test_for_invalid() - " Vim gives incorrect emsg here until v8.2.3284, but the exact emsg from that - " patch cannot be used until v8.2.2658 is ported (for loop over Strings) - call assert_fails("for x in 99", 'E897:') - call assert_fails("for x in function('winnr')", 'E897:') - call assert_fails("for x in {'a': 9}", 'E897:') + call assert_fails("for x in 99", 'E1098:') + call assert_fails("for x in function('winnr')", 'E1098:') + call assert_fails("for x in {'a': 9}", 'E1098:') if 0 /1/5/2/s/\n diff --git a/src/nvim/testdir/test_ex_mode.vim b/src/nvim/testdir/test_ex_mode.vim index 92e0559618..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) @@ -98,4 +97,14 @@ func Test_ex_mode_count_overflow() call delete('Xexmodescript') endfunc +func Test_ex_mode_large_indent() + new + set ts=500 ai + call setline(1, "\t") + exe "normal gQi\<CR>." + set ts=8 noai + bwipe! +endfunc + + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_excmd.vim b/src/nvim/testdir/test_excmd.vim index 2d01cbba83..8055a51a11 100644 --- a/src/nvim/testdir/test_excmd.vim +++ b/src/nvim/testdir/test_excmd.vim @@ -1,6 +1,8 @@ " Tests for various Ex commands. source check.vim +source shared.vim +source term_util.vim func Test_ex_delete() new @@ -122,6 +124,27 @@ func Test_append_cmd() close! endfunc +func Test_append_cmd_empty_buf() + CheckRunVimInTerminal + let lines =<< trim END + func Timer(timer) + append + aaaaa + bbbbb + . + endfunc + call timer_start(10, 'Timer') + END + call writefile(lines, 'Xtest_append_cmd_empty_buf') + let buf = RunVimInTerminal('-S Xtest_append_cmd_empty_buf', {'rows': 6}) + call WaitForAssert({-> assert_equal('bbbbb', term_getline(buf, 2))}) + call WaitForAssert({-> assert_equal('aaaaa', term_getline(buf, 1))}) + + " clean up + call StopVimInTerminal(buf) + call delete('Xtest_append_cmd_empty_buf') +endfunc + " Test for the :insert command func Test_insert_cmd() set noautoindent " test assumes noautoindent, but it's on by default in Nvim @@ -151,6 +174,27 @@ func Test_insert_cmd() close! endfunc +func Test_insert_cmd_empty_buf() + CheckRunVimInTerminal + let lines =<< trim END + func Timer(timer) + insert + aaaaa + bbbbb + . + endfunc + call timer_start(10, 'Timer') + END + call writefile(lines, 'Xtest_insert_cmd_empty_buf') + let buf = RunVimInTerminal('-S Xtest_insert_cmd_empty_buf', {'rows': 6}) + call WaitForAssert({-> assert_equal('bbbbb', term_getline(buf, 2))}) + call WaitForAssert({-> assert_equal('aaaaa', term_getline(buf, 1))}) + + " clean up + call StopVimInTerminal(buf) + call delete('Xtest_insert_cmd_empty_buf') +endfunc + " Test for the :change command func Test_change_cmd() set noautoindent " test assumes noautoindent, but it's on by default in Nvim @@ -356,3 +400,27 @@ func Test_winsize_cmd() call assert_fails('win_getid(1)', 'E475: Invalid argument: _getid(1)') " Actually changing the window size would be flaky. endfunc + +func Test_not_break_expression_register() + call setreg('=', '1+1') + if 0 + put =1 + endif + 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_exec_while_if.vim b/src/nvim/testdir/test_exec_while_if.vim index 3da2784d77..3f13b09945 100644 --- a/src/nvim/testdir/test_exec_while_if.vim +++ b/src/nvim/testdir/test_exec_while_if.vim @@ -6,11 +6,7 @@ func Test_exec_while_if() let i = 0 while i < 12 let i = i + 1 - if has("ebcdic") - execute "normal o" . i . "\047" - else - execute "normal o" . i . "\033" - endif + execute "normal o" . i . "\033" if i % 2 normal Ax if i == 9 @@ -21,21 +17,13 @@ func Test_exec_while_if() else let j = 9 while j > 0 - if has("ebcdic") - execute "normal" j . "a" . j . "\x27" - else - execute "normal" j . "a" . j . "\x1b" - endif + execute "normal" j . "a" . j . "\x1b" let j = j - 1 endwhile endif endif if i == 9 - if has("ebcdic") - execute "normal Az\047" - else - execute "normal Az\033" - endif + execute "normal Az\033" endif endwhile unlet i j diff --git a/src/nvim/testdir/test_execute_func.vim b/src/nvim/testdir/test_execute_func.vim index 2cb6d73407..16cc20e9a7 100644 --- a/src/nvim/testdir/test_execute_func.vim +++ b/src/nvim/testdir/test_execute_func.vim @@ -147,3 +147,30 @@ func Test_win_execute_other_tab() tabclose unlet xyz endfunc + +func Test_win_execute_visual_redraw() + call setline(1, ['a', 'b', 'c']) + new + wincmd p + " start Visual in current window, redraw in other window with fewer lines + call feedkeys("G\<C-V>", 'txn') + call win_execute(winnr('#')->win_getid(), 'redraw') + call feedkeys("\<Esc>", 'txn') + bwipe! + bwipe! + + enew + new + call setline(1, ['a', 'b', 'c']) + let winid = win_getid() + wincmd p + " start Visual in current window, extend it in other window with more lines + call feedkeys("\<C-V>", 'txn') + call win_execute(winid, 'call feedkeys("G\<C-V>", ''txn'')') + redraw + + bwipe! + bwipe! +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_expand.vim b/src/nvim/testdir/test_expand.vim new file mode 100644 index 0000000000..48dce25bb3 --- /dev/null +++ b/src/nvim/testdir/test_expand.vim @@ -0,0 +1,83 @@ +" Test for expanding file names + +func Test_with_directories() + call mkdir('Xdir1') + call mkdir('Xdir2') + call mkdir('Xdir3') + cd Xdir3 + call mkdir('Xdir4') + cd .. + + split Xdir1/file + call setline(1, ['a', 'b']) + w + w Xdir3/Xdir4/file + close + + next Xdir?/*/file + call assert_equal('Xdir3/Xdir4/file', expand('%')) + if has('unix') + next! Xdir?/*/nofile + call assert_equal('Xdir?/*/nofile', expand('%')) + endif + " Edit another file, on MS-Windows the swap file would be in use and can't + " be deleted. + edit foo + + call assert_equal(0, delete('Xdir1', 'rf')) + call assert_equal(0, delete('Xdir2', 'rf')) + call assert_equal(0, delete('Xdir3', 'rf')) +endfunc + +func Test_with_tilde() + let dir = getcwd() + call mkdir('Xdir ~ dir') + call assert_true(isdirectory('Xdir ~ dir')) + cd Xdir\ ~\ dir + call assert_true(getcwd() =~ 'Xdir \~ dir') + call chdir(dir) + call delete('Xdir ~ dir', 'd') + call assert_false(isdirectory('Xdir ~ dir')) +endfunc + +func Test_expand_tilde_filename() + split ~ + call assert_equal('~', expand('%')) + call assert_notequal(expand('%:p'), expand('~/')) + call assert_match('\~', expand('%:p')) + bwipe! +endfunc + +func Test_expandcmd() + let $FOO = 'Test' + call assert_equal('e x/Test/y', expandcmd('e x/$FOO/y')) + unlet $FOO + + new + edit Xfile1 + call assert_equal('e Xfile1', expandcmd('e %')) + edit Xfile2 + edit Xfile1 + call assert_equal('e Xfile2', 'e #'->expandcmd()) + edit Xfile2 + edit Xfile3 + edit Xfile4 + let bnum = bufnr('Xfile2') + call assert_equal('e Xfile2', expandcmd('e #' . bnum)) + call setline('.', 'Vim!@#') + call assert_equal('e Vim', expandcmd('e <cword>')) + call assert_equal('e Vim!@#', expandcmd('e <cWORD>')) + enew! + edit Xfile.java + call assert_equal('e Xfile.py', expandcmd('e %:r.py')) + call assert_equal('make abc.java', expandcmd('make abc.%:e')) + call assert_equal('make Xabc.java', expandcmd('make %:s?file?abc?')) + edit a1a2a3.rb + call assert_equal('make b1b2b3.rb a1a2a3 Xfile.o', expandcmd('make %:gs?a?b? %< #<.o')) + + call assert_fails('call expandcmd("make <afile>")', 'E495:') + call assert_fails('call expandcmd("make <afile>")', 'E495:') + enew + call assert_fails('call expandcmd("make %")', 'E499:') + close +endfunc diff --git a/src/nvim/testdir/test_expr.vim b/src/nvim/testdir/test_expr.vim index 1d7fd3e385..5b10e691e5 100644 --- a/src/nvim/testdir/test_expr.vim +++ b/src/nvim/testdir/test_expr.vim @@ -283,6 +283,71 @@ function Test_printf_misc() call assert_equal('🐍', printf('%.2S', '🐍🐍')) call assert_equal('', printf('%.1S', '🐍🐍')) + call assert_equal('[ あいう]', printf('[%10.6S]', 'あいうえお')) + call assert_equal('[ あいうえ]', printf('[%10.8S]', 'あいうえお')) + call assert_equal('[あいうえお]', printf('[%10.10S]', 'あいうえお')) + call assert_equal('[あいうえお]', printf('[%10.12S]', 'あいうえお')) + + call assert_equal('あいう', printf('%S', 'あいう')) + call assert_equal('あいう', printf('%#S', 'あいう')) + + call assert_equal('あb', printf('%2S', 'あb')) + call assert_equal('あb', printf('%.4S', 'あb')) + call assert_equal('あ', printf('%.2S', 'あb')) + call assert_equal(' あb', printf('%4S', 'あb')) + call assert_equal('0あb', printf('%04S', 'あb')) + call assert_equal('あb ', printf('%-4S', 'あb')) + call assert_equal('あ ', printf('%-4.2S', 'あb')) + + call assert_equal('aい', printf('%2S', 'aい')) + call assert_equal('aい', printf('%.4S', 'aい')) + call assert_equal('a', printf('%.2S', 'aい')) + call assert_equal(' aい', printf('%4S', 'aい')) + call assert_equal('0aい', printf('%04S', 'aい')) + call assert_equal('aい ', printf('%-4S', 'aい')) + call assert_equal('a ', printf('%-4.2S', 'aい')) + + call assert_equal('[あいう]', printf('[%05S]', 'あいう')) + call assert_equal('[あいう]', printf('[%06S]', 'あいう')) + call assert_equal('[0あいう]', printf('[%07S]', 'あいう')) + + call assert_equal('[あiう]', printf('[%05S]', 'あiう')) + call assert_equal('[0あiう]', printf('[%06S]', 'あiう')) + call assert_equal('[00あiう]', printf('[%07S]', 'あiう')) + + call assert_equal('[0あい]', printf('[%05.4S]', 'あいう')) + call assert_equal('[00あい]', printf('[%06.4S]', 'あいう')) + call assert_equal('[000あい]', printf('[%07.4S]', 'あいう')) + + call assert_equal('[00あi]', printf('[%05.4S]', 'あiう')) + call assert_equal('[000あi]', printf('[%06.4S]', 'あiう')) + call assert_equal('[0000あi]', printf('[%07.4S]', 'あiう')) + + call assert_equal('[0あい]', printf('[%05.5S]', 'あいう')) + call assert_equal('[00あい]', printf('[%06.5S]', 'あいう')) + call assert_equal('[000あい]', printf('[%07.5S]', 'あいう')) + + call assert_equal('[あiう]', printf('[%05.5S]', 'あiう')) + call assert_equal('[0あiう]', printf('[%06.5S]', 'あiう')) + call assert_equal('[00あiう]', printf('[%07.5S]', 'あiう')) + + call assert_equal('[0000000000]', printf('[%010.0S]', 'あいう')) + call assert_equal('[0000000000]', printf('[%010.1S]', 'あいう')) + call assert_equal('[00000000あ]', printf('[%010.2S]', 'あいう')) + call assert_equal('[00000000あ]', printf('[%010.3S]', 'あいう')) + call assert_equal('[000000あい]', printf('[%010.4S]', 'あいう')) + call assert_equal('[000000あい]', printf('[%010.5S]', 'あいう')) + call assert_equal('[0000あいう]', printf('[%010.6S]', 'あいう')) + call assert_equal('[0000あいう]', printf('[%010.7S]', 'あいう')) + + call assert_equal('[0000000000]', printf('[%010.1S]', 'あiう')) + call assert_equal('[00000000あ]', printf('[%010.2S]', 'あiう')) + call assert_equal('[0000000あi]', printf('[%010.3S]', 'あiう')) + call assert_equal('[0000000あi]', printf('[%010.4S]', 'あiう')) + call assert_equal('[00000あiう]', printf('[%010.5S]', 'あiう')) + call assert_equal('[00000あiう]', printf('[%010.6S]', 'あiう')) + call assert_equal('[00000あiう]', printf('[%010.7S]', 'あiう')) + call assert_equal('1%', printf('%d%%', 1)) endfunc diff --git a/src/nvim/testdir/test_feedkeys.vim b/src/nvim/testdir/test_feedkeys.vim index 70500f2bb5..f343b0174c 100644 --- a/src/nvim/testdir/test_feedkeys.vim +++ b/src/nvim/testdir/test_feedkeys.vim @@ -12,3 +12,15 @@ func Test_feedkeys_x_with_empty_string() call assert_equal('foo', getline('.')) quit! endfunc + +func Test_feedkeys_with_abbreviation() + new + inoreabbrev trigger value + call feedkeys("atrigger ", 'x') + call feedkeys("atrigger ", 'x') + call assert_equal('value value ', getline(1)) + bwipe! + iunabbrev trigger +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 1ee23bb646..5f4a7dac6e 100644 --- a/src/nvim/testdir/test_filetype.vim +++ b/src/nvim/testdir/test_filetype.vim @@ -76,10 +76,12 @@ let s:filename_checks = { \ 'ave': ['file.ave'], \ 'awk': ['file.awk', 'file.gawk'], \ 'b': ['file.mch', 'file.ref', 'file.imp'], + \ 'basic': ['file.bas', 'file.bi', 'file.bm'], \ 'bzl': ['file.bazel', 'file.bzl', 'WORKSPACE'], \ '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'], @@ -130,6 +132,7 @@ let s:filename_checks = { \ 'cvs': ['cvs123'], \ 'cvsrc': ['.cvsrc'], \ 'cynpp': ['file.cyn'], + \ 'd': ['file.d'], \ 'dart': ['file.dart', 'file.drt'], \ 'datascript': ['file.ds'], \ 'dcd': ['file.dcd'], @@ -146,12 +149,13 @@ let s:filename_checks = { \ 'diff': ['file.diff', 'file.rej'], \ 'dircolors': ['.dir_colors', '.dircolors', '/etc/DIR_COLORS', 'any/etc/DIR_COLORS'], \ 'dnsmasq': ['/etc/dnsmasq.conf', '/etc/dnsmasq.d/file', 'any/etc/dnsmasq.conf', 'any/etc/dnsmasq.d/file'], - \ 'dockerfile': ['Containerfile', 'Dockerfile', 'file.Dockerfile'], + \ 'dockerfile': ['Containerfile', 'Dockerfile', 'file.Dockerfile', 'Dockerfile.debian', 'Containerfile.something'], \ 'dosbatch': ['file.bat', 'file.sys'], \ 'dosini': ['.editorconfig', '/etc/pacman.conf', '/etc/yum.conf', 'file.ini', 'npmrc', '.npmrc', 'php.ini', 'php.ini-5', 'php.ini-file', '/etc/yum.repos.d/file', 'any/etc/pacman.conf', 'any/etc/yum.conf', 'any/etc/yum.repos.d/file', 'file.wrap'], \ 'dot': ['file.dot', 'file.gv'], \ 'dracula': ['file.drac', 'file.drc', 'filelvs', 'filelpe', 'drac.file', 'lpe', 'lvs', 'some-lpe', 'some-lvs'], \ 'dtd': ['file.dtd'], + \ 'dtrace': ['/usr/lib/dtrace/io.d'], \ 'dts': ['file.dts', 'file.dtsi'], \ 'dune': ['jbuild', 'dune', 'dune-project', 'dune-workspace'], \ 'dylan': ['file.dylan'], @@ -182,47 +186,59 @@ let s:filename_checks = { \ 'fgl': ['file.4gl', 'file.4gh', 'file.m4gl'], \ 'fish': ['file.fish'], \ 'focexec': ['file.fex', 'file.focexec'], + \ 'form': ['file.frm'], \ 'forth': ['file.ft', 'file.fth'], \ 'fortran': ['file.f', 'file.for', 'file.fortran', 'file.fpp', 'file.ftn', 'file.f77', 'file.f90', 'file.f95', 'file.f03', 'file.f08'], \ 'fpcmake': ['file.fpc'], \ 'framescript': ['file.fsl'], - \ 'freebasic': ['file.fb', 'file.bi'], + \ 'freebasic': ['file.fb'], \ 'fsharp': ['file.fs', 'file.fsi', 'file.fsx'], \ 'fstab': ['fstab', 'mtab'], + \ 'fusion': ['file.fusion'], \ 'fvwm': ['/.fvwm/file', 'any/.fvwm/file'], \ 'gdb': ['.gdbinit', 'gdbinit'], + \ 'gdresource': ['file.tscn', 'file.tres'], + \ 'gdscript': ['file.gd'], \ 'gdmo': ['file.mo', 'file.gdmo'], \ 'gedcom': ['file.ged', 'lltxxxxx.txt', '/tmp/lltmp', '/tmp/lltmp-file', 'any/tmp/lltmp', 'any/tmp/lltmp-file'], \ 'gemtext': ['file.gmi', 'file.gemini'], \ 'gift': ['file.gift'], - \ 'gitcommit': ['COMMIT_EDITMSG', 'MERGE_MSG', 'TAG_EDITMSG'], - \ 'gitconfig': ['file.git/config', '.gitconfig', '.gitmodules', 'file.git/modules//config', '/.config/git/config', '/etc/gitconfig', '/etc/gitconfig.d/file', '/.gitconfig.d/file', 'any/.config/git/config', 'any/.gitconfig.d/file', 'some.git/config', 'some.git/modules/any/config'], + \ 'gitcommit': ['COMMIT_EDITMSG', 'MERGE_MSG', 'TAG_EDITMSG', 'NOTES_EDITMSG', 'EDIT_DESCRIPTION'], + \ 'gitconfig': ['file.git/config', 'file.git/config.worktree', 'file.git/worktrees/x/config.worktree', '.gitconfig', '.gitmodules', 'file.git/modules//config', '/.config/git/config', '/etc/gitconfig', '/usr/local/etc/gitconfig', '/etc/gitconfig.d/file', '/.gitconfig.d/file', 'any/.config/git/config', 'any/.gitconfig.d/file', 'some.git/config', 'some.git/modules/any/config'], \ 'gitolite': ['gitolite.conf', '/gitolite-admin/conf/file', 'any/gitolite-admin/conf/file'], \ 'gitrebase': ['git-rebase-todo'], \ 'gitsendemail': ['.gitsendemail.msg.xxxxxx'], \ 'gkrellmrc': ['gkrellmrc', 'gkrellmrc_x'], + \ 'glsl': ['file.glsl'], \ 'gnash': ['gnashrc', '.gnashrc', 'gnashpluginrc', '.gnashpluginrc'], - \ 'gnuplot': ['file.gpi'], + \ 'gnuplot': ['file.gpi', '.gnuplot'], \ 'go': ['file.go'], \ 'gomod': ['go.mod'], + \ 'gowork': ['go.work'], \ 'gp': ['file.gp', '.gprc'], \ 'gpg': ['/.gnupg/options', '/.gnupg/gpg.conf', '/usr/any/gnupg/options.skel', 'any/.gnupg/gpg.conf', 'any/.gnupg/options', 'any/usr/any/gnupg/options.skel'], \ 'grads': ['file.gs'], + \ 'graphql': ['file.graphql', 'file.graphqls', 'file.gql'], \ 'gretl': ['file.gretl'], \ 'groovy': ['file.gradle', 'file.groovy'], \ 'group': ['any/etc/group', 'any/etc/group-', 'any/etc/group.edit', 'any/etc/gshadow', 'any/etc/gshadow-', 'any/etc/gshadow.edit', 'any/var/backups/group.bak', 'any/var/backups/gshadow.bak', '/etc/group', '/etc/group-', '/etc/group.edit', '/etc/gshadow', '/etc/gshadow-', '/etc/gshadow.edit', '/var/backups/group.bak', '/var/backups/gshadow.bak'], \ 'grub': ['/boot/grub/menu.lst', '/boot/grub/grub.conf', '/etc/grub.conf', 'any/boot/grub/grub.conf', 'any/boot/grub/menu.lst', 'any/etc/grub.conf'], \ 'gsp': ['file.gsp'], \ 'gtkrc': ['.gtkrc', 'gtkrc', '.gtkrc-file', 'gtkrc-file'], + \ 'hack': ['file.hack', 'file.hackpartial'], \ 'haml': ['file.haml'], \ 'hamster': ['file.hsm'], + \ 'handlebars': ['file.hbs'], \ 'haskell': ['file.hs', 'file.hsc', 'file.hs-boot', 'file.hsig'], \ 'haste': ['file.ht'], \ 'hastepreproc': ['file.htpp'], \ 'hb': ['file.hb'], + \ 'hcl': ['file.hcl'], \ 'hercules': ['file.vc', 'file.ev', 'file.sum', 'file.errsum'], + \ 'heex': ['file.heex'], \ 'hex': ['file.hex', 'file.h32'], \ 'hgcommit': ['hg-editor-file.txt'], + \ 'hjson': ['file.hjson'], \ 'hog': ['file.hog', 'snort.conf', 'vision.conf'], \ 'hollywood': ['file.hws'], \ 'hostconf': ['/etc/host.conf', 'any/etc/host.conf'], @@ -258,12 +274,14 @@ 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'], \ 'jovial': ['file.jov', 'file.j73', 'file.jovial'], \ 'jproperties': ['file.properties', 'file.properties_xx', 'file.properties_xx_xx', 'some.properties_xx_xx_file'], \ 'json': ['file.json', 'file.jsonp', 'file.json-patch', 'file.webmanifest', 'Pipfile.lock', 'file.ipynb', '.babelrc', '.eslintrc', '.prettierrc', '.firebaserc', 'file.slnf'], + \ 'json5': ['file.json5'], \ 'jsonc': ['file.jsonc'], \ 'jsp': ['file.jsp'], \ 'julia': ['file.jl'], @@ -277,6 +295,7 @@ let s:filename_checks = { \ 'latte': ['file.latte', 'file.lte'], \ 'ld': ['file.ld'], \ 'ldif': ['file.ldif'], + \ 'ledger': ['file.ldg', 'file.ledger', 'file.journal'], \ 'less': ['file.less'], \ 'lex': ['file.lex', 'file.l', 'file.lxx', 'file.l++'], \ 'lftp': ['lftp.conf', '.lftprc', 'anylftp/rc', 'lftp/rc', 'some-lftp/rc'], @@ -286,7 +305,7 @@ let s:filename_checks = { \ 'lilo': ['lilo.conf', 'lilo.conf-file'], \ 'limits': ['/etc/limits', '/etc/anylimits.conf', '/etc/anylimits.d/file.conf', '/etc/limits.conf', '/etc/limits.d/file.conf', '/etc/some-limits.conf', '/etc/some-limits.d/file.conf', 'any/etc/limits', 'any/etc/limits.conf', 'any/etc/limits.d/file.conf', 'any/etc/some-limits.conf', 'any/etc/some-limits.d/file.conf'], \ 'liquid': ['file.liquid'], - \ 'lisp': ['file.lsp', 'file.lisp', 'file.el', 'file.cl', '.emacs', '.sawfishrc', 'sbclrc', '.sbclrc'], + \ 'lisp': ['file.lsp', 'file.lisp', 'file.asd', 'file.el', 'file.cl', '.emacs', '.sawfishrc', 'sbclrc', '.sbclrc'], \ 'lite': ['file.lite', 'file.lt'], \ 'litestep': ['/LiteStep/any/file.rc', 'any/LiteStep/any/file.rc'], \ 'loginaccess': ['/etc/login.access', 'any/etc/login.access'], @@ -350,6 +369,7 @@ let s:filename_checks = { \ 'netrc': ['.netrc'], \ 'nginx': ['file.nginx', 'nginxfile.conf', 'filenginx.conf', 'any/etc/nginx/file', 'any/usr/local/nginx/conf/file', 'any/nginx/file.conf'], \ 'ninja': ['file.ninja'], + \ 'nix': ['file.nix'], \ 'nqc': ['file.nqc'], \ 'nroff': ['file.tr', 'file.nr', 'file.roff', 'file.tmac', 'file.mom', 'tmac.file'], \ 'nsis': ['file.nsi', 'file.nsh'], @@ -392,6 +412,7 @@ let s:filename_checks = { \ 'ppd': ['file.ppd'], \ 'ppwiz': ['file.it', 'file.ih'], \ 'privoxy': ['file.action'], + \ 'prisma': ['file.prisma'], \ 'proc': ['file.pc'], \ 'procmail': ['.procmail', '.procmailrc'], \ 'prolog': ['file.pdb'], @@ -402,10 +423,12 @@ let s:filename_checks = { \ 'ps1xml': ['file.ps1xml'], \ 'psf': ['file.psf'], \ 'psl': ['file.psl'], + \ 'pug': ['file.pug'], \ 'puppet': ['file.pp'], \ 'pyret': ['file.arr'], \ 'pyrex': ['file.pyx', 'file.pxd'], \ 'python': ['file.py', 'file.pyw', '.pythonstartup', '.pythonrc', 'file.ptl', 'file.pyi', 'SConstruct'], + \ 'ql': ['file.ql', 'file.qll'], \ 'quake': ['anybaseq2/file.cfg', 'anyid1/file.cfg', 'quake3/file.cfg', 'baseq2/file.cfg', 'id1/file.cfg', 'quake1/file.cfg', 'some-baseq2/file.cfg', 'some-id1/file.cfg', 'some-quake1/file.cfg'], \ 'radiance': ['file.rad', 'file.mat'], \ 'raku': ['file.pm6', 'file.p6', 'file.t6', 'file.pod6', 'file.raku', 'file.rakumod', 'file.rakudoc', 'file.rakutest'], @@ -416,6 +439,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'], @@ -436,7 +460,7 @@ let s:filename_checks = { \ 'sather': ['file.sa'], \ 'sbt': ['file.sbt'], \ 'scala': ['file.scala', 'file.sc'], - \ 'scheme': ['file.scm', 'file.ss', 'file.rkt', 'file.rktd', 'file.rktl'], + \ 'scheme': ['file.scm', 'file.ss', 'file.sld', 'file.rkt', 'file.rktd', 'file.rktl'], \ 'scilab': ['file.sci', 'file.sce'], \ 'screen': ['.screenrc', 'screenrc'], \ 'sexplib': ['file.sexp'], @@ -446,7 +470,7 @@ let s:filename_checks = { \ 'sdc': ['file.sdc'], \ 'sdl': ['file.sdl', 'file.pr'], \ 'sed': ['file.sed'], - \ 'sensors': ['/etc/sensors.conf', '/etc/sensors3.conf', 'any/etc/sensors.conf', 'any/etc/sensors3.conf'], + \ 'sensors': ['/etc/sensors.conf', '/etc/sensors3.conf', '/etc/sensors.d/file', 'any/etc/sensors.conf', 'any/etc/sensors3.conf', 'any/etc/sensors.d/file'], \ 'services': ['/etc/services', 'any/etc/services'], \ 'setserial': ['/etc/serial.conf', 'any/etc/serial.conf'], \ 'sh': ['.bashrc', 'file.bash', '/usr/share/doc/bash-completion/filter.sh','/etc/udev/cdsymlinks.conf', 'any/etc/udev/cdsymlinks.conf'], @@ -457,6 +481,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'], @@ -481,12 +506,13 @@ let s:filename_checks = { \ 'squid': ['squid.conf'], \ 'squirrel': ['file.nut'], \ 'srec': ['file.s19', 'file.s28', 'file.s37', 'file.mot', 'file.srec'], - \ 'sshconfig': ['ssh_config', '/.ssh/config', '/etc/ssh/ssh_config.d/file.conf', 'any/etc/ssh/ssh_config.d/file.conf', 'any/.ssh/config'], + \ 'sshconfig': ['ssh_config', '/.ssh/config', '/etc/ssh/ssh_config.d/file.conf', 'any/etc/ssh/ssh_config.d/file.conf', 'any/.ssh/config', 'any/.ssh/file.conf'], \ 'sshdconfig': ['sshd_config', '/etc/ssh/sshd_config.d/file.conf', 'any/etc/ssh/sshd_config.d/file.conf'], \ 'st': ['file.st'], \ 'stata': ['file.ado', 'file.do', 'file.imata', 'file.mata'], \ 'stp': ['file.stp'], \ 'sudoers': ['any/etc/sudoers', 'sudoers.tmp', '/etc/sudoers', 'any/etc/sudoers.d/file'], + \ 'surface': ['file.sface'], \ 'svg': ['file.svg'], \ 'svn': ['svn-commitfile.tmp', 'svn-commit-file.tmp', 'svn-commit.tmp'], \ 'swift': ['file.swift'], @@ -500,8 +526,10 @@ let s:filename_checks = { \ 'taskdata': ['pending.data', 'completed.data', 'undo.data'], \ 'taskedit': ['file.task'], \ 'tcl': ['file.tcl', 'file.tm', 'file.tk', 'file.itcl', 'file.itk', 'file.jacl', '.tclshrc', 'tclsh.rc', '.wishrc'], + \ 'teal': ['file.tl'], \ 'teraterm': ['file.ttl'], \ 'terminfo': ['file.ti'], + \ 'terraform': ['file.tfvars'], \ 'tex': ['file.latex', 'file.sty', 'file.dtx', 'file.ltx', 'file.bbl'], \ 'texinfo': ['file.texinfo', 'file.texi', 'file.txi'], \ 'texmf': ['texmf.cnf'], @@ -509,6 +537,7 @@ let s:filename_checks = { \ 'tf': ['file.tf', '.tfrc', 'tfrc'], \ 'tidy': ['.tidyrc', 'tidyrc', 'tidy.conf'], \ 'tilde': ['file.t.html'], + \ 'tla': ['file.tla'], \ 'tli': ['file.tli'], \ 'tmux': ['tmuxfile.conf', '.tmuxfile.conf', '.tmux-file.conf', '.tmux.conf', 'tmux-file.conf', 'tmux.conf', 'tmux.conf.local'], \ 'toml': ['file.toml', 'Gopkg.lock', 'Pipfile', '/home/user/.cargo/config'], @@ -520,6 +549,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'], @@ -534,6 +564,7 @@ let s:filename_checks = { \ 'usserverlog': ['usserver.log', 'USSERVER.LOG', 'usserver.file.log', 'USSERVER.FILE.LOG', 'file.usserver.log', 'FILE.USSERVER.LOG'], \ 'usw2kagtlog': ['usw2kagt.log', 'USW2KAGT.LOG', 'usw2kagt.file.log', 'USW2KAGT.FILE.LOG', 'file.usw2kagt.log', 'FILE.USW2KAGT.LOG'], \ 'vb': ['file.sba', 'file.vb', 'file.vbs', 'file.dsm', 'file.ctl'], + \ 'vala': ['file.vala'], \ 'vera': ['file.vr', 'file.vri', 'file.vrh'], \ 'verilog': ['file.v'], \ 'verilogams': ['file.va', 'file.vams'], @@ -569,6 +600,7 @@ let s:filename_checks = { \ 'xslt': ['file.xsl', 'file.xslt'], \ 'yacc': ['file.yy', 'file.yxx', 'file.y++'], \ 'yaml': ['file.yaml', 'file.yml'], + \ 'yang': ['file.yang'], \ 'raml': ['file.raml'], \ 'z8a': ['file.z8a'], \ 'zig': ['file.zig'], @@ -653,7 +685,7 @@ let s:script_checks = { \ ['#!/path/nodejs'], \ ['#!/path/rhino']], \ 'bc': [['#!/path/bc']], - \ 'sed': [['#!/path/sed']], + \ 'sed': [['#!/path/sed'], ['#n'], ['#n comment']], \ 'ocaml': [['#!/path/ocaml']], \ 'awk': [['#!/path/awk'], \ ['#!/path/gawk']], @@ -706,83 +738,162 @@ func Test_setfiletype_completion() call assert_equal('"setfiletype java javacc javascript javascriptreact', @:) endfunc -func Test_hook_file() +""""""""""""""""""""""""""""""""""""""""""""""""" +" Tests for specific extentions and filetypes. +" Keep sorted. +""""""""""""""""""""""""""""""""""""""""""""""""" + +func Test_bas_file() filetype on - call writefile(['[Trigger]', 'this is pacman config'], 'Xfile.hook') - split Xfile.hook - call assert_equal('dosini', &filetype) + call writefile(['looks like BASIC'], 'Xfile.bas') + split Xfile.bas + call assert_equal('basic', &filetype) bwipe! - call writefile(['not pacman'], 'Xfile.hook') - split Xfile.hook - call assert_notequal('dosini', &filetype) + " Test dist#ft#FTbas() + + let g:filetype_bas = 'freebasic' + split Xfile.bas + call assert_equal('freebasic', &filetype) bwipe! + unlet g:filetype_bas - call delete('Xfile.hook') - filetype off -endfunc + " FreeBASIC -func Test_ts_file() - filetype on + call writefile(["/' FreeBASIC multiline comment '/"], 'Xfile.bas') + split Xfile.bas + call assert_equal('freebasic', &filetype) + bwipe! - call writefile(['<?xml version="1.0" encoding="utf-8"?>'], 'Xfile.ts') - split Xfile.ts - call assert_equal('xml', &filetype) + call writefile(['#define TESTING'], 'Xfile.bas') + split Xfile.bas + call assert_equal('freebasic', &filetype) bwipe! - call writefile(['// looks like Typescript'], 'Xfile.ts') - split Xfile.ts - call assert_equal('typescript', &filetype) + call writefile(['option byval'], 'Xfile.bas') + split Xfile.bas + call assert_equal('freebasic', &filetype) bwipe! - call delete('Xfile.hook') + call writefile(['extern "C"'], 'Xfile.bas') + split Xfile.bas + call assert_equal('freebasic', &filetype) + bwipe! + + " QB64 + + call writefile(['$LET TESTING = 1'], 'Xfile.bas') + split Xfile.bas + call assert_equal('qb64', &filetype) + bwipe! + + call writefile(['OPTION _EXPLICIT'], 'Xfile.bas') + split Xfile.bas + call assert_equal('qb64', &filetype) + bwipe! + + " Visual Basic + + call writefile(['Attribute VB_NAME = "Testing"'], 'Xfile.bas') + split Xfile.bas + call assert_equal('vb', &filetype) + bwipe! + + call delete('Xfile.bas') filetype off endfunc -func Test_ttl_file() +func Test_d_file() filetype on - call writefile(['@base <http://example.org/> .'], 'Xfile.ttl') - split Xfile.ttl - call assert_equal('turtle', &filetype) + call writefile(['looks like D'], 'Xfile.d') + split Xfile.d + call assert_equal('d', &filetype) bwipe! - call writefile(['looks like Tera Term Language'], 'Xfile.ttl') - split Xfile.ttl - call assert_equal('teraterm', &filetype) + call writefile(['#!/some/bin/dtrace'], 'Xfile.d') + split Xfile.d + call assert_equal('dtrace', &filetype) + bwipe! + + call writefile(['#pragma D option'], 'Xfile.d') + split Xfile.d + call assert_equal('dtrace', &filetype) + bwipe! + + call writefile([':some:thing:'], 'Xfile.d') + split Xfile.d + call assert_equal('dtrace', &filetype) + bwipe! + + call writefile(['module this', '#pragma D option'], 'Xfile.d') + split Xfile.d + call assert_equal('d', &filetype) + bwipe! + + call writefile(['import that', '#pragma D option'], 'Xfile.d') + split Xfile.d + call assert_equal('d', &filetype) bwipe! - call delete('Xfile.ttl') filetype off endfunc -func Test_pp_file() +func Test_dep3patch_file() filetype on - call writefile(['looks like puppet'], 'Xfile.pp') - split Xfile.pp - call assert_equal('puppet', &filetype) + call assert_true(mkdir('debian/patches', 'p')) + + " series files are not patches + call writefile(['Description: some awesome patch'], 'debian/patches/series') + split debian/patches/series + call assert_notequal('dep3patch', &filetype) bwipe! - let g:filetype_pp = 'pascal' - split Xfile.pp - call assert_equal('pascal', &filetype) + " diff/patch files without the right headers should still show up as ft=diff + call writefile([], 'debian/patches/foo.diff') + split debian/patches/foo.diff + call assert_equal('diff', &filetype) bwipe! - unlet g:filetype_pp - " Test dist#ft#FTpp() - call writefile(['{ pascal comment'], 'Xfile.pp') - split Xfile.pp - call assert_equal('pascal', &filetype) + " Files with the right headers are detected as dep3patch, even if they don't + " have a diff/patch extension + call writefile(['Subject: dep3patches'], 'debian/patches/bar') + split debian/patches/bar + call assert_equal('dep3patch', &filetype) bwipe! - call writefile(['procedure pascal'], 'Xfile.pp') - split Xfile.pp - call assert_equal('pascal', &filetype) + " Files in sub-directories are detected + call assert_true(mkdir('debian/patches/s390x', 'p')) + call writefile(['Subject: dep3patches'], 'debian/patches/s390x/bar') + split debian/patches/s390x/bar + call assert_equal('dep3patch', &filetype) bwipe! - call delete('Xfile.pp') + " The detection stops when seeing the "header end" marker + call writefile(['---', 'Origin: the cloud'], 'debian/patches/baz') + split debian/patches/baz + call assert_notequal('dep3patch', &filetype) + bwipe! + + call delete('debian', 'rf') +endfunc + +func Test_dsl_file() + filetype on + + call writefile([' <!doctype dsssl-spec ['], 'dslfile.dsl') + split dslfile.dsl + call assert_equal('dsl', &filetype) + bwipe! + + call writefile(['workspace {'], 'dslfile.dsl') + split dslfile.dsl + call assert_equal('structurizr', &filetype) + bwipe! + + call delete('dslfile.dsl') filetype off endfunc @@ -823,20 +934,183 @@ func Test_ex_file() filetype off endfunc -func Test_dsl_file() +func Test_foam_file() filetype on + call assert_true(mkdir('0', 'p')) + call assert_true(mkdir('0.orig', 'p')) - call writefile([' <!doctype dsssl-spec ['], 'dslfile.dsl') - split dslfile.dsl - call assert_equal('dsl', &filetype) + call writefile(['FoamFile {', ' object something;'], 'Xfile1Dict') + split Xfile1Dict + call assert_equal('foam', &filetype) bwipe! - call writefile(['workspace {'], 'dslfile.dsl') - split dslfile.dsl - call assert_equal('structurizr', &filetype) + call writefile(['FoamFile {', ' object something;'], 'Xfile1Dict.something') + split Xfile1Dict.something + call assert_equal('foam', &filetype) bwipe! - call delete('dslfile.dsl') + call writefile(['FoamFile {', ' object something;'], 'XfileProperties') + split XfileProperties + call assert_equal('foam', &filetype) + bwipe! + + call writefile(['FoamFile {', ' object something;'], 'XfileProperties.something') + split XfileProperties.something + call assert_equal('foam', &filetype) + bwipe! + + call writefile(['FoamFile {', ' object something;'], 'XfileProperties') + split XfileProperties + call assert_equal('foam', &filetype) + bwipe! + + call writefile(['FoamFile {', ' object something;'], 'XfileProperties.something') + split XfileProperties.something + call assert_equal('foam', &filetype) + bwipe! + + call writefile(['FoamFile {', ' object something;'], '0/Xfile') + split 0/Xfile + call assert_equal('foam', &filetype) + bwipe! + + call writefile(['FoamFile {', ' object something;'], '0.orig/Xfile') + split 0.orig/Xfile + call assert_equal('foam', &filetype) + bwipe! + + call delete('0', 'rf') + call delete('0.orig', 'rf') + call delete('Xfile1Dict') + call delete('Xfile1Dict.something') + call delete('XfileProperties') + call delete('XfileProperties.something') + filetype off +endfunc + +func Test_frm_file() + filetype on + + call writefile(['looks like FORM'], 'Xfile.frm') + split Xfile.frm + call assert_equal('form', &filetype) + bwipe! + + " Test dist#ft#FTfrm() + + let g:filetype_frm = 'form' + split Xfile.frm + call assert_equal('form', &filetype) + bwipe! + unlet g:filetype_frm + + " Visual Basic + + call writefile(['Begin VB.Form Form1'], 'Xfile.frm') + split Xfile.frm + call assert_equal('vb', &filetype) + bwipe! + + call delete('Xfile.frm') + filetype off +endfunc + +func Test_fs_file() + filetype on + + call writefile(['looks like F#'], 'Xfile.fs') + split Xfile.fs + call assert_equal('fsharp', &filetype) + bwipe! + + let g:filetype_fs = 'forth' + split Xfile.fs + call assert_equal('forth', &filetype) + bwipe! + unlet g:filetype_fs + + " Test dist#ft#FTfs() + + " Forth (Gforth) + + call writefile(['( Forth inline comment )'], 'Xfile.fs') + split Xfile.fs + call assert_equal('forth', &filetype) + bwipe! + + call writefile(['.( Forth displayed inline comment )'], 'Xfile.fs') + split Xfile.fs + call assert_equal('forth', &filetype) + bwipe! + + call writefile(['\ Forth line comment'], 'Xfile.fs') + split Xfile.fs + call assert_equal('forth', &filetype) + bwipe! + + " empty line comment - no space required + call writefile(['\'], 'Xfile.fs') + split Xfile.fs + call assert_equal('forth', &filetype) + bwipe! + + call writefile(['\G Forth documentation comment '], 'Xfile.fs') + split Xfile.fs + call assert_equal('forth', &filetype) + bwipe! + + call writefile([': squared ( n -- n^2 )', 'dup * ;'], 'Xfile.fs') + split Xfile.fs + call assert_equal('forth', &filetype) + bwipe! + + call delete('Xfile.fs') + filetype off +endfunc + +func Test_git_file() + filetype on + + call assert_true(mkdir('Xrepo.git', 'p')) + + call writefile([], 'Xrepo.git/HEAD') + split Xrepo.git/HEAD + call assert_equal('', &filetype) + bwipe! + + call writefile(['0000000000000000000000000000000000000000'], 'Xrepo.git/HEAD') + split Xrepo.git/HEAD + call assert_equal('git', &filetype) + bwipe! + + call writefile(['0000000000000000000000000000000000000000000000000000000000000000'], 'Xrepo.git/HEAD') + split Xrepo.git/HEAD + call assert_equal('git', &filetype) + bwipe! + + call writefile(['ref: refs/heads/master'], 'Xrepo.git/HEAD') + split Xrepo.git/HEAD + call assert_equal('git', &filetype) + bwipe! + + call delete('Xrepo.git', 'rf') + filetype off +endfunc + +func Test_hook_file() + filetype on + + call writefile(['[Trigger]', 'this is pacman config'], 'Xfile.hook') + split Xfile.hook + call assert_equal('dosini', &filetype) + bwipe! + + call writefile(['not pacman'], 'Xfile.hook') + split Xfile.hook + call assert_notequal('dosini', &filetype) + bwipe! + + call delete('Xfile.hook') filetype off endfunc @@ -940,109 +1214,154 @@ func Test_m_file() filetype off endfunc -func Test_xpm_file() +func Test_patch_file() filetype on - call writefile(['this is XPM2'], 'file.xpm') - split file.xpm - call assert_equal('xpm2', &filetype) + call writefile([], 'Xfile.patch') + split Xfile.patch + call assert_equal('diff', &filetype) bwipe! - call delete('file.xpm') + call writefile(['From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001'], 'Xfile.patch') + split Xfile.patch + call assert_equal('gitsendemail', &filetype) + bwipe! + + call writefile(['From 0000000000000000000000000000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001'], 'Xfile.patch') + split Xfile.patch + call assert_equal('gitsendemail', &filetype) + bwipe! + + call delete('Xfile.patch') filetype off endfunc -func Test_fs_file() +func Test_perl_file() filetype on - call writefile(['looks like F#'], 'Xfile.fs') - split Xfile.fs - call assert_equal('fsharp', &filetype) - bwipe! + " only tests one case, should do more + let lines =<< trim END - let g:filetype_fs = 'forth' - split Xfile.fs - call assert_equal('forth', &filetype) - bwipe! - unlet g:filetype_fs + use a + END + call writefile(lines, "Xfile.t") + split Xfile.t + call assert_equal('perl', &filetype) + bwipe - " Test dist#ft#FTfs() + call delete('Xfile.t') + filetype off +endfunc - " Forth (Gforth) +func Test_pp_file() + filetype on - call writefile(['( Forth inline comment )'], 'Xfile.fs') - split Xfile.fs - call assert_equal('forth', &filetype) + call writefile(['looks like puppet'], 'Xfile.pp') + split Xfile.pp + call assert_equal('puppet', &filetype) bwipe! - call writefile(['.( Forth displayed inline comment )'], 'Xfile.fs') - split Xfile.fs - call assert_equal('forth', &filetype) + let g:filetype_pp = 'pascal' + split Xfile.pp + call assert_equal('pascal', &filetype) bwipe! + unlet g:filetype_pp - call writefile(['\ Forth line comment'], 'Xfile.fs') - split Xfile.fs - call assert_equal('forth', &filetype) + " Test dist#ft#FTpp() + call writefile(['{ pascal comment'], 'Xfile.pp') + split Xfile.pp + call assert_equal('pascal', &filetype) bwipe! - " empty line comment - no space required - call writefile(['\'], 'Xfile.fs') - split Xfile.fs - call assert_equal('forth', &filetype) + call writefile(['procedure pascal'], 'Xfile.pp') + split Xfile.pp + call assert_equal('pascal', &filetype) bwipe! - call writefile(['\G Forth documentation comment '], 'Xfile.fs') - split Xfile.fs - call assert_equal('forth', &filetype) + call delete('Xfile.pp') + filetype off +endfunc + +func Test_tex_file() + filetype on + + " only tests one case, should do more + let lines =<< trim END + % This is a sentence. + + This is a sentence. + END + call writefile(lines, "Xfile.tex") + split Xfile.tex + call assert_equal('plaintex', &filetype) + bwipe + + call delete('Xfile.tex') + filetype off +endfunc + +func Test_tf_file() + filetype on + + call writefile([';;; TF MUD client is super duper cool'], 'Xfile.tf') + split Xfile.tf + call assert_equal('tf', &filetype) bwipe! - call writefile([': squared ( n -- n^2 )', 'dup * ;'], 'Xfile.fs') - split Xfile.fs - call assert_equal('forth', &filetype) + call writefile(['provider "azurerm" {'], 'Xfile.tf') + split Xfile.tf + call assert_equal('terraform', &filetype) bwipe! - call delete('Xfile.fs') + call delete('Xfile.tf') filetype off endfunc -func Test_dep3patch_file() +func Test_ts_file() filetype on - call assert_true(mkdir('debian/patches', 'p')) - - " series files are not patches - call writefile(['Description: some awesome patch'], 'debian/patches/series') - split debian/patches/series - call assert_notequal('dep3patch', &filetype) + call writefile(['<?xml version="1.0" encoding="utf-8"?>'], 'Xfile.ts') + split Xfile.ts + call assert_equal('xml', &filetype) bwipe! - " diff/patch files without the right headers should still show up as ft=diff - call writefile([], 'debian/patches/foo.diff') - split debian/patches/foo.diff - call assert_equal('diff', &filetype) + call writefile(['// looks like Typescript'], 'Xfile.ts') + split Xfile.ts + call assert_equal('typescript', &filetype) bwipe! - " Files with the right headers are detected as dep3patch, even if they don't - " have a diff/patch extension - call writefile(['Subject: dep3patches'], 'debian/patches/bar') - split debian/patches/bar - call assert_equal('dep3patch', &filetype) + call delete('Xfile.ts') + filetype off +endfunc + +func Test_ttl_file() + filetype on + + call writefile(['@base <http://example.org/> .'], 'Xfile.ttl') + split Xfile.ttl + call assert_equal('turtle', &filetype) bwipe! - " Files in sub-directories are detected - call assert_true(mkdir('debian/patches/s390x', 'p')) - call writefile(['Subject: dep3patches'], 'debian/patches/s390x/bar') - split debian/patches/s390x/bar - call assert_equal('dep3patch', &filetype) + call writefile(['looks like Tera Term Language'], 'Xfile.ttl') + split Xfile.ttl + call assert_equal('teraterm', &filetype) bwipe! - " The detection stops when seeing the "header end" marker - call writefile(['---', 'Origin: the cloud'], 'debian/patches/baz') - split debian/patches/baz - call assert_notequal('dep3patch', &filetype) + call delete('Xfile.ttl') + filetype off +endfunc + +func Test_xpm_file() + filetype on + + call writefile(['this is XPM2'], 'file.xpm') + split file.xpm + call assert_equal('xpm2', &filetype) bwipe! - call delete('debian/patches', 'rf') + call delete('file.xpm') + filetype off endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_filetype_lua.vim b/src/nvim/testdir/test_filetype_lua.vim new file mode 100644 index 0000000000..f73e4ca33f --- /dev/null +++ b/src/nvim/testdir/test_filetype_lua.vim @@ -0,0 +1,2 @@ +let g:do_filetype_lua = 1 +source test_filetype.vim 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_filter_map.vim b/src/nvim/testdir/test_filter_map.vim index a52a66ac2f..1cd3a2287b 100644 --- a/src/nvim/testdir/test_filter_map.vim +++ b/src/nvim/testdir/test_filter_map.vim @@ -88,4 +88,14 @@ func Test_map_filter_fails() call assert_fails("let l = filter('abc', '\"> \" . v:val')", 'E896:') endfunc +func Test_map_and_modify() + let l = ["abc"] + " cannot change the list halfway a map() + call assert_fails('call map(l, "remove(l, 0)[0]")', 'E741:') + + let d = #{a: 1, b: 2, c: 3} + call assert_fails('call map(d, "remove(d, v:key)[0]")', 'E741:') + call assert_fails('echo map(d, {k,v -> remove(d, k)})', 'E741:') +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_findfile.vim b/src/nvim/testdir/test_findfile.vim index 5a20475d3d..1684c5d30a 100644 --- a/src/nvim/testdir/test_findfile.vim +++ b/src/nvim/testdir/test_findfile.vim @@ -226,4 +226,26 @@ func Test_find_cmd() call assert_fails('tabfind', 'E471:') endfunc +func Test_find_non_existing_path() + new + let save_path = &path + let save_dir = getcwd() + call mkdir('dir1/dir2', 'p') + call writefile([], 'dir1/file.txt') + call writefile([], 'dir1/dir2/base.txt') + call chdir('dir1/dir2') + e base.txt + set path=../include + + call assert_fails(':find file.txt', 'E345:') + + call chdir(save_dir) + bw! + call delete('dir1/dir2/base.txt', 'rf') + call delete('dir1/dir2', 'rf') + call delete('dir1/file.txt', 'rf') + call delete('dir1', 'rf') + let &path = save_path +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_fnamemodify.vim b/src/nvim/testdir/test_fnamemodify.vim index 411f7ebbb3..5ae2a5ee17 100644 --- a/src/nvim/testdir/test_fnamemodify.vim +++ b/src/nvim/testdir/test_fnamemodify.vim @@ -3,8 +3,10 @@ func Test_fnamemodify() let save_home = $HOME let save_shell = &shell + let save_shellslash = &shellslash let $HOME = fnamemodify('.', ':p:h:h') set shell=sh + set shellslash call assert_equal('/', fnamemodify('.', ':p')[-1:]) call assert_equal('r', fnamemodify('.', ':p:h')[-1:]) @@ -27,6 +29,21 @@ func Test_fnamemodify() call assert_equal('fb2.tar.gz', fnamemodify('abc.fb2.tar.gz', ':e:e:e')) call assert_equal('fb2.tar.gz', fnamemodify('abc.fb2.tar.gz', ':e:e:e:e')) call assert_equal('tar', fnamemodify('abc.fb2.tar.gz', ':e:e:r')) + call assert_equal(getcwd(), fnamemodify('', ':p:h')) + + let cwd = getcwd() + call chdir($HOME) + call assert_equal('foobar', fnamemodify('~/foobar', ':~:.')) + call chdir(cwd) + call mkdir($HOME . '/XXXXXXXX/a', 'p') + call mkdir($HOME . '/XXXXXXXX/b', 'p') + call chdir($HOME . '/XXXXXXXX/a/') + call assert_equal('foo', fnamemodify($HOME . '/XXXXXXXX/a/foo', ':p:~:.')) + call assert_equal('~/XXXXXXXX/b/foo', fnamemodify($HOME . '/XXXXXXXX/b/foo', ':p:~:.')) + call mkdir($HOME . '/XXXXXXXX/a.ext', 'p') + call assert_equal('~/XXXXXXXX/a.ext/foo', fnamemodify($HOME . '/XXXXXXXX/a.ext/foo', ':p:~:.')) + call chdir(cwd) + call delete($HOME . '/XXXXXXXX', 'rf') call assert_equal('''abc def''', fnamemodify('abc def', ':S')) call assert_equal('''abc" "def''', fnamemodify('abc" "def', ':S')) @@ -44,6 +61,7 @@ func Test_fnamemodify() let $HOME = save_home let &shell = save_shell + let &shellslash = save_shellslash endfunc func Test_fnamemodify_er() @@ -73,6 +91,7 @@ func Test_fnamemodify_er() call assert_equal('b.c', fnamemodify('a.b.c.d.e', ':r:r:e:e:e')) call assert_equal('b.c', fnamemodify('a.b.c.d.e', ':r:r:e:e:e:e')) + call assert_equal('', fnamemodify('', ':p:t')) call assert_equal('', fnamemodify(v:_null_string, v:_null_string)) endfunc diff --git a/src/nvim/testdir/test_functions.vim b/src/nvim/testdir/test_functions.vim index 0edbeb420a..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')) @@ -376,6 +345,25 @@ func Test_pathshorten() call assert_equal('~.f/bar', pathshorten('~.foo/bar')) call assert_equal('.~f/bar', pathshorten('.~foo/bar')) call assert_equal('~/f/bar', pathshorten('~/foo/bar')) + call assert_fails('call pathshorten([])', 'E730:') + + " test pathshorten with optional variable to set preferred size of shortening + call assert_equal('', pathshorten('', 2)) + call assert_equal('foo', pathshorten('foo', 2)) + call assert_equal('/foo', pathshorten('/foo', 2)) + call assert_equal('fo/', pathshorten('foo/', 2)) + call assert_equal('fo/bar', pathshorten('foo/bar', 2)) + call assert_equal('fo/ba/foobar', pathshorten('foo/bar/foobar', 2)) + call assert_equal('/fo/ba/foobar', pathshorten('/foo/bar/foobar', 2)) + call assert_equal('.fo/bar', pathshorten('.foo/bar', 2)) + call assert_equal('~fo/bar', pathshorten('~foo/bar', 2)) + call assert_equal('~.fo/bar', pathshorten('~.foo/bar', 2)) + call assert_equal('.~fo/bar', pathshorten('.~foo/bar', 2)) + call assert_equal('~/fo/bar', pathshorten('~/foo/bar', 2)) + call assert_fails('call pathshorten([],2)', 'E730:') + call assert_notequal('~/fo/bar', pathshorten('~/foo/bar', 3)) + call assert_equal('~/foo/bar', pathshorten('~/foo/bar', 3)) + call assert_equal('~/f/bar', pathshorten('~/foo/bar', 0)) endfunc func Test_strpart() @@ -1274,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, ... ) @@ -1331,68 +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 - -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 = '' @@ -1494,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 @@ -1519,24 +1480,31 @@ func Test_libcall_libcallnr() let libc = 'msvcrt.dll' elseif has('mac') let libc = 'libSystem.B.dylib' - elseif system('uname -s') =~ 'SunOS' - " Set the path to libc.so according to the architecture. - let test_bits = system('file ' . GetVimProg()) - let test_arch = system('uname -p') - if test_bits =~ '64-bit' && test_arch =~ 'sparc' - let libc = '/usr/lib/sparcv9/libc.so' - elseif test_bits =~ '64-bit' && test_arch =~ 'i386' - let libc = '/usr/lib/amd64/libc.so' + elseif executable('ldd') + let libc = matchstr(split(system('ldd ' . GetVimProg())), '/libc\.so\>') + endif + if get(l:, 'libc', '') ==# '' + " On Unix, libc.so can be in various places. + if has('linux') + " There is not documented but regarding the 1st argument of glibc's + " dlopen an empty string and nullptr are equivalent, so using an empty + " string for the 1st argument of libcall allows to call functions. + let libc = '' + elseif has('sun') + " Set the path to libc.so according to the architecture. + let test_bits = system('file ' . GetVimProg()) + let test_arch = system('uname -p') + if test_bits =~ '64-bit' && test_arch =~ 'sparc' + let libc = '/usr/lib/sparcv9/libc.so' + elseif test_bits =~ '64-bit' && test_arch =~ 'i386' + let libc = '/usr/lib/amd64/libc.so' + else + let libc = '/usr/lib/libc.so' + endif else - let libc = '/usr/lib/libc.so' + " Unfortunately skip this test until a good way is found. + return endif - elseif system('uname -s') =~ 'OpenBSD' - let libc = 'libc.so' - else - " On Unix, libc.so can be in various places. - " Interestingly, using an empty string for the 1st argument of libcall - " allows to call functions from libc which is not documented. - let libc = '' endif if has('win32') @@ -1559,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() @@ -1658,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:') @@ -1675,6 +1735,33 @@ func Test_nr2char() call assert_equal("\x80\xfc\b\xfd\x80\xfeX\x80\xfeX\x80\xfeX\x80\xfeX\x80\xfeX", eval('"\<M-' .. nr2char(0x40000000) .. '>"')) endfunc +" Test for getcurpos() and setpos() +func Test_getcurpos_setpos() + new + call setline(1, ['012345678', '012345678']) + normal gg6l + let sp = getcurpos() + normal 0 + call setpos('.', sp) + normal jyl + call assert_equal('6', @") + call assert_equal(-1, setpos('.', v:_null_list)) + call assert_equal(-1, setpos('.', {})) + + let winid = win_getid() + normal G$ + let pos = getcurpos() + wincmd w + call assert_equal(pos, getcurpos(winid)) + + wincmd w + close! + + call assert_equal(getcurpos(), getcurpos(0)) + call assert_equal([0, 0, 0, 0, 0], getcurpos(-1)) + call assert_equal([0, 0, 0, 0, 0], getcurpos(1999)) +endfunc + func HasDefault(msg = 'msg') return a:msg endfunc diff --git a/src/nvim/testdir/test_gf.vim b/src/nvim/testdir/test_gf.vim index 43efd6248e..589899f532 100644 --- a/src/nvim/testdir/test_gf.vim +++ b/src/nvim/testdir/test_gf.vim @@ -19,11 +19,7 @@ func Test_gf_url() call search("^second") call search("URL") call assert_equal("URL://machine.name/tmp/vimtest2b", expand("<cfile>")) - if has("ebcdic") - set isf=@,240-249,/,.,-,_,+,,,$,:,~,\ - else - set isf=@,48-57,/,.,-,_,+,,,$,~,\ - endif + set isf=@,48-57,/,.,-,_,+,,,$,~,\ call search("^third") call search("name") call assert_equal("URL:\\\\machine.name\\vimtest2c", expand("<cfile>")) @@ -76,11 +72,7 @@ endfunc " Test for invoking 'gf' on a ${VAR} variable func Test_gf() - if has("ebcdic") - set isfname=@,240-249,/,.,-,_,+,,,$,:,~,{,} - else - set isfname=@,48-57,/,.,-,_,+,,,$,:,~,{,} - endif + set isfname=@,48-57,/,.,-,_,+,,,$,:,~,{,} call writefile(["Test for gf command"], "Xtest1") if has("unix") 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 8e59efd22d..b2d943be00 100644 --- a/src/nvim/testdir/test_help.vim +++ b/src/nvim/testdir/test_help.vim @@ -1,4 +1,3 @@ - " Tests for :help func Test_help_restore_snapshot() @@ -13,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 @@ -101,4 +112,13 @@ func Test_helptag_cmd() call delete('Xdir', 'rf') endfunc +func Test_help_long_argument() + try + exe 'help \%' .. repeat('0', 1021) + catch + call assert_match("E149:", v:exception) + endtry +endfunc + + " vim: shiftwidth=2 sts=2 expandtab 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_ins_complete.vim b/src/nvim/testdir/test_ins_complete.vim index ce75799551..186fa8871f 100644 --- a/src/nvim/testdir/test_ins_complete.vim +++ b/src/nvim/testdir/test_ins_complete.vim @@ -109,7 +109,7 @@ func s:CompleteDone_CompleteFuncNone( findstart, base ) return v:none endfunc -function! s:CompleteDone_CompleteFuncDict( findstart, base ) +func s:CompleteDone_CompleteFuncDict( findstart, base ) if a:findstart return 0 endif @@ -126,7 +126,7 @@ function! s:CompleteDone_CompleteFuncDict( findstart, base ) \ } \ ] \ } -endfunction +endfunc func s:CompleteDone_CheckCompletedItemNone() let s:called_completedone = 1 @@ -341,6 +341,14 @@ func Test_compl_feedkeys() set completeopt& endfunc +func s:ComplInCmdwin_GlobalCompletion(a, l, p) + return 'global' +endfunc + +func s:ComplInCmdwin_LocalCompletion(a, l, p) + return 'local' +endfunc + func Test_compl_in_cmdwin() set wildmenu wildchar=<Tab> com! -nargs=1 -complete=command GetInput let input = <q-args> @@ -376,6 +384,47 @@ func Test_compl_in_cmdwin() call feedkeys("q::GetInput b:test_\<Tab>\<CR>:q\<CR>", 'tx!') call assert_equal('b:test_', input) + + " Argument completion of buffer-local command + func s:ComplInCmdwin_GlobalCompletionList(a, l, p) + return ['global'] + endfunc + + func s:ComplInCmdwin_LocalCompletionList(a, l, p) + return ['local'] + endfunc + + func s:ComplInCmdwin_CheckCompletion(arg) + call assert_equal('local', a:arg) + endfunc + + com! -nargs=1 -complete=custom,<SID>ComplInCmdwin_GlobalCompletion + \ TestCommand call s:ComplInCmdwin_CheckCompletion(<q-args>) + com! -buffer -nargs=1 -complete=custom,<SID>ComplInCmdwin_LocalCompletion + \ TestCommand call s:ComplInCmdwin_CheckCompletion(<q-args>) + call feedkeys("q:iTestCommand \<Tab>\<CR>", 'tx!') + + com! -nargs=1 -complete=customlist,<SID>ComplInCmdwin_GlobalCompletionList + \ TestCommand call s:ComplInCmdwin_CheckCompletion(<q-args>) + com! -buffer -nargs=1 -complete=customlist,<SID>ComplInCmdwin_LocalCompletionList + \ TestCommand call s:ComplInCmdwin_CheckCompletion(<q-args>) + + call feedkeys("q:iTestCommand \<Tab>\<CR>", 'tx!') + + func! s:ComplInCmdwin_CheckCompletion(arg) + call assert_equal('global', a:arg) + endfunc + new + call feedkeys("q:iTestCommand \<Tab>\<CR>", 'tx!') + quit + + delfunc s:ComplInCmdwin_GlobalCompletion + delfunc s:ComplInCmdwin_LocalCompletion + delfunc s:ComplInCmdwin_GlobalCompletionList + delfunc s:ComplInCmdwin_LocalCompletionList + delfunc s:ComplInCmdwin_CheckCompletion + + delcom -buffer TestCommand delcom TestCommand delcom GetInput unlet w:test_winvar @@ -445,6 +494,28 @@ func Test_issue_7021() set completeslash= endfunc +func Test_pum_stopped_by_timer() + CheckScreendump + + let lines =<< trim END + call setline(1, ['hello', 'hullo', 'heeee', '']) + func StartCompl() + call timer_start(100, { -> execute('stopinsert') }) + call feedkeys("Gah\<C-N>") + endfunc + END + + call writefile(lines, 'Xpumscript') + let buf = RunVimInTerminal('-S Xpumscript', #{rows: 12}) + call term_sendkeys(buf, ":call StartCompl()\<CR>") + call TermWait(buf, 200) + call term_sendkeys(buf, "k") + call VerifyScreenDump(buf, 'Test_pum_stopped_by_timer', {}) + + call StopVimInTerminal(buf) + call delete('Xpumscript') +endfunc + func Test_pum_with_folds_two_tabs() CheckScreendump diff --git a/src/nvim/testdir/test_lambda.vim b/src/nvim/testdir/test_lambda.vim index 63bb4ae1ef..c1fe47d1c9 100644 --- a/src/nvim/testdir/test_lambda.vim +++ b/src/nvim/testdir/test_lambda.vim @@ -1,24 +1,24 @@ " Test for lambda and closure -function! Test_lambda_feature() +func Test_lambda_feature() call assert_equal(1, has('lambda')) -endfunction +endfunc -function! Test_lambda_with_filter() +func Test_lambda_with_filter() let s:x = 2 call assert_equal([2, 3], filter([1, 2, 3], {i, v -> v >= s:x})) -endfunction +endfunc -function! Test_lambda_with_map() +func Test_lambda_with_map() let s:x = 1 call assert_equal([2, 3, 4], map([1, 2, 3], {i, v -> v + s:x})) -endfunction +endfunc -function! Test_lambda_with_sort() +func Test_lambda_with_sort() call assert_equal([1, 2, 3, 4, 7], sort([3,7,2,1,4], {a, b -> a - b})) -endfunction +endfunc -function! Test_lambda_with_timer() +func Test_lambda_with_timer() if !has('timers') return endif @@ -54,10 +54,10 @@ function! Test_lambda_with_timer() call assert_true(s:n > m) endfunc -function! Test_lambda_with_partial() +func Test_lambda_with_partial() let l:Cb = function({... -> ['zero', a:1, a:2, a:3]}, ['one', 'two']) call assert_equal(['zero', 'one', 'two', 'three'], l:Cb('three')) -endfunction +endfunc function Test_lambda_fails() call assert_equal(3, {a, b -> a + b}(1, 2)) @@ -70,59 +70,59 @@ func Test_not_lambda() call assert_equal('foo', x['>']) endfunc -function! Test_lambda_capture_by_reference() +func Test_lambda_capture_by_reference() let v = 1 let l:F = {x -> x + v} let v = 2 call assert_equal(12, l:F(10)) -endfunction +endfunc -function! Test_lambda_side_effect() - function! s:update_and_return(arr) +func Test_lambda_side_effect() + func! s:update_and_return(arr) let a:arr[1] = 5 return a:arr - endfunction + endfunc - function! s:foo(arr) + func! s:foo(arr) return {-> s:update_and_return(a:arr)} - endfunction + endfunc let arr = [3,2,1] call assert_equal([3, 5, 1], s:foo(arr)()) -endfunction +endfunc -function! Test_lambda_refer_local_variable_from_other_scope() - function! s:foo(X) +func Test_lambda_refer_local_variable_from_other_scope() + func! s:foo(X) return a:X() " refer l:x in s:bar() - endfunction + endfunc - function! s:bar() + func! s:bar() let x = 123 return s:foo({-> x}) - endfunction + endfunc call assert_equal(123, s:bar()) -endfunction +endfunc -function! Test_lambda_do_not_share_local_variable() - function! s:define_funcs() +func Test_lambda_do_not_share_local_variable() + func! s:define_funcs() let l:One = {-> split(execute("let a = 'abc' | echo a"))[0]} let l:Two = {-> exists("a") ? a : "no"} return [l:One, l:Two] - endfunction + endfunc let l:F = s:define_funcs() call assert_equal('no', l:F[1]()) call assert_equal('abc', l:F[0]()) call assert_equal('no', l:F[1]()) -endfunction +endfunc -function! Test_lambda_closure_counter() - function! s:foo() +func Test_lambda_closure_counter() + func! s:foo() let x = 0 return {-> [execute("let x += 1"), x][-1]} - endfunction + endfunc let l:F = s:foo() call garbagecollect() @@ -130,52 +130,52 @@ function! Test_lambda_closure_counter() call assert_equal(2, l:F()) call assert_equal(3, l:F()) call assert_equal(4, l:F()) -endfunction +endfunc -function! Test_lambda_with_a_var() - function! s:foo() +func Test_lambda_with_a_var() + func! s:foo() let x = 2 return {... -> a:000 + [x]} - endfunction - function! s:bar() + endfunc + func! s:bar() return s:foo()(1) - endfunction + endfunc call assert_equal([1, 2], s:bar()) -endfunction +endfunc -function! Test_lambda_call_lambda_from_lambda() - function! s:foo(x) +func Test_lambda_call_lambda_from_lambda() + func! s:foo(x) let l:F1 = {-> {-> a:x}} return {-> l:F1()} - endfunction + endfunc let l:F = s:foo(1) call assert_equal(1, l:F()()) -endfunction +endfunc -function! Test_lambda_delfunc() - function! s:gen() +func Test_lambda_delfunc() + func! s:gen() let pl = l: let l:Foo = {-> get(pl, "Foo", get(pl, "Bar", {-> 0}))} let l:Bar = l:Foo delfunction l:Foo return l:Bar - endfunction + endfunc let l:F = s:gen() call assert_fails(':call l:F()', 'E933:') -endfunction +endfunc -function! Test_lambda_scope() - function! s:NewCounter() +func Test_lambda_scope() + func! s:NewCounter() let c = 0 return {-> [execute('let c += 1'), c][-1]} - endfunction + endfunc - function! s:NewCounter2() + func! s:NewCounter2() return {-> [execute('let c += 100'), c][-1]} - endfunction + endfunc let l:C = s:NewCounter() let l:D = s:NewCounter2() @@ -183,37 +183,37 @@ function! Test_lambda_scope() call assert_equal(1, l:C()) call assert_fails(':call l:D()', 'E121:') call assert_equal(2, l:C()) -endfunction +endfunc -function! Test_lambda_share_scope() - function! s:New() +func Test_lambda_share_scope() + func! s:New() let c = 0 let l:Inc0 = {-> [execute('let c += 1'), c][-1]} let l:Dec0 = {-> [execute('let c -= 1'), c][-1]} return [l:Inc0, l:Dec0] - endfunction + endfunc let [l:Inc, l:Dec] = s:New() call assert_equal(1, l:Inc()) call assert_equal(2, l:Inc()) call assert_equal(1, l:Dec()) -endfunction +endfunc -function! Test_lambda_circular_reference() - function! s:Foo() +func Test_lambda_circular_reference() + func! s:Foo() let d = {} let d.f = {-> d} return d.f - endfunction + endfunc call s:Foo() call garbagecollect() let i = 0 | while i < 10000 | call s:Foo() | let i+= 1 | endwhile call garbagecollect() -endfunction +endfunc -function! Test_lambda_combination() +func Test_lambda_combination() call assert_equal(2, {x -> {x -> x}}(1)(2)) call assert_equal(10, {y -> {x -> x(y)(10)}({y -> y})}({z -> z})) call assert_equal(5.0, {x -> {y -> x / y}}(10)(2.0)) @@ -226,17 +226,17 @@ function! Test_lambda_combination() let Z = {f -> {x -> f({y -> x(x)(y)})}({x -> f({y -> x(x)(y)})})} let Fact = {f -> {x -> x == 0 ? 1 : x * f(x - 1)}} call assert_equal(120, Z(Fact)(5)) -endfunction +endfunc -function! Test_closure_counter() - function! s:foo() +func Test_closure_counter() + func! s:foo() let x = 0 - function! s:bar() closure + func! s:bar() closure let x += 1 return x - endfunction + endfunc return function('s:bar') - endfunction + endfunc let l:F = s:foo() call garbagecollect() @@ -244,30 +244,30 @@ function! Test_closure_counter() call assert_equal(2, l:F()) call assert_equal(3, l:F()) call assert_equal(4, l:F()) -endfunction +endfunc -function! Test_closure_unlet() - function! s:foo() +func Test_closure_unlet() + func! s:foo() let x = 1 - function! s:bar() closure + func! s:bar() closure unlet x - endfunction + endfunc call s:bar() return l: - endfunction + endfunc call assert_false(has_key(s:foo(), 'x')) call garbagecollect() -endfunction +endfunc -function! LambdaFoo() +func LambdaFoo() let x = 0 - function! LambdaBar() closure + func! LambdaBar() closure let x += 1 return x - endfunction + endfunc return function('LambdaBar') -endfunction +endfunc func Test_closure_refcount() let g:Count = LambdaFoo() @@ -303,3 +303,8 @@ func Test_lambda_with_index() let Extract = {-> function(List, ['foobar'])()[0]} call assert_equal('foobar', Extract()) endfunc + +func Test_lambda_error() + " This was causing a crash + call assert_fails('ec{@{->{d->()()', 'E15') +endfunc diff --git a/src/nvim/testdir/test_listchars.vim b/src/nvim/testdir/test_listchars.vim index 0bcbd9c4a5..c6e2ebd406 100644 --- a/src/nvim/testdir/test_listchars.vim +++ b/src/nvim/testdir/test_listchars.vim @@ -1,6 +1,8 @@ " Tests for 'listchars' display with 'list' and :list +source check.vim source view_util.vim +source screendump.vim func Test_listchars() enew! @@ -517,4 +519,34 @@ func Test_listchars_window_local() set list& listchars& endfunc +func Test_listchars_foldcolumn() + CheckScreendump + + let lines =<< trim END + call setline(1, ['aaa', '', 'a', 'aaaaaa']) + vsplit + vsplit + windo set signcolumn=yes foldcolumn=1 winminwidth=0 nowrap list listchars=extends:>,precedes:< + END + call writefile(lines, 'XTest_listchars') + + let buf = RunVimInTerminal('-S XTest_listchars', {'rows': 10, 'cols': 60}) + + call term_sendkeys(buf, "13\<C-W>>") + call VerifyScreenDump(buf, 'Test_listchars_01', {}) + call term_sendkeys(buf, "\<C-W>>") + call VerifyScreenDump(buf, 'Test_listchars_02', {}) + call term_sendkeys(buf, "\<C-W>>") + call VerifyScreenDump(buf, 'Test_listchars_03', {}) + call term_sendkeys(buf, "\<C-W>>") + call VerifyScreenDump(buf, 'Test_listchars_04', {}) + call term_sendkeys(buf, "\<C-W>>") + call VerifyScreenDump(buf, 'Test_listchars_05', {}) + + " clean up + call StopVimInTerminal(buf) + call delete('XTest_listchars') +endfunc + + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_listdict.vim b/src/nvim/testdir/test_listdict.vim index f6c404d390..aa66d86af1 100644 --- a/src/nvim/testdir/test_listdict.vim +++ b/src/nvim/testdir/test_listdict.vim @@ -516,22 +516,22 @@ func Test_dict_lock_operator() endfunc " No remove() of write-protected scope-level variable -func! Tfunc(this_is_a_long_parameter_name) +func Tfunc1(this_is_a_long_parameter_name) call assert_fails("call remove(a:, 'this_is_a_long_parameter_name')", 'E742') -endfun +endfunc func Test_dict_scope_var_remove() - call Tfunc('testval') + call Tfunc1('testval') endfunc " No extend() of write-protected scope-level variable func Test_dict_scope_var_extend() call assert_fails("call extend(a:, {'this_is_a_long_parameter_name': 1234})", 'E742') endfunc -func! Tfunc(this_is_a_long_parameter_name) +func Tfunc2(this_is_a_long_parameter_name) call assert_fails("call extend(a:, {'this_is_a_long_parameter_name': 1234})", 'E742') endfunc func Test_dict_scope_var_extend_overwrite() - call Tfunc('testval') + call Tfunc2('testval') endfunc " No :unlet of variable in locked scope @@ -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_listlbr_utf8.vim b/src/nvim/testdir/test_listlbr_utf8.vim index c38e0c5f3c..1f100d6244 100644 --- a/src/nvim/testdir/test_listlbr_utf8.vim +++ b/src/nvim/testdir/test_listlbr_utf8.vim @@ -69,6 +69,16 @@ func Test_nolinebreak_with_list() call s:close_windows() endfunc +" this was causing a crash +func Test_linebreak_with_list_and_tabs() + set linebreak list listchars=tab:⇤\ ⇥ tabstop=100 + new + call setline(1, "\t\t\ttext") + redraw + bwipe! + set nolinebreak nolist listchars&vim tabstop=8 +endfunc + func Test_linebreak_with_nolist() call s:test_windows('setl nolist') call setline(1, "\t*mask = nil;") diff --git a/src/nvim/testdir/test_mapping.vim b/src/nvim/testdir/test_mapping.vim index f88e8cf843..1080a3c85b 100644 --- a/src/nvim/testdir/test_mapping.vim +++ b/src/nvim/testdir/test_mapping.vim @@ -1,6 +1,8 @@ " Tests for mappings and abbreviations source shared.vim +source check.vim +source screendump.vim func Test_abbreviation() " abbreviation with 0x80 should work @@ -451,6 +453,82 @@ func Test_expr_map_gets_cursor() nunmap ! endfunc +func Test_expr_map_restore_cursor() + CheckScreendump + + let lines =<< trim END + call setline(1, ['one', 'two', 'three']) + 2 + set ls=2 + hi! link StatusLine ErrorMsg + noremap <expr> <C-B> Func() + func Func() + let g:on = !get(g:, 'on', 0) + redraws + return '' + endfunc + func Status() + return get(g:, 'on', 0) ? '[on]' : '' + endfunc + set stl=%{Status()} + END + call writefile(lines, 'XtestExprMap') + let buf = RunVimInTerminal('-S XtestExprMap', #{rows: 10}) + call term_sendkeys(buf, "\<C-B>") + call VerifyScreenDump(buf, 'Test_map_expr_1', {}) + + " clean up + call StopVimInTerminal(buf) + call delete('XtestExprMap') +endfunc + +func Test_map_listing() + CheckScreendump + + let lines =<< trim END + nmap a b + END + call writefile(lines, 'XtestMapList') + let buf = RunVimInTerminal('-S XtestMapList', #{rows: 6}) + call term_sendkeys(buf, ": nmap a\<CR>") + call VerifyScreenDump(buf, 'Test_map_list_1', {}) + + " clean up + call StopVimInTerminal(buf) + call delete('XtestMapList') +endfunc + +func Test_expr_map_error() + CheckScreendump + + let lines =<< trim END + func Func() + throw 'test' + return '' + endfunc + + nnoremap <expr> <F2> Func() + cnoremap <expr> <F2> Func() + + call test_override('ui_delay', 10) + END + call writefile(lines, 'XtestExprMap') + let buf = RunVimInTerminal('-S XtestExprMap', #{rows: 10}) + call term_sendkeys(buf, "\<F2>") + call TermWait(buf) + call term_sendkeys(buf, "\<CR>") + call VerifyScreenDump(buf, 'Test_map_expr_2', {}) + + call term_sendkeys(buf, ":abc\<F2>") + call VerifyScreenDump(buf, 'Test_map_expr_3', {}) + call term_sendkeys(buf, "\<Esc>0") + call VerifyScreenDump(buf, 'Test_map_expr_4', {}) + + " clean up + call StopVimInTerminal(buf) + call delete('XtestExprMap') +endfunc + " Test for mapping errors func Test_map_error() call assert_fails('unmap', 'E474:') diff --git a/src/nvim/testdir/test_marks.vim b/src/nvim/testdir/test_marks.vim index b3035d73ce..00ee8f6d6a 100644 --- a/src/nvim/testdir/test_marks.vim +++ b/src/nvim/testdir/test_marks.vim @@ -1,6 +1,6 @@ " Test that a deleted mark is restored after delete-undo-redo-undo. -function! Test_Restore_DelMark() +func Test_Restore_DelMark() enew! call append(0, [" textline A", " textline B", " textline C"]) normal! 2gg @@ -11,10 +11,10 @@ function! Test_Restore_DelMark() call assert_equal(2, pos[1]) call assert_equal(1, pos[2]) enew! -endfunction +endfunc " Test that CTRL-A and CTRL-X updates last changed mark '[, ']. -function! Test_Incr_Marks() +func Test_Incr_Marks() enew! call append(0, ["123 123 123", "123 123 123", "123 123 123"]) normal! gg @@ -23,7 +23,17 @@ function! Test_Incr_Marks() call assert_equal("123 XXXXXXX", getline(2)) call assert_equal("XXX 123 123", getline(3)) enew! -endfunction +endfunc + +func Test_previous_jump_mark() + new + call setline(1, ['']->repeat(6)) + normal Ggg + call assert_equal(6, getpos("''")[1]) + normal jjjjj + call assert_equal(6, getpos("''")[1]) + bwipe! +endfunc func Test_setpos() new Xone @@ -207,6 +217,21 @@ func Test_mark_error() call assert_fails('mark _', 'E191:') endfunc +" Test for :lockmarks when pasting content +func Test_lockmarks_with_put() + new + call append(0, repeat(['sky is blue'], 4)) + normal gg + 1,2yank r + put r + normal G + lockmarks put r + call assert_equal(2, line("'[")) + call assert_equal(3, line("']")) + + bwipe! +endfunc + " Test for the getmarklist() function func Test_getmarklist() new @@ -231,3 +256,5 @@ func Test_getmarklist() call assert_equal([], {}->getmarklist()) close! endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_matchadd_conceal.vim b/src/nvim/testdir/test_matchadd_conceal.vim index 2cbaf5cb76..29a2c30b0d 100644 --- a/src/nvim/testdir/test_matchadd_conceal.vim +++ b/src/nvim/testdir/test_matchadd_conceal.vim @@ -7,7 +7,7 @@ source shared.vim source term_util.vim source view_util.vim -function! Test_simple_matchadd() +func Test_simple_matchadd() new 1put='# This is a Test' @@ -333,7 +333,7 @@ func Test_matchadd_and_syn_conceal() call assert_notequal(screenattr(1, 10) , screenattr(1, 11)) call assert_notequal(screenattr(1, 11) , screenattr(1, 12)) call assert_equal(screenattr(1, 11) , screenattr(1, 32)) -endfunction +endfunc func Test_cursor_column_in_concealed_line_after_window_scroll() CheckRunVimInTerminal diff --git a/src/nvim/testdir/test_matchadd_conceal_utf8.vim b/src/nvim/testdir/test_matchadd_conceal_utf8.vim index 7bfac13ad8..1d0c740734 100644 --- a/src/nvim/testdir/test_matchadd_conceal_utf8.vim +++ b/src/nvim/testdir/test_matchadd_conceal_utf8.vim @@ -3,19 +3,19 @@ if !has('conceal') finish endif -function! s:screenline(lnum) abort +func s:screenline(lnum) abort let line = [] for c in range(1, winwidth(0)) call add(line, nr2char(a:lnum->screenchar(c))) endfor return s:trim(join(line, '')) -endfunction +endfunc -function! s:trim(str) abort +func s:trim(str) abort return matchstr(a:str,'^\s*\zs.\{-}\ze\s*$') -endfunction +endfunc -function! Test_match_using_multibyte_conceal_char() +func Test_match_using_multibyte_conceal_char() new setlocal concealcursor=n conceallevel=1 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_messages.vim b/src/nvim/testdir/test_messages.vim index e0286548d9..9c84d77dd2 100644 --- a/src/nvim/testdir/test_messages.vim +++ b/src/nvim/testdir/test_messages.vim @@ -40,7 +40,7 @@ endfunc " indicator (e.g., "-- INSERT --") when ":stopinsert" is invoked. Message " output could then be disturbed when 'cmdheight' was greater than one. " This test ensures that the bugfix for this issue remains in place. -function! Test_stopinsert_does_not_break_message_output() +func Test_stopinsert_does_not_break_message_output() set cmdheight=2 redraw! @@ -55,7 +55,7 @@ function! Test_stopinsert_does_not_break_message_output() redraw! set cmdheight& -endfunction +endfunc func Test_message_completion() call feedkeys(":message \<C-A>\<C-B>\"\<CR>", 'tx') diff --git a/src/nvim/testdir/test_mksession.vim b/src/nvim/testdir/test_mksession.vim index 057895047d..a8e50af510 100644 --- a/src/nvim/testdir/test_mksession.vim +++ b/src/nvim/testdir/test_mksession.vim @@ -309,6 +309,31 @@ func Test_mksession_buffer_count() set nohidden endfunc +func Test_mksession_buffer_order() + %bwipe! + e Xfoo | e Xbar | e Xbaz | e Xqux + bufdo write + mksession! Xtest_mks.out + + " Verify that loading the session preserves order of buffers + %bwipe! + source Xtest_mks.out + + let s:buf_info = getbufinfo() + call assert_true(s:buf_info[0]['name'] =~# 'Xfoo$') + call assert_true(s:buf_info[1]['name'] =~# 'Xbar$') + call assert_true(s:buf_info[2]['name'] =~# 'Xbaz$') + call assert_true(s:buf_info[3]['name'] =~# 'Xqux$') + + " Clean up. + call delete('Xfoo') + call delete('Xbar') + call delete('Xbaz') + call delete('Xqux') + call delete('Xtest_mks.out') + %bwipe! +endfunc + if has('extra_search') func Test_mksession_hlsearch() @@ -696,6 +721,36 @@ func Test_mksession_foldopt() set sessionoptions& endfunc +" Test for mksession with "help" but not "options" in 'sessionoptions' +func Test_mksession_help_noopt() + set sessionoptions-=options + set sessionoptions+=help + help + let fname = expand('%') + mksession! Xtest_mks.out + bwipe + + source Xtest_mks.out + call assert_equal('help', &buftype) + call assert_equal('help', &filetype) + call assert_equal(fname, expand('%')) + call assert_false(&modifiable) + call assert_true(&readonly) + + helpclose + help index + let fname = expand('%') + mksession! Xtest_mks.out + bwipe + + source Xtest_mks.out + call assert_equal('help', &buftype) + call assert_equal(fname, expand('%')) + + call delete('Xtest_mks.out') + set sessionoptions& +endfunc + " Test for mksession with window position func Test_mksession_winpos() if !has('gui_running') diff --git a/src/nvim/testdir/test_normal.vim b/src/nvim/testdir/test_normal.vim index aff22f5d01..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) @@ -1812,7 +1812,15 @@ fun! Test_normal33_g_cmd2() call assert_equal(87, col('.')) call assert_equal('E', getreg(0)) + " Test for gM with Tab characters + call setline('.', "\ta\tb\tc\td\te\tf") + norm! gMyl + call assert_equal(6, col('.')) + call assert_equal("c", getreg(0)) + " Test for g Ctrl-G + call setline('.', lineC) + norm! 60gMyl set ff=unix let a=execute(":norm! g\<c-g>") call assert_match('Col 87 of 144; Line 2 of 2; Word 1 of 1; Byte 88 of 146', a) @@ -2759,4 +2767,37 @@ func Test_normal_count_after_operator() bw! endfunc +func Test_normal_gj_on_extra_wide_char() + new | 25vsp + let text='1 foooooooo ar e inszwe1 foooooooo inszwei' . + \ ' i drei vier fünf sechs sieben acht un zehn elf zwöfl' . + \ ' dreizehn v ierzehn fünfzehn' + put =text + call cursor(2,1) + norm! gj + call assert_equal([0,2,25,0], getpos('.')) + 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_number.vim b/src/nvim/testdir/test_number.vim index d737ebe9f0..dfbdc0bffd 100644 --- a/src/nvim/testdir/test_number.vim +++ b/src/nvim/testdir/test_number.vim @@ -284,10 +284,10 @@ func Test_relativenumber_colors() " Default colors call VerifyScreenDump(buf, 'Test_relnr_colors_1', {}) - call term_sendkeys(buf, ":hi LineNrAbove ctermfg=blue\<CR>") + call term_sendkeys(buf, ":hi LineNrAbove ctermfg=blue\<CR>:\<CR>") call VerifyScreenDump(buf, 'Test_relnr_colors_2', {}) - call term_sendkeys(buf, ":hi LineNrBelow ctermfg=green\<CR>") + call term_sendkeys(buf, ":hi LineNrBelow ctermfg=green\<CR>:\<CR>") call VerifyScreenDump(buf, 'Test_relnr_colors_3', {}) call term_sendkeys(buf, ":hi clear LineNrAbove\<CR>") diff --git a/src/nvim/testdir/test_options.vim b/src/nvim/testdir/test_options.vim index 5946732937..8612b7013b 100644 --- a/src/nvim/testdir/test_options.vim +++ b/src/nvim/testdir/test_options.vim @@ -22,16 +22,16 @@ func Test_whichwrap() call assert_equal('h', &whichwrap) set whichwrap& -endfunction +endfunc -function! Test_isfname() +func Test_isfname() " This used to cause Vim to access uninitialized memory. set isfname= call assert_equal("~X", expand("~X")) set isfname& -endfunction +endfunc -function Test_wildchar() +func Test_wildchar() " Empty 'wildchar' used to access invalid memory. call assert_fails('set wildchar=', 'E521:') call assert_fails('set wildchar=abc', 'E521:') @@ -42,7 +42,7 @@ function Test_wildchar() let a=execute('set wildchar?') call assert_equal("\n wildchar=<Esc>", a) set wildchar& -endfunction +endfunc func Test_wildoptions() set wildoptions= @@ -51,7 +51,7 @@ func Test_wildoptions() call assert_equal('tagfile', &wildoptions) endfunc -function! Test_options() +func Test_options_command() let caught = 'ok' try options @@ -88,9 +88,9 @@ function! Test_options() " close option-window close -endfunction +endfunc -function! Test_path_keep_commas() +func Test_path_keep_commas() " Test that changing 'path' keeps two commas. set path=foo,,bar set path-=bar @@ -98,7 +98,7 @@ function! Test_path_keep_commas() call assert_equal('foo,,bar', &path) set path& -endfunction +endfunc func Test_filetype_valid() set ft=valid_name @@ -259,6 +259,8 @@ func Test_set_errors() call assert_fails('set shiftwidth=-1', 'E487:') call assert_fails('set sidescroll=-1', 'E487:') call assert_fails('set tabstop=-1', 'E487:') + call assert_fails('set tabstop=10000', 'E474:') + call assert_fails('set tabstop=5500000000', 'E474:') call assert_fails('set textwidth=-1', 'E487:') call assert_fails('set timeoutlen=-1', 'E487:') call assert_fails('set updatecount=-1', 'E487:') @@ -312,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') @@ -368,6 +399,13 @@ func Test_set_all() set tw& iskeyword& splitbelow& endfunc +func Test_set_one_column() + let out_mult = execute('set all')->split("\n") + let out_one = execute('set! all')->split("\n") + " one column should be two to four times as many lines + call assert_inrange(len(out_mult) * 2, len(out_mult) * 4, len(out_one)) +endfunc + func Test_set_values() " The file is only generated when running "make test" in the src directory. if filereadable('opt_test.vim') @@ -647,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 f42b177c50..65232175c6 100644 --- a/src/nvim/testdir/test_put.vim +++ b/src/nvim/testdir/test_put.vim @@ -1,5 +1,7 @@ " Tests for put commands, e.g. ":put", "p", "gp", "P", "gP", etc. +source check.vim + func Test_put_block() new call feedkeys("i\<C-V>u2500\<CR>x\<ESC>", 'x') @@ -112,15 +114,92 @@ func Test_put_p_indent_visual() bwipe! endfunc +func Test_gp_with_count_leaves_cursor_at_end() + new + call setline(1, '<---->') + call setreg('@', "foo\nbar", 'c') + normal 1G3|3gp + call assert_equal([0, 4, 4, 0], getpos(".")) + call assert_equal(['<--foo', 'barfoo', 'barfoo', 'bar-->'], getline(1, '$')) + call assert_equal([0, 4, 3, 0], getpos("']")) + + bwipe! +endfunc + +func Test_p_with_count_leaves_mark_at_end() + new + call setline(1, '<---->') + call setreg('@', "start\nend", 'c') + normal 1G3|3p + call assert_equal([0, 1, 4, 0], getpos(".")) + call assert_equal(['<--start', 'endstart', 'endstart', 'end-->'], getline(1, '$')) + call assert_equal([0, 4, 3, 0], getpos("']")) + + 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' + silent! normal 0o00 + 0put + call assert_equal('text', getline(1)) + bwipe! +endfunc + func Test_multibyte_op_end_mark() - new - call setline(1, 'тест') - normal viwdp - call assert_equal([0, 1, 7, 0], getpos("'>")) - call assert_equal([0, 1, 7, 0], getpos("']")) - - normal Vyp - call assert_equal([0, 1, 2147483647, 0], getpos("'>")) - call assert_equal([0, 2, 7, 0], getpos("']")) - bwipe! - endfunc + new + call setline(1, 'тест') + normal viwdp + call assert_equal([0, 1, 7, 0], getpos("'>")) + call assert_equal([0, 1, 7, 0], getpos("']")) + + normal Vyp + call assert_equal([0, 1, 2147483647, 0], getpos("'>")) + call assert_equal([0, 2, 7, 0], getpos("']")) + bwipe! +endfunc diff --git a/src/nvim/testdir/test_python3.vim b/src/nvim/testdir/test_python3.vim index 54da3d2eba..f6a1942e24 100644 --- a/src/nvim/testdir/test_python3.vim +++ b/src/nvim/testdir/test_python3.vim @@ -69,6 +69,7 @@ func Test_vim_function() endfunc func Test_skipped_python3_command_does_not_affect_pyxversion() + throw 'skipped: Nvim hardcodes pyxversion=3' set pyxversion=0 if 0 python3 import vim diff --git a/src/nvim/testdir/test_pyx2.vim b/src/nvim/testdir/test_pyx2.vim index b6ed80f842..6a8ebf3da0 100644 --- a/src/nvim/testdir/test_pyx2.vim +++ b/src/nvim/testdir/test_pyx2.vim @@ -1,9 +1,9 @@ " Test for pyx* commands and functions with Python 2. -set pyx=2 if !has('python') finish endif +set pyx=2 let s:py2pattern = '^2\.[0-7]\.\d\+' let s:py3pattern = '^3\.\d\+\.\d\+' diff --git a/src/nvim/testdir/test_quickfix.vim b/src/nvim/testdir/test_quickfix.vim index 6db679c5f9..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' @@ -1914,6 +1937,7 @@ func Test_switchbuf() " If opening a file changes 'switchbuf', then the new value should be " retained. + set modeline&vim call writefile(["vim: switchbuf=split"], 'Xqftestfile1') enew | only set switchbuf&vim @@ -2715,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') @@ -5027,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_random.vim b/src/nvim/testdir/test_random.vim new file mode 100644 index 0000000000..6d3f7dcfd9 --- /dev/null +++ b/src/nvim/testdir/test_random.vim @@ -0,0 +1,51 @@ +" Tests for srand() and rand() + +func Test_Rand() + let r = srand(123456789) + call assert_equal([1573771921, 319883699, 2742014374, 1324369493], r) + call assert_equal(4284103975, rand(r)) + call assert_equal(1001954530, rand(r)) + call assert_equal(2701803082, rand(r)) + call assert_equal(2658065534, rand(r)) + call assert_equal(3104308804, rand(r)) + + " Nvim does not support test_settime + " call test_settime(12341234) + let s = srand() + if !has('win32') && filereadable('/dev/urandom') + " using /dev/urandom + call assert_notequal(s, srand()) + " else + " " using time() + " call assert_equal(s, srand()) + " call test_settime(12341235) + " call assert_notequal(s, srand()) + endif + + " Nvim does not support test_srand_seed + " call test_srand_seed(123456789) + " call assert_equal(4284103975, rand()) + " call assert_equal(1001954530, rand()) + " call test_srand_seed() + + if has('float') + call assert_fails('echo srand(1.2)', 'E805:') + endif + call assert_fails('echo srand([1])', 'E745:') + call assert_fails('echo rand("burp")', 'E475:') + call assert_fails('echo rand([1, 2, 3])', 'E475:') + call assert_fails('echo rand([[1], 2, 3, 4])', 'E475:') + call assert_fails('echo rand([1, [2], 3, 4])', 'E475:') + call assert_fails('echo rand([1, 2, [3], 4])', 'E475:') + call assert_fails('echo rand([1, 2, 3, [4]])', 'E475:') + + " call test_settime(0) +endfunc + +func Test_issue_5587() + call rand() + call garbagecollect() + call rand() +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_regexp_latin.vim b/src/nvim/testdir/test_regexp_latin.vim index 712f1e6025..a92f7e1192 100644 --- a/src/nvim/testdir/test_regexp_latin.vim +++ b/src/nvim/testdir/test_regexp_latin.vim @@ -787,4 +787,12 @@ func Test_regexp_error() set re& endfunc +func Test_using_mark_position() + " this was using freed memory + new + norm O0 + call assert_fails("s/\\%')", 'E486:') + bwipe! +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_regexp_utf8.vim b/src/nvim/testdir/test_regexp_utf8.vim index d8d5797dcf..b640a6d043 100644 --- a/src/nvim/testdir/test_regexp_utf8.vim +++ b/src/nvim/testdir/test_regexp_utf8.vim @@ -152,9 +152,6 @@ func s:classes_test() if has('win32') let identchars_ok = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz
¡¢£¤¥¦§µÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿ' let kwordchars_ok = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyzµÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ' - elseif has('ebcdic') - let identchars_ok = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz¬®µº¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ' - let kwordchars_ok = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz¬®µº¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ' else let identchars_ok = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyzµÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ' let kwordchars_ok = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyzµÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ' @@ -166,8 +163,6 @@ func s:classes_test() let fnamechars_ok = '$+,-./0123456789:ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz~ ¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ' elseif has('vms') let fnamechars_ok = '#$%+,-./0123456789:;<>ABCDEFGHIJKLMNOPQRSTUVWXYZ[]_abcdefghijklmnopqrstuvwxyz~ ¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ' - elseif has('ebcdic') - let fnamechars_ok = '#$%+,-./=ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz~ ¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ' else let fnamechars_ok = '#$%+,-./0123456789=ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz~ ¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ' endif @@ -590,4 +585,12 @@ func Test_match_char_class_upper() bwipe! endfunc +func Test_match_invalid_byte() + call writefile(0z630a.765d30aa0a.2e0a.790a.4030, 'Xinvalid') + new + source Xinvalid + bwipe! + call delete('Xinvalid') +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_registers.vim b/src/nvim/testdir/test_registers.vim index 84a5aca3d5..23e39eba35 100644 --- a/src/nvim/testdir/test_registers.vim +++ b/src/nvim/testdir/test_registers.vim @@ -1,6 +1,4 @@ -" " Tests for register operations -" source check.vim source view_util.vim @@ -13,6 +11,8 @@ func Test_aaa_empty_reg_test() call assert_fails('normal @!', 'E354:') call assert_fails('normal @:', 'E30:') call assert_fails('normal @.', 'E29:') + call assert_fails('put /', 'E35:') + call assert_fails('put .', 'E29:') endfunc func Test_yank_shows_register() @@ -119,6 +119,17 @@ func Test_recording_esc_sequence() endif endfunc +func Test_recording_with_select_mode() + new + call feedkeys("qacc12345\<Esc>gH98765\<Esc>q", "tx") + call assert_equal("98765", getline(1)) + call assert_equal("cc12345\<Esc>gH98765\<Esc>", @a) + call setline(1, 'asdf') + normal! @a + call assert_equal("98765", getline(1)) + bwipe! +endfunc + " Test for executing the last used register (@) func Test_last_used_exec_reg() " Test for the @: command @@ -141,6 +152,14 @@ func Test_last_used_exec_reg() normal @@ call assert_equal('EditEdit', a) + " Test for repeating the last command-line in visual mode + call append(0, 'register') + normal gg + let @r = '' + call feedkeys("v:yank R\<CR>", 'xt') + call feedkeys("v@:", 'xt') + call assert_equal("\nregister\nregister\n", @r) + enew! endfunc @@ -164,6 +183,19 @@ func Test_get_register() call assert_equal('', getregtype('!')) + " Test for inserting an invalid register content + call assert_beeps('exe "normal i\<C-R>!"') + + " Test for inserting a register with multiple lines + call deletebufline('', 1, '$') + call setreg('r', ['a', 'b']) + exe "normal i\<C-R>r" + call assert_equal(['a', 'b', ''], getline(1, '$')) + + " Test for inserting a multi-line register in the command line + call feedkeys(":\<C-R>r\<Esc>", 'xt') + call assert_equal("a\rb", histget(':', -1)) " Modified because of #6137 + enew! endfunc @@ -187,9 +219,200 @@ func Test_set_register() call setreg('=', 'b', 'a') call assert_equal('regwrite', getreg('=')) + " Test for setting a list of lines to special registers + call setreg('/', []) + call assert_equal('', @/) + call setreg('=', []) + call assert_equal('', @=) + call assert_fails("call setreg('/', ['a', 'b'])", 'E883:') + call assert_fails("call setreg('=', ['a', 'b'])", 'E883:') + call assert_equal(0, setreg('_', ['a', 'b'])) + + " Test for recording to a invalid register + call assert_beeps('normal q$') + + " Appending to a register when recording + call append(0, "text for clipboard test") + normal gg + call feedkeys('qrllq', 'xt') + call feedkeys('qRhhq', 'xt') + call assert_equal('llhh', getreg('r')) + + " Appending a list of characters to a register from different lines + let @r = '' + call append(0, ['abcdef', '123456']) + normal gg"ry3l + call cursor(2, 4) + normal "Ry3l + call assert_equal('abc456', @r) + + " Test for gP with multiple lines selected using characterwise motion + %delete + call append(0, ['vim editor', 'vim editor']) + let @r = '' + exe "normal ggwy/vim /e\<CR>gP" + call assert_equal(['vim editor', 'vim editor', 'vim editor'], getline(1, 3)) + + " Test for gP with . register + %delete + normal iabc + normal ".gp + call assert_equal('abcabc', getline(1)) + normal 0".gP + call assert_equal('abcabcabc', getline(1)) + enew! endfunc +" Test for clipboard registers (* and +) +func Test_clipboard_regs() + throw 'skipped: needs clipboard=autoselect,autoselectplus' + + CheckNotGui + CheckFeature clipboard_working + + new + call append(0, "text for clipboard test") + normal gg"*yiw + call assert_equal('text', getreg('*')) + normal gg2w"+yiw + call assert_equal('clipboard', getreg('+')) + + " Test for replacing the clipboard register contents + set clipboard=unnamed + let @* = 'food' + normal ggviw"*p + call assert_equal('text', getreg('*')) + call assert_equal('food for clipboard test', getline(1)) + normal ggviw"*p + call assert_equal('food', getreg('*')) + call assert_equal('text for clipboard test', getline(1)) + + " Test for replacing the selection register contents + set clipboard=unnamedplus + let @+ = 'food' + normal ggviw"+p + call assert_equal('text', getreg('+')) + call assert_equal('food for clipboard test', getline(1)) + normal ggviw"+p + call assert_equal('food', getreg('+')) + call assert_equal('text for clipboard test', getline(1)) + + " Test for auto copying visually selected text to clipboard register + call setline(1, "text for clipboard test") + let @* = '' + set clipboard=autoselect + normal ggwwviwy + call assert_equal('clipboard', @*) + + " Test for auto copying visually selected text to selection register + let @+ = '' + set clipboard=autoselectplus + normal ggwviwy + call assert_equal('for', @+) + + set clipboard&vim + bwipe! +endfunc + +" Test for restarting the current mode (insert or virtual replace) after +" executing the contents of a register +func Test_put_reg_restart_mode() + new + call append(0, 'editor') + normal gg + let @r = "ivim \<Esc>" + call feedkeys("i\<C-O>@r\<C-R>=mode()\<CR>", 'xt') + call assert_equal('vimi editor', getline(1)) + + call setline(1, 'editor') + normal gg + call feedkeys("gR\<C-O>@r\<C-R>=mode()\<CR>", 'xt') + call assert_equal('vimReditor', getline(1)) + + bwipe! +endfunc + +" Test for getting register info +func Test_get_reginfo() + enew + call setline(1, ['foo', 'bar']) + + exe 'norm! "zyy' + let info = getreginfo('"') + call assert_equal('z', info.points_to) + call setreg('y', 'baz') + call assert_equal('z', getreginfo('').points_to) + call setreg('y', { 'isunnamed': v:true }) + call assert_equal('y', getreginfo('"').points_to) + + exe '$put' + call assert_equal(getreg('y'), getline(3)) + call setreg('', 'qux') + call assert_equal('0', getreginfo('').points_to) + call setreg('x', 'quux') + call assert_equal('0', getreginfo('').points_to) + + let info = getreginfo('') + call assert_equal(getreg('', 1, 1), info.regcontents) + call assert_equal(getregtype(''), info.regtype) + + exe "norm! 0\<c-v>e" .. '"zy' + let info = getreginfo('z') + call assert_equal(getreg('z', 1, 1), info.regcontents) + call assert_equal(getregtype('z'), info.regtype) + call assert_equal(1, +info.isunnamed) + + let info = getreginfo('"') + call assert_equal('z', info.points_to) + + bwipe! +endfunc + +" Test for restoring register with dict from getreginfo +func Test_set_register_dict() + enew! + + call setreg('"', #{ regcontents: ['one', 'two'], + \ regtype: 'V', points_to: 'z' }) + call assert_equal(['one', 'two'], getreg('"', 1, 1)) + let info = getreginfo('"') + call assert_equal('z', info.points_to) + call assert_equal('V', info.regtype) + call assert_equal(1, +getreginfo('z').isunnamed) + + call setreg('x', #{ regcontents: ['three', 'four'], + \ regtype: 'v', isunnamed: v:true }) + call assert_equal(['three', 'four'], getreg('"', 1, 1)) + let info = getreginfo('"') + call assert_equal('x', info.points_to) + call assert_equal('v', info.regtype) + call assert_equal(1, +getreginfo('x').isunnamed) + + call setreg('y', #{ regcontents: 'five', + \ regtype: "\<c-v>", isunnamed: v:false }) + call assert_equal("\<c-v>4", getreginfo('y').regtype) + call assert_equal(0, +getreginfo('y').isunnamed) + call assert_equal(['three', 'four'], getreg('"', 1, 1)) + call assert_equal('x', getreginfo('"').points_to) + + call setreg('"', #{ regcontents: 'six' }) + call assert_equal('0', getreginfo('"').points_to) + call assert_equal(1, +getreginfo('0').isunnamed) + call assert_equal(['six'], getreginfo('0').regcontents) + call assert_equal(['six'], getreginfo('"').regcontents) + + let @x = 'one' + call setreg('x', {}) + call assert_equal(1, len(split(execute('reg x'), '\n'))) + + call assert_fails("call setreg('0', #{regtype: 'V'}, 'v')", 'E118:') + call assert_fails("call setreg('0', #{regtype: 'X'})", 'E475:') + call assert_fails("call setreg('0', #{regtype: 'vy'})", 'E475:') + + bwipe! +endfunc + func Test_v_register() enew call setline(1, 'nothing') @@ -288,84 +511,25 @@ func Test_insert_small_delete() bwipe! endfunc -" Test for getting register info -func Test_get_reginfo() - enew - call setline(1, ['foo', 'bar']) - - exe 'norm! "zyy' - let info = getreginfo('"') - call assert_equal('z', info.points_to) - call setreg('y', 'baz') - call assert_equal('z', getreginfo('').points_to) - call setreg('y', { 'isunnamed': v:true }) - call assert_equal('y', getreginfo('"').points_to) - - exe '$put' - call assert_equal(getreg('y'), getline(3)) - call setreg('', 'qux') - call assert_equal('0', getreginfo('').points_to) - call setreg('x', 'quux') - call assert_equal('0', getreginfo('').points_to) - - let info = getreginfo('') - call assert_equal(getreg('', 1, 1), info.regcontents) - call assert_equal(getregtype(''), info.regtype) - - exe "norm! 0\<c-v>e" .. '"zy' - let info = getreginfo('z') - call assert_equal(getreg('z', 1, 1), info.regcontents) - call assert_equal(getregtype('z'), info.regtype) - call assert_equal(1, +info.isunnamed) - - let info = getreginfo('"') - call assert_equal('z', info.points_to) +func Test_record_in_select_mode() + new + call setline(1, 'text') + sil norm q00 + sil norm q + call assert_equal('0ext', getline(1)) + + %delete + let @r = '' + call setline(1, ['abc', 'abc', 'abc']) + smap <F2> <Right><Right>, + call feedkeys("qrgh\<F2>Dk\<Esc>q", 'xt') + call assert_equal("gh\<F2>Dk\<Esc>", @r) + norm j0@rj0@@ + call assert_equal([',Dk', ',Dk', ',Dk'], getline(1, 3)) + sunmap <F2> bwipe! endfunc -" Test for restoring register with dict from getreginfo -func Test_set_register_dict() - enew! - - call setreg('"', #{ regcontents: ['one', 'two'], - \ regtype: 'V', points_to: 'z' }) - call assert_equal(['one', 'two'], getreg('"', 1, 1)) - let info = getreginfo('"') - call assert_equal('z', info.points_to) - call assert_equal('V', info.regtype) - call assert_equal(1, +getreginfo('z').isunnamed) - - call setreg('x', #{ regcontents: ['three', 'four'], - \ regtype: 'v', isunnamed: v:true }) - call assert_equal(['three', 'four'], getreg('"', 1, 1)) - let info = getreginfo('"') - call assert_equal('x', info.points_to) - call assert_equal('v', info.regtype) - call assert_equal(1, +getreginfo('x').isunnamed) - - call setreg('y', #{ regcontents: 'five', - \ regtype: "\<c-v>", isunnamed: v:false }) - call assert_equal("\<c-v>4", getreginfo('y').regtype) - call assert_equal(0, +getreginfo('y').isunnamed) - call assert_equal(['three', 'four'], getreg('"', 1, 1)) - call assert_equal('x', getreginfo('"').points_to) - - call setreg('"', #{ regcontents: 'six' }) - call assert_equal('0', getreginfo('"').points_to) - call assert_equal(1, +getreginfo('0').isunnamed) - call assert_equal(['six'], getreginfo('0').regcontents) - call assert_equal(['six'], getreginfo('"').regcontents) - - let @x = 'one' - call setreg('x', {}) - call assert_equal(1, len(split(execute('reg x'), '\n'))) - - call assert_fails("call setreg('0', #{regtype: 'V'}, 'v')", 'E118:') - call assert_fails("call setreg('0', #{regtype: 'X'})", 'E475:') - call assert_fails("call setreg('0', #{regtype: 'vy'})", 'E475:') - - bwipe! -endfunc " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_rename.vim b/src/nvim/testdir/test_rename.vim index 3887fcfabf..5359b84923 100644 --- a/src/nvim/testdir/test_rename.vim +++ b/src/nvim/testdir/test_rename.vim @@ -1,5 +1,7 @@ " Test rename() +source shared.vim + func Test_rename_file_to_file() call writefile(['foo'], 'Xrename1') @@ -81,7 +83,7 @@ func Test_rename_copy() call assert_equal(0, rename('Xrenamedir/Xrenamefile', 'Xrenamefile')) - if !has('win32') + if !has('win32') && !IsRoot() " On Windows, the source file is removed despite " its directory being made not writable. call assert_equal(['foo'], readfile('Xrenamedir/Xrenamefile')) diff --git a/src/nvim/testdir/test_retab.vim b/src/nvim/testdir/test_retab.vim index f11a32bade..1650a03876 100644 --- a/src/nvim/testdir/test_retab.vim +++ b/src/nvim/testdir/test_retab.vim @@ -69,9 +69,33 @@ 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() call assert_fails('retab -1', 'E487:') call assert_fails('retab! -1', 'E487:') + call assert_fails('ret -1000', 'E487:') + 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_scriptnames.vim b/src/nvim/testdir/test_scriptnames.vim index fc6c910bfa..44ec146666 100644 --- a/src/nvim/testdir/test_scriptnames.vim +++ b/src/nvim/testdir/test_scriptnames.vim @@ -23,4 +23,10 @@ func Test_scriptnames() bwipe call delete('Xscripting') + + let msgs = execute('messages') + scriptnames + call assert_equal(msgs, execute('messages')) endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_search.vim b/src/nvim/testdir/test_search.vim index c796f1f676..a3d5ca96a1 100644 --- a/src/nvim/testdir/test_search.vim +++ b/src/nvim/testdir/test_search.vim @@ -292,15 +292,84 @@ endfunc func Test_searchpair() new - call setline(1, ['other code here', '', '[', '" cursor here', ']']) + call setline(1, ['other code', 'here [', ' [', ' " cursor here', ' ]]']) + + 4 + call assert_equal(3, searchpair('\[', '', ']', 'bW')) + call assert_equal([0, 3, 2, 0], getpos('.')) + 4 + call assert_equal(2, searchpair('\[', '', ']', 'bWr')) + call assert_equal([0, 2, 6, 0], getpos('.')) + 4 + call assert_equal(1, searchpair('\[', '', ']', 'bWm')) + call assert_equal([0, 3, 2, 0], getpos('.')) + 4|norm ^ + call assert_equal(5, searchpair('\[', '', ']', 'Wn')) + call assert_equal([0, 4, 2, 0], getpos('.')) + 4 + call assert_equal(2, searchpair('\[', '', ']', 'bW', + \ 'getline(".") =~ "^\\s*\["')) + call assert_equal([0, 2, 6, 0], getpos('.')) + set nomagic + 4 + call assert_equal(3, searchpair('\[', '', ']', 'bW')) + call assert_equal([0, 3, 2, 0], getpos('.')) + set magic + 4|norm ^ + call assert_equal(0, searchpair('{', '', '}', 'bW')) + call assert_equal([0, 4, 2, 0], getpos('.')) + + %d + call setline(1, ['if 1', ' if 2', ' else', ' endif 2', 'endif 1']) + + /\<if 1 + call assert_equal(5, searchpair('\<if\>', '\<else\>', '\<endif\>', 'W')) + call assert_equal([0, 5, 1, 0], getpos('.')) + /\<if 2 + call assert_equal(3, searchpair('\<if\>', '\<else\>', '\<endif\>', 'W')) + call assert_equal([0, 3, 3, 0], getpos('.')) + + q! +endfunc + +func Test_searchpairpos() + new + call setline(1, ['other code', 'here [', ' [', ' " cursor here', ' ]]']) + + 4 + call assert_equal([3, 2], searchpairpos('\[', '', ']', 'bW')) + call assert_equal([0, 3, 2, 0], getpos('.')) 4 - let a = searchpair('\[','',']','bW') - call assert_equal(3, a) + call assert_equal([2, 6], searchpairpos('\[', '', ']', 'bWr')) + call assert_equal([0, 2, 6, 0], getpos('.')) + 4|norm ^ + call assert_equal([5, 2], searchpairpos('\[', '', ']', 'Wn')) + call assert_equal([0, 4, 2, 0], getpos('.')) + 4 + call assert_equal([2, 6], searchpairpos('\[', '', ']', 'bW', + \ 'getline(".") =~ "^\\s*\["')) + call assert_equal([0, 2, 6, 0], getpos('.')) + 4 + call assert_equal([2, 6], searchpairpos('\[', '', ']', 'bWr')) + call assert_equal([0, 2, 6, 0], getpos('.')) set nomagic 4 - let a = searchpair('\[','',']','bW') - call assert_equal(3, a) + call assert_equal([3, 2], searchpairpos('\[', '', ']', 'bW')) + call assert_equal([0, 3, 2, 0], getpos('.')) set magic + 4|norm ^ + call assert_equal([0, 0], searchpairpos('{', '', '}', 'bW')) + call assert_equal([0, 4, 2, 0], getpos('.')) + + %d + call setline(1, ['if 1', ' if 2', ' else', ' endif 2', 'endif 1']) + /\<if 1 + call assert_equal([5, 1], searchpairpos('\<if\>', '\<else\>', '\<endif\>', 'W')) + call assert_equal([0, 5, 1, 0], getpos('.')) + /\<if 2 + call assert_equal([3, 3], searchpairpos('\<if\>', '\<else\>', '\<endif\>', 'W')) + call assert_equal([0, 3, 3, 0], getpos('.')) + q! endfunc @@ -309,17 +378,29 @@ 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') + call assert_fails("call searchpair('start', 'middle', 'end', 'sn')", 'E475: Invalid argument: sn') +endfunc + +func Test_searchpairpos_errors() + call assert_fails("call searchpairpos([0], 'middle', 'end', 'bW', 'skip', 99, 100)", 'E730: using List as a String') + 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', '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') + call assert_fails("call searchpairpos('start', 'middle', 'end', 'sn')", 'E475: Invalid argument: sn') endfunc func Test_searchpair_skip() func Zero() - return 0 + return 0 endfunc func Partial(x) - return a:x + return a:x endfunc new call setline(1, ['{', 'foo', 'foo', 'foo', '}']) @@ -1192,7 +1273,7 @@ endfunc " This was causing E874. Also causes an invalid read? func Test_look_behind() new - call setline(1, '0\|\&\n\@<=') + call setline(1, '0\|\&\n\@<=') call search(getline(".")) bwipe! endfunc @@ -1236,11 +1317,11 @@ endfunc func Test_search_Ctrl_L_combining() " Make sure, that Ctrl-L works correctly with combining characters. " It uses an artificial example of an 'a' with 4 combining chars: - " 'a' U+0061 Dec:97 LATIN SMALL LETTER A a /\%u61\Z "\u0061" + " 'a' U+0061 Dec:97 LATIN SMALL LETTER A a /\%u61\Z "\u0061" " ' ̀' U+0300 Dec:768 COMBINING GRAVE ACCENT ̀ /\%u300\Z "\u0300" " ' ́' U+0301 Dec:769 COMBINING ACUTE ACCENT ́ /\%u301\Z "\u0301" " ' ̇' U+0307 Dec:775 COMBINING DOT ABOVE ̇ /\%u307\Z "\u0307" - " ' ̣' U+0323 Dec:803 COMBINING DOT BELOW ̣ /\%u323 "\u0323" + " ' ̣' U+0323 Dec:803 COMBINING DOT BELOW ̣ /\%u323 "\u0323" " Those should also appear on the commandline CheckOption incsearch diff --git a/src/nvim/testdir/test_search_stat.vim b/src/nvim/testdir/test_search_stat.vim index 335a51268d..d950626615 100644 --- a/src/nvim/testdir/test_search_stat.vim +++ b/src/nvim/testdir/test_search_stat.vim @@ -1,14 +1,15 @@ " Tests for search_stats, when "S" is not in 'shortmess' -source screendump.vim source check.vim +source screendump.vim func Test_search_stat() new set shortmess-=S " Append 50 lines with text to search for, "foobar" appears 20 times call append(0, repeat(['foobar', 'foo', 'fooooobar', 'foba', 'foobar'], 10)) - call nvim_win_set_cursor(0, [1, 0]) + + call cursor(1, 1) " searchcount() returns an empty dictionary when previous pattern was not set call assert_equal({}, searchcount(#{pattern: ''})) @@ -45,7 +46,6 @@ func Test_search_stat() \ searchcount(#{pattern: 'fooooobar', maxcount: 1})) " match at second line - call cursor(1, 1) let messages_before = execute('messages') let @/ = 'fo*\(bar\?\)\?' let g:a = execute(':unsilent :norm! n') @@ -73,7 +73,6 @@ func Test_search_stat() let stat = '\[2/50\]' let g:a = execute(':unsilent :norm! n') call assert_notmatch(pat .. stat, g:a) - call writefile(getline(1, '$'), 'sample.txt') " n does not update search stat call assert_equal( \ #{current: 50, exact_match: 1, total: 50, incomplete: 0, maxcount: 99}, @@ -263,6 +262,34 @@ func Test_searchcount_fails() call assert_fails('echo searchcount("boo!")', 'E715:') endfunc +func Test_searchcount_in_statusline() + CheckScreendump + + let lines =<< trim END + set shortmess-=S + call append(0, 'this is something') + function TestSearchCount() abort + let search_count = searchcount() + if !empty(search_count) + return '[' . search_count.current . '/' . search_count.total . ']' + else + return '' + endif + endfunction + set hlsearch + set laststatus=2 statusline+=%{TestSearchCount()} + END + call writefile(lines, 'Xsearchstatusline') + let buf = RunVimInTerminal('-S Xsearchstatusline', #{rows: 10}) + call TermWait(buf) + call term_sendkeys(buf, "/something") + call VerifyScreenDump(buf, 'Test_searchstat_4', {}) + + call term_sendkeys(buf, "\<Esc>") + call StopVimInTerminal(buf) + call delete('Xsearchstatusline') +endfunc + func Test_search_stat_foldopen() CheckScreendump @@ -320,30 +347,29 @@ func! Test_search_stat_screendump() call delete('Xsearchstat') endfunc -func Test_searchcount_in_statusline() +func Test_search_stat_then_gd() CheckScreendump let lines =<< trim END + call setline(1, ['int cat;', 'int dog;', 'cat = dog;']) set shortmess-=S - call append(0, 'this is something') - function TestSearchCount() abort - let search_count = searchcount() - if !empty(search_count) - return '[' . search_count.current . '/' . search_count.total . ']' - else - return '' - endif - endfunction set hlsearch - set laststatus=2 statusline+=%{TestSearchCount()} END - call writefile(lines, 'Xsearchstatusline') - let buf = RunVimInTerminal('-S Xsearchstatusline', #{rows: 10}) + call writefile(lines, 'Xsearchstatgd') + + let buf = RunVimInTerminal('-S Xsearchstatgd', #{rows: 10}) + call term_sendkeys(buf, "/dog\<CR>") call TermWait(buf) - call term_sendkeys(buf, "/something") - call VerifyScreenDump(buf, 'Test_searchstat_4', {}) + call VerifyScreenDump(buf, 'Test_searchstatgd_1', {}) + + call term_sendkeys(buf, "G0gD") + call TermWait(buf) + call VerifyScreenDump(buf, 'Test_searchstatgd_2', {}) - call term_sendkeys(buf, "\<Esc>") call StopVimInTerminal(buf) - call delete('Xsearchstatusline') + call delete('Xsearchstatgd') endfunc + + + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_selectmode.vim b/src/nvim/testdir/test_selectmode.vim new file mode 100644 index 0000000000..b483841060 --- /dev/null +++ b/src/nvim/testdir/test_selectmode.vim @@ -0,0 +1,57 @@ +" Test for Select-mode + +source shared.vim + +" Test for selecting a register with CTRL-R +func Test_selectmode_register() + new + + " Default behavior: use unnamed register + call setline(1, 'foo') + call setreg('"', 'bar') + call setreg('a', 'baz') + exe ":norm! v\<c-g>a" + call assert_equal(getline('.'), 'aoo') + call assert_equal('f', getreg('"')) + call assert_equal('baz', getreg('a')) + + " Use the black hole register + call setline(1, 'foo') + call setreg('"', 'bar') + call setreg('a', 'baz') + exe ":norm! v\<c-g>\<c-r>_a" + call assert_equal(getline('.'), 'aoo') + call assert_equal('bar', getreg('"')) + call assert_equal('baz', getreg('a')) + + " Invalid register: use unnamed register + call setline(1, 'foo') + call setreg('"', 'bar') + call setreg('a', 'baz') + exe ":norm! v\<c-g>\<c-r>?a" + call assert_equal(getline('.'), 'aoo') + call assert_equal('f', getreg('"')) + call assert_equal('baz', getreg('a')) + + " Use unnamed register + call setline(1, 'foo') + call setreg('"', 'bar') + call setreg('a', 'baz') + exe ":norm! v\<c-g>\<c-r>\"a" + call assert_equal(getline('.'), 'aoo') + call assert_equal('f', getreg('"')) + call assert_equal('baz', getreg('a')) + + " use specicifed register, unnamed register is also written + call setline(1, 'foo') + call setreg('"', 'bar') + call setreg('a', 'baz') + exe ":norm! v\<c-g>\<c-r>aa" + call assert_equal(getline('.'), 'aoo') + call assert_equal('f', getreg('"')) + call assert_equal('f', getreg('a')) + + bw! +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_source_utf8.vim b/src/nvim/testdir/test_source_utf8.vim index e93ea29dff..66fabe0442 100644 --- a/src/nvim/testdir/test_source_utf8.vim +++ b/src/nvim/testdir/test_source_utf8.vim @@ -1,4 +1,5 @@ " Test the :source! command +source check.vim func Test_source_utf8() " check that sourcing a script with 0x80 as second byte works @@ -31,24 +32,24 @@ endfunc " Test for sourcing a file with CTRL-V's at the end of the line func Test_source_ctrl_v() - call writefile(['map __1 afirst', - \ 'map __2 asecond', - \ 'map __3 athird', - \ 'map __4 afourth', - \ 'map __5 afifth', - \ "map __1 asd\<C-V>", - \ "map __2 asd\<C-V>\<C-V>", - \ "map __3 asd\<C-V>\<C-V>", - \ "map __4 asd\<C-V>\<C-V>\<C-V>", - \ "map __5 asd\<C-V>\<C-V>\<C-V>", - \ ], 'Xtestfile') + call writefile(['map __1 afirst', + \ 'map __2 asecond', + \ 'map __3 athird', + \ 'map __4 afourth', + \ 'map __5 afifth', + \ "map __1 asd\<C-V>", + \ "map __2 asd\<C-V>\<C-V>", + \ "map __3 asd\<C-V>\<C-V>", + \ "map __4 asd\<C-V>\<C-V>\<C-V>", + \ "map __5 asd\<C-V>\<C-V>\<C-V>", + \ ], 'Xtestfile') source Xtestfile enew! exe "normal __1\<Esc>\<Esc>__2\<Esc>__3\<Esc>\<Esc>__4\<Esc>__5\<Esc>" exe "%s/\<C-J>/0/g" call assert_equal(['sd', - \ "map __2 asd\<Esc>secondsd\<Esc>sd0map __5 asd0fifth"], - \ getline(1, 2)) + \ "map __2 asd\<Esc>secondsd\<Esc>sd0map __5 asd0fifth"], + \ getline(1, 2)) enew! call delete('Xtestfile') diff --git a/src/nvim/testdir/test_spell.vim b/src/nvim/testdir/test_spell.vim index cf0faeee31..1ecb5c8070 100644 --- a/src/nvim/testdir/test_spell.vim +++ b/src/nvim/testdir/test_spell.vim @@ -768,6 +768,14 @@ func Test_spell_screendump() call delete('XtestSpell') endfunc +func Test_spell_single_word() + new + silent! norm 0R00 + spell! + silent 0norm 0r$ Dvz= + bwipe! +endfunc + let g:test_data_aff1 = [ \"SET ISO8859-1", \"TRY esianrtolcdugmphbyfvkwjkqxz-\xEB\xE9\xE8\xEA\xEF\xEE\xE4\xE0\xE2\xF6\xFC\xFB'ESIANRTOLCDUGMPHBYFVKWJKQXZ", 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_statusline.vim b/src/nvim/testdir/test_statusline.vim index a3e4dcdd25..fad13e3340 100644 --- a/src/nvim/testdir/test_statusline.vim +++ b/src/nvim/testdir/test_statusline.vim @@ -186,7 +186,16 @@ func Test_statusline() set virtualedit=all norm 10| call assert_match('^10,-10\s*$', s:get_statusline()) + set list + call assert_match('^10,-10\s*$', s:get_statusline()) set virtualedit& + exe "norm A\<Tab>\<Tab>a\<Esc>" + " In list mode a <Tab> is shown as "^I", which is 2-wide. + call assert_match('^9,-9\s*$', s:get_statusline()) + set list& + " Now the second <Tab> ends at the 16th screen column. + call assert_match('^17,-17\s*$', s:get_statusline()) + undo " %w: Preview window flag, text is "[Preview]". " %W: Preview window flag, text is ",PRV". @@ -498,5 +507,20 @@ func Test_statusline_after_split_vsplit() set ls& stl& endfunc +" Test using a multibyte character for 'stl' and 'stlnc' items in 'fillchars' +" with a custom 'statusline' +func Test_statusline_mbyte_fillchar() + only + set laststatus=2 + set fillchars=vert:\|,fold:-,stl:━,stlnc:═ + set statusline=a%=b + call assert_match('^a\+━\+b$', s:get_statusline()) + vnew + call assert_match('^a\+━\+b━a\+═\+b$', s:get_statusline()) + wincmd w + call assert_match('^a\+═\+b═a\+━\+b$', s:get_statusline()) + set statusline& fillchars& laststatus& + %bw! +endfunc " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_substitute.vim b/src/nvim/testdir/test_substitute.vim index 20b760ac15..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 @@ -105,7 +108,7 @@ func Test_substitute_variants() call assert_equal(var.exp, getline('.'), msg) endfor endfor -endfunction +endfunc " Test the l, p, # flags. func Test_substitute_flags_lp() @@ -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_swap.vim b/src/nvim/testdir/test_swap.vim index b3018b2b0d..b180f27685 100644 --- a/src/nvim/testdir/test_swap.vim +++ b/src/nvim/testdir/test_swap.vim @@ -1,6 +1,7 @@ " Tests for the swap feature source check.vim +source shared.vim func s:swapname() return trim(execute('swapname')) @@ -198,14 +199,17 @@ func Test_swapfile_delete() quit call assert_equal(fnamemodify(swapfile_name, ':t'), fnamemodify(s:swapname, ':t')) - " Write the swapfile with a modified PID, now it will be automatically - " deleted. Process one should never be Vim. - let swapfile_bytes[24:27] = 0z01000000 - call writefile(swapfile_bytes, swapfile_name) - let s:swapname = '' - split XswapfileText - quit - call assert_equal('', s:swapname) + " This test won't work as root because root can successfully run kill(1, 0) + if !IsRoot() + " Write the swapfile with a modified PID, now it will be automatically + " deleted. Process one should never be Vim. + let swapfile_bytes[24:27] = 0z01000000 + call writefile(swapfile_bytes, swapfile_name) + let s:swapname = '' + split XswapfileText + quit + call assert_equal('', s:swapname) + endif " Now set the modified flag, the swap file will not be deleted let swapfile_bytes[28 + 80 + 899] = 0x55 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 1858b48807..18692f42c9 100644 --- a/src/nvim/testdir/test_system.vim +++ b/src/nvim/testdir/test_system.vim @@ -46,15 +46,15 @@ func Test_System() bwipe! call assert_fails('call system("wc -l", 99999)', 'E86:') -endfunction +endfunc -function! Test_system_exmode() +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 @@ function! 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_tagjump.vim b/src/nvim/testdir/test_tagjump.vim index 2aa04df42a..e0b05edf15 100644 --- a/src/nvim/testdir/test_tagjump.vim +++ b/src/nvim/testdir/test_tagjump.vim @@ -641,7 +641,7 @@ func Test_tag_envvar() endfunc " Test for :ptag -func Test_ptag() +func Test_tag_preview() call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//", \ "second\tXfile1\t2", \ "third\tXfile1\t3",], @@ -655,11 +655,19 @@ func Test_ptag() call assert_equal(2, winnr('$')) call assert_equal(1, getwinvar(1, '&previewwindow')) call assert_equal(0, getwinvar(2, '&previewwindow')) - wincmd w + wincmd P call assert_equal(3, line('.')) " jump to the tag again + wincmd w ptag third + wincmd P + call assert_equal(3, line('.')) + + " jump to the newer tag + wincmd w + ptag + wincmd P call assert_equal(3, line('.')) " close the preview window @@ -829,8 +837,8 @@ func Test_tag_last_search_pat() %bwipe endfunc -" Test for jumping to a tag when the tag stack is full -func Test_tag_stack_full() +" Tag stack tests +func Test_tag_stack() let l = [] for i in range(10, 31) let l += ["var" .. i .. "\tXfoo\t/^int var" .. i .. ";$/"] @@ -844,6 +852,7 @@ func Test_tag_stack_full() endfor call writefile(l, 'Xfoo') + " Jump to a tag when the tag stack is full. Oldest entry should be removed. enew for i in range(10, 30) exe "tag var" .. i @@ -856,9 +865,15 @@ func Test_tag_stack_full() call assert_equal('var12', l.items[0].tagname) call assert_equal('var31', l.items[19].tagname) - " Jump from the top of the stack + " Use tnext with a single match + call assert_fails('tnext', 'E427:') + + " Jump to newest entry from the top of the stack call assert_fails('tag', 'E556:') + " Pop with zero count from the top of the stack + call assert_fails('0pop', 'E556:') + " Pop from an unsaved buffer enew! call append(1, "sample text") @@ -869,6 +884,13 @@ func Test_tag_stack_full() " Pop all the entries in the tag stack call assert_fails('30pop', 'E555:') + " Pop with a count when already at the bottom of the stack + call assert_fails('exe "normal 4\<C-T>"', 'E555:') + call assert_equal(1, gettagstack().curidx) + + " Jump to newest entry from the bottom of the stack with zero count + call assert_fails('0tag', 'E555:') + " Pop the tag stack when it is empty call settagstack(1, {'items' : []}) call assert_fails('pop', 'E73:') @@ -895,6 +917,7 @@ func Test_tag_multimatch() [CODE] call writefile(code, 'Xfoo') + call settagstack(1, {'items' : []}) tag first tlast call assert_equal(3, line('.')) @@ -903,6 +926,151 @@ func Test_tag_multimatch() call assert_equal(1, line('.')) call assert_fails('tprev', 'E425:') + tlast + call feedkeys("5\<CR>", 't') + tselect first + call assert_equal(2, gettagstack().curidx) + + set ignorecase + tag FIRST + tnext + call assert_equal(2, line('.')) + set ignorecase& + + call delete('Xtags') + call delete('Xfoo') + set tags& + %bwipe +endfunc + +" Test for previewing multiple matching tags +func Test_preview_tag_multimatch() + call writefile([ + \ "!_TAG_FILE_ENCODING\tutf-8\t//", + \ "first\tXfoo\t1", + \ "first\tXfoo\t2", + \ "first\tXfoo\t3"], + \ 'Xtags') + set tags=Xtags + let code =<< trim [CODE] + int first() {} + int first() {} + int first() {} + [CODE] + call writefile(code, 'Xfoo') + + enew | only + ptag first + ptlast + wincmd P + call assert_equal(3, line('.')) + wincmd w + call assert_fails('ptnext', 'E428:') + ptprev + wincmd P + call assert_equal(2, line('.')) + wincmd w + ptfirst + wincmd P + call assert_equal(1, line('.')) + wincmd w + call assert_fails('ptprev', 'E425:') + ptnext + wincmd P + call assert_equal(2, line('.')) + wincmd w + ptlast + call feedkeys("5\<CR>", 't') + ptselect first + wincmd P + call assert_equal(3, line('.')) + + pclose + + call delete('Xtags') + call delete('Xfoo') + set tags& + %bwipe +endfunc + +" Test for jumping to multiple matching tags across multiple :tags commands +func Test_tnext_multimatch() + call writefile([ + \ "!_TAG_FILE_ENCODING\tutf-8\t//", + \ "first\tXfoo1\t1", + \ "first\tXfoo2\t1", + \ "first\tXfoo3\t1"], + \ 'Xtags') + set tags=Xtags + let code =<< trim [CODE] + int first() {} + [CODE] + call writefile(code, 'Xfoo1') + call writefile(code, 'Xfoo2') + call writefile(code, 'Xfoo3') + + tag first + tag first + pop + tnext + tnext + call assert_fails('tnext', 'E428:') + + call delete('Xtags') + call delete('Xfoo1') + call delete('Xfoo2') + call delete('Xfoo3') + set tags& + %bwipe +endfunc + +" Test for jumping to multiple matching tags in non-existing files +func Test_multimatch_non_existing_files() + call writefile([ + \ "!_TAG_FILE_ENCODING\tutf-8\t//", + \ "first\tXfoo1\t1", + \ "first\tXfoo2\t1", + \ "first\tXfoo3\t1"], + \ 'Xtags') + set tags=Xtags + + call settagstack(1, {'items' : []}) + call assert_fails('tag first', 'E429:') + call assert_equal(3, gettagstack().items[0].matchnr) + + call delete('Xtags') + set tags& + %bwipe +endfunc + +func Test_tselect_listing() + call writefile([ + \ "!_TAG_FILE_ENCODING\tutf-8\t//", + \ "first\tXfoo\t1" .. ';"' .. "\tv\ttyperef:typename:int\tfile:", + \ "first\tXfoo\t2" .. ';"' .. "\tv\ttyperef:typename:char\tfile:"], + \ 'Xtags') + set tags=Xtags + + let code =<< trim [CODE] + static int first; + static char first; + [CODE] + call writefile(code, 'Xfoo') + + call feedkeys("\<CR>", "t") + let l = split(execute("tselect first"), "\n") + let expected =<< [DATA] + # pri kind tag file + 1 FS v first Xfoo + typeref:typename:int + 1 + 2 FS v first Xfoo + typeref:typename:char + 2 +Type number and <Enter> (q or empty cancels): +[DATA] + call assert_equal(expected, l) + call delete('Xtags') call delete('Xfoo') set tags& diff --git a/src/nvim/testdir/test_textformat.vim b/src/nvim/testdir/test_textformat.vim index bf0706a0c2..e9f846af7b 100644 --- a/src/nvim/testdir/test_textformat.vim +++ b/src/nvim/testdir/test_textformat.vim @@ -196,6 +196,130 @@ func Test_text_format() enew! endfunc +func Test_format_c_comment() + new + setl ai cindent tw=40 et fo=croql + let text =<< trim END + var = 2345; // asdf asdf asdf asdf asdf asdf asdf asdf asdf asdf + END + call setline(1, text) + normal gql + let expected =<< trim END + var = 2345; // asdf asdf asdf asdf asdf + // asdf asdf asdf asdf asdf + END + call assert_equal(expected, getline(1, '$')) + + %del + let text =<< trim END + var = 2345; // asdf asdf asdf asdf asdf asdf asdf asdf asdf asdf asdf asdf + END + call setline(1, text) + normal gql + let expected =<< trim END + var = 2345; // asdf asdf asdf asdf asdf + // asdf asdf asdf asdf asdf + // asdf asdf + END + call assert_equal(expected, getline(1, '$')) + + %del + let text =<< trim END + #if 0 // This is another long end of + // line comment that + // wraps. + END + call setline(1, text) + normal gq2j + let expected =<< trim END + #if 0 // This is another long + // end of line comment + // that wraps. + END + call assert_equal(expected, getline(1, '$')) + + " 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; + val = val; // This is a comment + END + call setline(1, text) + normal 2Go + let expected =<< trim END + nop; + val = val; // This is a comment + // + END + call assert_equal(expected, getline(1, '$')) + normal 2GO + let expected =<< trim END + nop; + + val = val; // This is a comment + // + END + call assert_equal(expected, getline(1, '$')) + + " Using "o" does not repeat a comment in a string + %del + let text =<< trim END + nop; + val = " // This is not a comment"; + END + call setline(1, text) + normal 2Gox + let expected =<< trim END + nop; + val = " // This is not a comment"; + x + END + call assert_equal(expected, getline(1, '$')) + + " Using CTRL-U after "o" fixes the indent + %del + let text =<< trim END + { + val = val; // This is a comment + END + call setline(1, text) + exe "normal! 2Go\<C-U>x\<Esc>" + let expected =<< trim END + { + val = val; // This is a comment + x + END + call assert_equal(expected, getline(1, '$')) + + bwipe! +endfunc + " Tests for :right, :center and :left on text with embedded TAB. func Test_format_align() enew! @@ -433,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_timers.vim b/src/nvim/testdir/test_timers.vim index aae315b2c5..09eed4e10d 100644 --- a/src/nvim/testdir/test_timers.vim +++ b/src/nvim/testdir/test_timers.vim @@ -379,4 +379,27 @@ func Test_timer_invalid_callback() call assert_fails('call timer_start(0, "0")', 'E921') endfunc +func Test_timer_using_win_execute_undo_sync() + let bufnr1 = bufnr() + new + let g:bufnr2 = bufnr() + let g:winid = win_getid() + exe "buffer " .. bufnr1 + wincmd w + call setline(1, ['test']) + autocmd InsertEnter * call timer_start(100, { -> win_execute(g:winid, 'buffer ' .. g:bufnr2) }) + call timer_start(200, { -> feedkeys("\<CR>bbbb\<Esc>") }) + call feedkeys("Oaaaa", 'x!t') + " will hang here until the second timer fires + call assert_equal(['aaaa', 'bbbb', 'test'], getline(1, '$')) + undo + call assert_equal(['test'], getline(1, '$')) + + bwipe! + bwipe! + unlet g:winid + unlet g:bufnr2 + au! InsertEnter +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_usercommands.vim b/src/nvim/testdir/test_usercommands.vim index 29e578ac6d..967ad85a64 100644 --- a/src/nvim/testdir/test_usercommands.vim +++ b/src/nvim/testdir/test_usercommands.vim @@ -269,10 +269,10 @@ endfunc func Test_CmdCompletion() call feedkeys(":com -\<C-A>\<C-B>\"\<CR>", 'tx') - call assert_equal('"com -addr bang bar buffer complete count nargs range register', @:) + call assert_equal('"com -addr bang bar buffer complete count keepscript nargs range register', @:) call feedkeys(":com -nargs=0 -\<C-A>\<C-B>\"\<CR>", 'tx') - call assert_equal('"com -nargs=0 -addr bang bar buffer complete count nargs range register', @:) + call assert_equal('"com -nargs=0 -addr bang bar buffer complete count keepscript nargs range register', @:) call feedkeys(":com -nargs=\<C-A>\<C-B>\"\<CR>", 'tx') call assert_equal('"com -nargs=* + 0 1 ?', @:) @@ -350,7 +350,6 @@ func Test_use_execute_in_completion() endfunc func Test_addr_all() - throw 'skipped: requires patch v8.1.0341 to pass' command! -addr=lines DoSomething let g:a1 = <line1> | let g:a2 = <line2> %DoSomething call assert_equal(1, g:a1) @@ -551,3 +550,26 @@ func Test_command_list() call assert_equal("\nNo user-defined commands found", execute(':command Xxx')) call assert_equal("\nNo user-defined commands found", execute('command')) endfunc + +func Test_delcommand_buffer() + command Global echo 'global' + command -buffer OneBuffer echo 'one' + new + command -buffer TwoBuffer echo 'two' + call assert_equal(0, exists(':OneBuffer')) + call assert_equal(2, exists(':Global')) + call assert_equal(2, exists(':TwoBuffer')) + delcommand -buffer TwoBuffer + call assert_equal(0, exists(':TwoBuffer')) + call assert_fails('delcommand -buffer Global', 'E1237:') + call assert_fails('delcommand -buffer OneBuffer', 'E1237:') + bwipe! + call assert_equal(2, exists(':OneBuffer')) + delcommand -buffer OneBuffer + call assert_equal(0, exists(':OneBuffer')) + call assert_fails('delcommand -buffer Global', 'E1237:') + delcommand Global + call assert_equal(0, exists(':Global')) +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_utf8.vim b/src/nvim/testdir/test_utf8.vim index 0818c2e4b0..9b010a5dbc 100644 --- a/src/nvim/testdir/test_utf8.vim +++ b/src/nvim/testdir/test_utf8.vim @@ -1,5 +1,6 @@ " Tests for Unicode manipulations +source check.vim source view_util.vim " Visual block Insert adjusts for multi-byte char @@ -7,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 @@ -148,4 +149,55 @@ func Test_print_overlong() bwipe! endfunc +func Test_recording_with_select_mode_utf8() + call Run_test_recording_with_select_mode_utf8() +endfunc + +func Run_test_recording_with_select_mode_utf8() + new + + " No escaping + call feedkeys("qacc12345\<Esc>gH哦\<Esc>q", "tx") + call assert_equal("哦", getline(1)) + call assert_equal("cc12345\<Esc>gH哦\<Esc>", @a) + call setline(1, 'asdf') + normal! @a + call assert_equal("哦", getline(1)) + + " 固 is 0xE5 0x9B 0xBA where 0x9B is CSI + call feedkeys("qacc12345\<Esc>gH固\<Esc>q", "tx") + call assert_equal("固", getline(1)) + call assert_equal("cc12345\<Esc>gH固\<Esc>", @a) + call setline(1, 'asdf') + normal! @a + call assert_equal("固", getline(1)) + + " 四 is 0xE5 0x9B 0x9B where 0x9B is CSI + call feedkeys("qacc12345\<Esc>gH四\<Esc>q", "tx") + call assert_equal("四", getline(1)) + call assert_equal("cc12345\<Esc>gH四\<Esc>", @a) + call setline(1, 'asdf') + normal! @a + call assert_equal("四", getline(1)) + + " 倒 is 0xE5 0x80 0x92 where 0x80 is K_SPECIAL + call feedkeys("qacc12345\<Esc>gH倒\<Esc>q", "tx") + call assert_equal("倒", getline(1)) + call assert_equal("cc12345\<Esc>gH倒\<Esc>", @a) + call setline(1, 'asdf') + normal! @a + call assert_equal("倒", getline(1)) + + bwipe! +endfunc + +" This must be done as one of the last tests, because it starts the GUI, which +" cannot be undone. +func Test_zz_recording_with_select_mode_utf8_gui() + CheckCanRunGui + + gui -f + call Run_test_recording_with_select_mode_utf8() +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_utf8_comparisons.vim b/src/nvim/testdir/test_utf8_comparisons.vim index fdf9d80802..f3c86b44fb 100644 --- a/src/nvim/testdir/test_utf8_comparisons.vim +++ b/src/nvim/testdir/test_utf8_comparisons.vim @@ -1,12 +1,12 @@ " Tests for case-insensitive UTF-8 comparisons (utf_strnicmp() in mbyte.c) " Also test "g~ap". -function! Ch(a, op, b, expected) +func Ch(a, op, b, expected) call assert_equal(eval(printf('"%s" %s "%s"', a:a, a:op, a:b)), a:expected, \ printf('"%s" %s "%s" should return %d', a:a, a:op, a:b, a:expected)) -endfunction +endfunc -function! Chk(a, b, result) +func Chk(a, b, result) if a:result == 0 call Ch(a:a, '==?', a:b, 1) call Ch(a:a, '!=?', a:b, 0) @@ -86,6 +86,9 @@ endfunc " test that g~ap changes one paragraph only. func Test_gap() new - call feedkeys("iabcd\n\ndefggg0g~ap", "tx") + " setup text + call feedkeys("iabcd\<cr>\<cr>defg", "tx") + " modify only first line + call feedkeys("gg0g~ap", "tx") call assert_equal(["ABCD", "", "defg"], getline(1,3)) endfunc diff --git a/src/nvim/testdir/test_vartabs.vim b/src/nvim/testdir/test_vartabs.vim index 46e0d62313..017bb6675d 100644 --- a/src/nvim/testdir/test_vartabs.vim +++ b/src/nvim/testdir/test_vartabs.vim @@ -135,7 +135,17 @@ func Test_vartabs() bwipeout! endfunc -func! Test_vartabs_breakindent() +func Test_retab_invalid_arg() + new + call setline(1, "\ttext") + retab 0 + call assert_fails("retab -8", 'E487: Argument must be positive') + call assert_fails("retab 10000", 'E475:') + call assert_fails("retab 720575940379279360", 'E475:') + bwipe! +endfunc + +func Test_vartabs_breakindent() if !exists("+breakindent") return endif diff --git a/src/nvim/testdir/test_vimscript.vim b/src/nvim/testdir/test_vimscript.vim index 34733f127f..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 "------------------------------------------------------------------------------- @@ -25,7 +28,7 @@ com! -nargs=1 Xout call Xout(<args>) " in the variable argument list. This function is useful if similar tests are " to be made for a ":return" from a function call or a ":finish" in a script " file. -function! MakeScript(funcname, ...) +func MakeScript(funcname, ...) let script = tempname() execute "redir! >" . script execute "function" a:funcname @@ -1156,6 +1159,82 @@ func Test_type() call assert_equal(v:t_list, type(v:_null_list)) call assert_equal(v:t_dict, type(v:_null_dict)) call assert_equal(v:t_blob, type(v:_null_blob)) + + call assert_equal(0, 0 + v:false) + call assert_equal(1, 0 + v:true) + " call assert_equal(0, 0 + v:none) + call assert_equal(0, 0 + v:null) + + call assert_equal('false', '' . v:false) + call assert_equal('true', '' . v:true) + " call assert_equal('none', '' . v:none) + call assert_equal('null', '' . v:null) + + call assert_true(v:false == 0) + call assert_false(v:false != 0) + call assert_true(v:true == 1) + call assert_false(v:true != 1) + call assert_false(v:true == v:false) + call assert_true(v:true != v:false) + + call assert_true(v:null == 0) + call assert_false(v:null != 0) + " call assert_true(v:none == 0) + " call assert_false(v:none != 0) + + call assert_true(v:false is v:false) + call assert_true(v:true is v:true) + " call assert_true(v:none is v:none) + call assert_true(v:null is v:null) + + call assert_false(v:false isnot v:false) + call assert_false(v:true isnot v:true) + " call assert_false(v:none isnot v:none) + call assert_false(v:null isnot v:null) + + call assert_false(v:false is 0) + call assert_false(v:true is 1) + call assert_false(v:true is v:false) + " call assert_false(v:none is 0) + call assert_false(v:null is 0) + " call assert_false(v:null is v:none) + + call assert_true(v:false isnot 0) + call assert_true(v:true isnot 1) + call assert_true(v:true isnot v:false) + " call assert_true(v:none isnot 0) + call assert_true(v:null isnot 0) + " call assert_true(v:null isnot v:none) + + call assert_equal(v:false, eval(string(v:false))) + call assert_equal(v:true, eval(string(v:true))) + " call assert_equal(v:none, eval(string(v:none))) + call assert_equal(v:null, eval(string(v:null))) + + call assert_equal(v:false, copy(v:false)) + call assert_equal(v:true, copy(v:true)) + " call assert_equal(v:none, copy(v:none)) + call assert_equal(v:null, copy(v:null)) + + call assert_equal([v:false], deepcopy([v:false])) + call assert_equal([v:true], deepcopy([v:true])) + " call assert_equal([v:none], deepcopy([v:none])) + call assert_equal([v:null], deepcopy([v:null])) + + call assert_true(empty(v:false)) + call assert_false(empty(v:true)) + call assert_true(empty(v:null)) + " call assert_true(empty(v:none)) + + func ChangeYourMind() + try + return v:true + finally + return 'something else' + endtry + endfunc + + call ChangeYourMind() endfunc "------------------------------------------------------------------------------- @@ -1668,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:]]*') @@ -1692,6 +1771,26 @@ func Test_function_defined_line() call delete('Xtest.vim') endfunc +func Test_for_over_string() + let res = '' + for c in 'aéc̀d' + let res ..= c .. '-' + endfor + call assert_equal('a-é-c̀-d-', res) + + let res = '' + for c in '' + let res ..= c .. '-' + endfor + call assert_equal('', res) + + let res = '' + for c in v:_null_string + let res ..= c .. '-' + endfor + call assert_equal('', res) +endfunc + "------------------------------------------------------------------------------- " Modelines {{{1 " vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker diff --git a/src/nvim/testdir/test_virtualedit.vim b/src/nvim/testdir/test_virtualedit.vim index 8f992f7501..250b896532 100644 --- a/src/nvim/testdir/test_virtualedit.vim +++ b/src/nvim/testdir/test_virtualedit.vim @@ -81,6 +81,133 @@ func Test_edit_change() normal Cx call assert_equal('x', getline(1)) bwipe! + set virtualedit= +endfunc + +" Tests for pasting at the beginning, end and middle of a tab character +" in virtual edit mode. +func Test_paste_in_tab() + new + call append(0, '') + set virtualedit=all + + " Tests for pasting a register with characterwise mode type + call setreg('"', 'xyz', 'c') + + " paste (p) unnamed register at the beginning of a tab + call setline(1, "a\tb") + call cursor(1, 2, 0) + normal p + call assert_equal('a xyz b', getline(1)) + + " paste (P) unnamed register at the beginning of a tab + call setline(1, "a\tb") + call cursor(1, 2, 0) + normal P + call assert_equal("axyz\tb", getline(1)) + + " paste (p) unnamed register at the end of a tab + call setline(1, "a\tb") + call cursor(1, 2, 6) + normal p + call assert_equal("a\txyzb", getline(1)) + + " paste (P) unnamed register at the end of a tab + call setline(1, "a\tb") + call cursor(1, 2, 6) + normal P + call assert_equal('a xyz b', getline(1)) + + " Tests for pasting a register with blockwise mode type + call setreg('"', 'xyz', 'b') + + " paste (p) unnamed register at the beginning of a tab + call setline(1, "a\tb") + call cursor(1, 2, 0) + normal p + call assert_equal('a xyz b', getline(1)) + + " paste (P) unnamed register at the beginning of a tab + call setline(1, "a\tb") + call cursor(1, 2, 0) + normal P + call assert_equal("axyz\tb", getline(1)) + + " paste (p) unnamed register at the end of a tab + call setline(1, "a\tb") + call cursor(1, 2, 6) + normal p + call assert_equal("a\txyzb", getline(1)) + + " paste (P) unnamed register at the end of a tab + call setline(1, "a\tb") + call cursor(1, 2, 6) + normal P + call assert_equal('a xyz b', getline(1)) + + " Tests for pasting with gp and gP in virtual edit mode + + " paste (gp) unnamed register at the beginning of a tab + call setline(1, "a\tb") + call cursor(1, 2, 0) + normal gp + call assert_equal('a xyz b', getline(1)) + call assert_equal([0, 1, 12, 0, 12], getcurpos()) + + " paste (gP) unnamed register at the beginning of a tab + call setline(1, "a\tb") + call cursor(1, 2, 0) + normal gP + call assert_equal("axyz\tb", getline(1)) + call assert_equal([0, 1, 5, 0, 5], getcurpos()) + + " paste (gp) unnamed register at the end of a tab + call setline(1, "a\tb") + call cursor(1, 2, 6) + normal gp + call assert_equal("a\txyzb", getline(1)) + call assert_equal([0, 1, 6, 0, 12], getcurpos()) + + " paste (gP) unnamed register at the end of a tab + call setline(1, "a\tb") + call cursor(1, 2, 6) + normal gP + call assert_equal('a xyz b', getline(1)) + call assert_equal([0, 1, 12, 0, 12], getcurpos()) + + " Tests for pasting a named register + let @r = 'xyz' + + " paste (gp) named register in the middle of a tab + call setline(1, "a\tb") + call cursor(1, 2, 2) + normal "rgp + call assert_equal('a xyz b', getline(1)) + call assert_equal([0, 1, 8, 0, 8], getcurpos()) + + " paste (gP) named register in the middle of a tab + call setline(1, "a\tb") + call cursor(1, 2, 2) + normal "rgP + call assert_equal('a xyz b', getline(1)) + call assert_equal([0, 1, 7, 0, 7], getcurpos()) + + bwipe! + set virtualedit= +endfunc + +" Test for yanking a few spaces within a tab to a register +func Test_yank_in_tab() + new + let @r = '' + call setline(1, "a\tb") + set virtualedit=all + call cursor(1, 2, 2) + normal "ry5l + call assert_equal(' ', @r) + + bwipe! + set virtualedit= endfunc " Insert "keyword keyw", ESC, C CTRL-N, shows "keyword ykeyword". @@ -216,4 +343,139 @@ func Test_yank_paste_small_del_reg() set virtualedit= endfunc +" After calling s:TryVirtualeditReplace(), line 1 will contain one of these +" two strings, depending on whether virtual editing is on or off. +let s:result_ve_on = 'a x' +let s:result_ve_off = 'x' + +" Utility function for Test_global_local_virtualedit() +func s:TryVirtualeditReplace() + call setline(1, 'a') + normal gg7l + normal rx +endfunc + +" Test for :set and :setlocal +func Test_global_local_virtualedit() + new + + " Verify that 'virtualedit' is initialized to empty, can be set globally to + " all and to empty, and can be set locally to all and to empty. + call s:TryVirtualeditReplace() + call assert_equal(s:result_ve_off, getline(1)) + set ve=all + call s:TryVirtualeditReplace() + call assert_equal(s:result_ve_on, getline(1)) + set ve= + call s:TryVirtualeditReplace() + call assert_equal(s:result_ve_off, getline(1)) + setlocal ve=all + call s:TryVirtualeditReplace() + call assert_equal(s:result_ve_on, getline(1)) + setlocal ve= + call s:TryVirtualeditReplace() + call assert_equal(s:result_ve_off, getline(1)) + + " Verify that :set affects multiple windows. + split + set ve=all + call s:TryVirtualeditReplace() + call assert_equal(s:result_ve_on, getline(1)) + wincmd p + call s:TryVirtualeditReplace() + call assert_equal(s:result_ve_on, getline(1)) + set ve= + wincmd p + call s:TryVirtualeditReplace() + call assert_equal(s:result_ve_off, getline(1)) + bwipe! + + " Verify that :setlocal affects only the current window. + new + split + setlocal ve=all + call s:TryVirtualeditReplace() + call assert_equal(s:result_ve_on, getline(1)) + wincmd p + call s:TryVirtualeditReplace() + call assert_equal(s:result_ve_off, getline(1)) + bwipe! + call s:TryVirtualeditReplace() + call assert_equal(s:result_ve_off, getline(1)) + + " Verify that the buffer 'virtualedit' state follows the global value only + " when empty and that "none" works as expected. + " + " 'virtualedit' State + " +--------+--------------------------+ + " | Local | Global | + " | | | + " +--------+--------+--------+--------+ + " | | "" | "all" | "none" | + " +--------+--------+--------+--------+ + " | "" | off | on | off | + " | "all" | on | on | on | + " | "none" | off | off | off | + " +--------+--------+--------+--------+ + new + + setglobal ve= + setlocal ve= + call s:TryVirtualeditReplace() + call assert_equal(s:result_ve_off, getline(1)) + setlocal ve=all + call s:TryVirtualeditReplace() + call assert_equal(s:result_ve_on, getline(1)) + setlocal ve=none + call s:TryVirtualeditReplace() + call assert_equal(s:result_ve_off, getline(1)) + + setglobal ve=all + setlocal ve= + call s:TryVirtualeditReplace() + call assert_equal(s:result_ve_on, getline(1)) + setlocal ve=all + call s:TryVirtualeditReplace() + call assert_equal(s:result_ve_on, getline(1)) + setlocal ve=none + call s:TryVirtualeditReplace() + call assert_equal(s:result_ve_off, getline(1)) + setlocal ve=NONE + call s:TryVirtualeditReplace() + call assert_equal(s:result_ve_off, getline(1)) + + setglobal ve=none + setlocal ve= + call s:TryVirtualeditReplace() + call assert_equal(s:result_ve_off, getline(1)) + setlocal ve=all + call s:TryVirtualeditReplace() + call assert_equal(s:result_ve_on, getline(1)) + setlocal ve=none + call s:TryVirtualeditReplace() + call assert_equal(s:result_ve_off, getline(1)) + + bwipe! + + " Verify that the 'virtualedit' state is copied to new windows. + new + call s:TryVirtualeditReplace() + call assert_equal(s:result_ve_off, getline(1)) + split + setlocal ve=all + call s:TryVirtualeditReplace() + call assert_equal(s:result_ve_on, getline(1)) + split + call s:TryVirtualeditReplace() + call assert_equal(s:result_ve_on, getline(1)) + setlocal ve= + split + call s:TryVirtualeditReplace() + call assert_equal(s:result_ve_off, getline(1)) + bwipe! + + setlocal virtualedit& + set virtualedit& +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_visual.vim b/src/nvim/testdir/test_visual.vim index 8344598486..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 @@ -433,13 +451,15 @@ func Test_Visual_Block() close! endfunc -func Test_visual_put_in_block() +" Test for 'p'ut in visual block mode +func Test_visual_block_put() 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! + 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, '$')) + bw! endfunc " Visual modes (v V CTRL-V) followed by an operator; count; repeating @@ -610,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 @@ -645,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> @@ -744,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 @@ -764,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 @@ -995,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 @@ -1090,6 +1209,13 @@ func Test_visual_put_blockedit_zy_and_zp() bw! endfunc +func Test_visual_block_yank_zy() + new + " this was reading before the start of the line + exe "norm o\<C-T>\<Esc>\<C-V>zy" + bwipe! +endfunc + func Test_visual_block_with_virtualedit() CheckScreendump @@ -1104,11 +1230,154 @@ func Test_visual_block_with_virtualedit() call term_sendkeys(buf, "\<C-V>gg$") call VerifyScreenDump(buf, 'Test_visual_block_with_virtualedit', {}) + call term_sendkeys(buf, "\<Esc>gg\<C-V>G$") + call VerifyScreenDump(buf, 'Test_visual_block_with_virtualedit2', {}) + " clean up call term_sendkeys(buf, "\<Esc>") call StopVimInTerminal(buf) - call delete('XTest_beval') + call delete('XTest_block') +endfunc + +func Test_visual_block_ctrl_w_f() + " Emtpy block selected in new buffer should not result in an error. + au! BufNew foo sil norm f + edit foo + + 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 + + + + : + r<sfile> + exe "%norm e3\<c-v>kr\t" + : + + : + END + call writefile(lines, 'XvisualReselect') + source XvisualReselect + + bwipe! + 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 + call setline(1, ["aaa", "bbbbbbbbbxbb"]) + /x + exe "normal ggvjfxO" + t0 + normal gNU + bwipe! +endfunc + +" This was leaving the end of the Visual area beyond the end of a line. +" Set 'undolevels' to start a new undo block. +func Test_visual_undo_deletes_last_line() + new + call setline(1, ["aaa", "ccc", "dyd"]) + set undolevels=100 + exe "normal obbbbbbbbbxbb\<Esc>" + set undolevels=100 + /y + 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_window_cmd.vim b/src/nvim/testdir/test_window_cmd.vim index a200bf7d42..ef6dec580f 100644 --- a/src/nvim/testdir/test_window_cmd.vim +++ b/src/nvim/testdir/test_window_cmd.vim @@ -575,7 +575,7 @@ func Test_winrestcmd() only endfunc -function! Fun_RenewFile() +func Fun_RenewFile() " Need to wait a bit for the timestamp to be older. let old_ftime = getftime("tmp.txt") while getftime("tmp.txt") == old_ftime @@ -585,7 +585,7 @@ function! Fun_RenewFile() sp wincmd p edit! tmp.txt -endfunction +endfunc func Test_window_prevwin() " Can we make this work on MS-Windows? @@ -939,4 +939,124 @@ func Test_window_resize() %bwipe! endfunc +func Test_win_move_separator() + edit a + leftabove vsplit b + let w = winwidth(0) + " check win_move_separator from left window on left window + call assert_equal(1, winnr()) + for offset in range(5) + call assert_true(win_move_separator(0, offset)) + call assert_equal(w + offset, winwidth(0)) + call assert_true(0->win_move_separator(-offset)) + call assert_equal(w, winwidth(0)) + endfor + " check win_move_separator from right window on left window number + wincmd l + call assert_notequal(1, winnr()) + for offset in range(5) + call assert_true(1->win_move_separator(offset)) + call assert_equal(w + offset, winwidth(1)) + call assert_true(win_move_separator(1, -offset)) + call assert_equal(w, winwidth(1)) + endfor + " check win_move_separator from right window on left window ID + let id = win_getid(1) + for offset in range(5) + call assert_true(win_move_separator(id, offset)) + call assert_equal(w + offset, winwidth(id)) + call assert_true(id->win_move_separator(-offset)) + call assert_equal(w, winwidth(id)) + endfor + " check win_move_separator from right window on right window is no-op + let w0 = winwidth(0) + call assert_true(win_move_separator(0, 1)) + call assert_equal(w0, winwidth(0)) + call assert_true(win_move_separator(0, -1)) + call assert_equal(w0, winwidth(0)) + " check that win_move_separator doesn't error with offsets beyond moving + " possibility + call assert_true(win_move_separator(id, 5000)) + call assert_true(winwidth(id) > w) + call assert_true(win_move_separator(id, -5000)) + call assert_true(winwidth(id) < w) + " check that win_move_separator returns false for an invalid window + wincmd = + let w = winwidth(0) + call assert_false(win_move_separator(-1, 1)) + call assert_equal(w, winwidth(0)) + " check that win_move_separator returns false for a floating window + let id = nvim_open_win( + \ 0, 0, #{relative: 'editor', row: 2, col: 2, width: 5, height: 3}) + let w = winwidth(id) + call assert_false(win_move_separator(id, 1)) + call assert_equal(w, winwidth(id)) + call nvim_win_close(id, 1) + %bwipe! +endfunc + +func Test_win_move_statusline() + redraw " This test fails in Nvim without a redraw to clear messages. + edit a + leftabove split b + let h = winheight(0) + " check win_move_statusline from top window on top window + call assert_equal(1, winnr()) + for offset in range(5) + call assert_true(win_move_statusline(0, offset)) + call assert_equal(h + offset, winheight(0)) + call assert_true(0->win_move_statusline(-offset)) + call assert_equal(h, winheight(0)) + endfor + " check win_move_statusline from bottom window on top window number + wincmd j + call assert_notequal(1, winnr()) + for offset in range(5) + call assert_true(1->win_move_statusline(offset)) + call assert_equal(h + offset, winheight(1)) + call assert_true(win_move_statusline(1, -offset)) + call assert_equal(h, winheight(1)) + endfor + " check win_move_statusline from bottom window on bottom window + let h0 = winheight(0) + for offset in range(5) + call assert_true(0->win_move_statusline(-offset)) + call assert_equal(h0 - offset, winheight(0)) + call assert_equal(1 + offset, &cmdheight) + call assert_true(win_move_statusline(0, offset)) + call assert_equal(h0, winheight(0)) + call assert_equal(1, &cmdheight) + endfor + call assert_true(win_move_statusline(0, 1)) + call assert_equal(h0, winheight(0)) + call assert_equal(1, &cmdheight) + " check win_move_statusline from bottom window on top window ID + let id = win_getid(1) + for offset in range(5) + call assert_true(win_move_statusline(id, offset)) + call assert_equal(h + offset, winheight(id)) + call assert_true(id->win_move_statusline(-offset)) + call assert_equal(h, winheight(id)) + endfor + " check that win_move_statusline doesn't error with offsets beyond moving + " possibility + call assert_true(win_move_statusline(id, 5000)) + call assert_true(winheight(id) > h) + call assert_true(win_move_statusline(id, -5000)) + call assert_true(winheight(id) < h) + " check that win_move_statusline returns false for an invalid window + wincmd = + let h = winheight(0) + call assert_false(win_move_statusline(-1, 1)) + call assert_equal(h, winheight(0)) + " check that win_move_statusline returns false for a floating window + let id = nvim_open_win( + \ 0, 0, #{relative: 'editor', row: 2, col: 2, width: 5, height: 3}) + let h = winheight(id) + call assert_false(win_move_statusline(id, 1)) + call assert_equal(h, winheight(id)) + call nvim_win_close(id, 1) + %bwipe! +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_writefile.vim b/src/nvim/testdir/test_writefile.vim index aa7882d129..b42665c9b5 100644 --- a/src/nvim/testdir/test_writefile.vim +++ b/src/nvim/testdir/test_writefile.vim @@ -43,7 +43,7 @@ func Test_writefile_fails_gently() endfunc func Test_writefile_fails_conversion() - if !has('iconv') || system('uname -s') =~ 'SunOS' + if !has('iconv') || has('sun') return endif " Without a backup file the write won't happen if there is a conversion @@ -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 5fec41f9a5..b262fc6c54 100644 --- a/src/nvim/tui/input.c +++ b/src/nvim/tui/input.c @@ -5,7 +5,7 @@ #include "nvim/api/private/helpers.h" #include "nvim/api/vim.h" #include "nvim/ascii.h" -#include "nvim/aucmd.h" +#include "nvim/autocmd.h" #include "nvim/charset.h" #include "nvim/ex_docmd.h" #include "nvim/macros.h" @@ -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] = '-'; @@ -315,7 +315,6 @@ static TermKeyResult tk_getkey(TermKey *tk, TermKeyKey *key, bool force) return force ? termkey_getkey_force(tk, key) : termkey_getkey(tk, key); } -static void tinput_timer_cb(TimeWatcher *watcher, void *data); static void tk_getkeys(TermInput *input, bool force) { @@ -379,7 +378,7 @@ static bool handle_focus_event(TermInput *input) bool focus_gained = *rbuffer_get(input->read_stream.buffer, 2) == 'I'; // Advance past the sequence rbuffer_consumed(input->read_stream.buffer, 3); - aucmd_schedule_focusgained(focus_gained); + autocmd_schedule_focusgained(focus_gained); return true; } return false; diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index e7a60aca49..58061f020d 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -1877,7 +1877,7 @@ static void patch_terminfo_bugs(TUIData *data, const char *term, const char *col "\x1b[?c"); } else if (konsolev > 0 && konsolev < 180770) { // Konsole before version 18.07.70: set up a nonce profile. This has - // side-effects on temporary font resizing. #6798 + // side effects on temporary font resizing. #6798 data->unibi_ext.set_cursor_style = (int)unibi_add_ext_str(ut, "Ss", TMUX_WRAP(tmux, "\x1b]50;CursorShape=%?" diff --git a/src/nvim/types.h b/src/nvim/types.h index 604155c33e..73cd2204d6 100644 --- a/src/nvim/types.h +++ b/src/nvim/types.h @@ -32,4 +32,6 @@ typedef enum { kTrue = 1, } TriState; +typedef struct Decoration Decoration; + #endif // NVIM_TYPES_H diff --git a/src/nvim/ui.c b/src/nvim/ui.c index 1aadaf5c9d..31b9614c34 100644 --- a/src/nvim/ui.c +++ b/src/nvim/ui.c @@ -8,7 +8,7 @@ #include <string.h> #include "nvim/ascii.h" -#include "nvim/aucmd.h" +#include "nvim/autocmd.h" #include "nvim/charset.h" #include "nvim/cursor.h" #include "nvim/cursor_shape.h" diff --git a/src/nvim/undo.c b/src/nvim/undo.c index d18f35a43a..2d8df4cad8 100644 --- a/src/nvim/undo.c +++ b/src/nvim/undo.c @@ -2633,6 +2633,10 @@ static void u_undo_end(bool did_undo, bool absolute, bool quiet) } } + if (VIsual_active) { + check_pos(curbuf, &VIsual); + } + smsg_attr_keep(0, _("%" PRId64 " %s; %s #%" PRId64 " %s"), u_oldcount < 0 ? (int64_t)-u_oldcount : (int64_t)u_oldcount, diff --git a/src/nvim/version.c b/src/nvim/version.c index 5e2a81795a..71cca52773 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -2093,7 +2093,7 @@ void list_in_columns(char_u **items, int size, int current) // The rightmost column doesn't need a separator. // Sacrifice it to fit in one more column if possible. - int ncol = (int)(Columns + 1) / width; + int ncol = (Columns + 1) / width; int nrow = item_count / ncol + (item_count % ncol ? 1 : 0); int cur_row = 1; diff --git a/src/nvim/vim.h b/src/nvim/vim.h index 2f8ddd1e88..6e0e9922a6 100644 --- a/src/nvim/vim.h +++ b/src/nvim/vim.h @@ -143,6 +143,7 @@ enum { EXPAND_COMPILER, EXPAND_USER_DEFINED, EXPAND_USER_LIST, + EXPAND_USER_LUA, EXPAND_SHELLCMD, EXPAND_CSCOPE, EXPAND_SIGN, diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c index ba6cfab98b..9d1318724e 100644 --- a/src/nvim/viml/parser/expressions.c +++ b/src/nvim/viml/parser/expressions.c @@ -1536,7 +1536,7 @@ static inline void east_set_error(const ParserState *const pstate, ExprASTError /* TODO(ZyX-I): Extend syntax to allow ${expr}. This is needed to */ \ /* handle environment variables like those bash uses for */ \ /* `export -f`: their names consist not only of alphanumeric */ \ - /* characetrs. */ \ + /* characters. */ \ case kExprNodeComplexIdentifier: \ case kExprNodePlainIdentifier: \ case kExprNodeCurlyBracesIdentifier: { \ @@ -1592,7 +1592,7 @@ typedef struct { /// string is a regex. /// @param[in] is_invalid Whether currently processed token is not valid. static void parse_quoted_string(ParserState *const pstate, ExprASTNode *const node, - const LexExprToken token, const ExprASTStack ast_stack, + const LexExprToken token, const ExprASTStack *ast_stack, const bool is_invalid) FUNC_ATTR_NONNULL_ALL { @@ -2907,7 +2907,7 @@ viml_pexpr_parse_no_paren_closing_error: {} ? kExprNodeDoubleQuotedString : kExprNodeSingleQuotedString)); *top_node_p = cur_node; - parse_quoted_string(pstate, cur_node, cur_token, ast_stack, is_invalid); + parse_quoted_string(pstate, cur_node, cur_token, &ast_stack, is_invalid); want_node = kENodeOperator; break; } diff --git a/src/nvim/viml/parser/expressions.h b/src/nvim/viml/parser/expressions.h index fe9327b27d..9d0bc9d468 100644 --- a/src/nvim/viml/parser/expressions.h +++ b/src/nvim/viml/parser/expressions.h @@ -57,7 +57,7 @@ typedef enum { } LexExprTokenType; typedef enum { - kExprCmpEqual, ///< Equality, unequality. + kExprCmpEqual, ///< Equality, inequality. kExprCmpMatches, ///< Matches regex, not matches regex. kExprCmpGreater, ///< `>` or `<=` kExprCmpGreaterOrEqual, ///< `>=` or `<`. diff --git a/src/nvim/window.c b/src/nvim/window.c index c711f462d1..83048d911f 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; @@ -577,18 +577,19 @@ static void cmd_with_count(char *cmd, char_u *bufp, size_t bufsize, int64_t Pren void win_set_buf(Window window, Buffer buffer, bool noautocmd, Error *err) { - win_T *win = find_window_by_handle(window, err), *save_curwin = curwin; + win_T *win = find_window_by_handle(window, err); buf_T *buf = find_buffer_by_handle(buffer, err); - tabpage_T *tab = win_find_tabpage(win), *save_curtab = curtab; + tabpage_T *tab = win_find_tabpage(win); if (!win || !buf) { return; } - if (noautocmd) { block_autocmds(); } - if (switch_win_noblock(&save_curwin, &save_curtab, win, tab, false) == FAIL) { + + switchwin_T switchwin; + if (switch_win_noblock(&switchwin, win, tab, false) == FAIL) { api_set_error(err, kErrorTypeException, "Failed to switch to window %d", @@ -608,7 +609,7 @@ void win_set_buf(Window window, Buffer buffer, bool noautocmd, Error *err) // So do it now. validate_cursor(); - restore_win_noblock(save_curwin, save_curtab, false); + restore_win_noblock(&switchwin, false); if (noautocmd) { unblock_autocmds(); } @@ -957,6 +958,11 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir) int wmh1; bool did_set_fraction = false; + // aucmd_win should always remain floating + if (new_wp != NULL && new_wp == aucmd_win) { + return FAIL; + } + if (flags & WSP_TOP) { oldwin = firstwin; } else if (flags & WSP_BOT || curwin->w_floating) { @@ -1481,8 +1487,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); } /* @@ -1591,7 +1595,7 @@ int make_windows(int count, bool vertical) int todo; if (vertical) { - // Each windows needs at least 'winminwidth' lines and a separator + // Each window needs at least 'winminwidth' lines and a separator // column. maxcount = (curwin->w_width + curwin->w_vsep_width - (p_wiw - p_wmw)) / (p_wmw + 1); @@ -1728,6 +1732,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); @@ -1828,6 +1838,9 @@ static void win_totop(int size, int flags) beep_flush(); return; } + if (curwin == aucmd_win) { + return; + } if (curwin->w_floating) { ui_comp_remove_grid(&curwin->w_grid_alloc); @@ -2119,7 +2132,7 @@ static void win_equal_rec(win_T *next_curwin, bool current, frame_T *topfr, int m = frame_minheight(topfr, next_curwin); room = height - m; if (room < 0) { - // The room is less then 'winheight', use all space for the + // The room is less than 'winheight', use all space for the // current window. next_curwin_size = p_wh + room; room = 0; @@ -2289,7 +2302,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; } @@ -2367,6 +2380,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 @@ -2431,7 +2460,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; @@ -2461,9 +2490,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 @@ -3055,9 +3093,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; } @@ -3610,7 +3660,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) { @@ -4644,20 +4696,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); @@ -5795,7 +5856,6 @@ void win_drag_vsep_line(win_T *dragwin, int offset) } fr = curfr; // put fr at window that grows } - assert(fr); // Not enough room if (room < offset) { @@ -5808,7 +5868,9 @@ void win_drag_vsep_line(win_T *dragwin, int offset) } if (fr == NULL) { - return; // Safety check, should not happen. + // This can happen when calling win_move_separator() on the rightmost + // window. Just don't do anything. + return; } // grow frame fr by offset lines @@ -6630,20 +6692,27 @@ static win_T *get_snapshot_focus(int idx) /// triggered, another tabpage access is limited. /// /// @return FAIL if switching to "win" failed. -int switch_win(win_T **save_curwin, tabpage_T **save_curtab, win_T *win, tabpage_T *tp, - bool no_display) +int switch_win(switchwin_T *switchwin, win_T *win, tabpage_T *tp, bool no_display) { block_autocmds(); - return switch_win_noblock(save_curwin, save_curtab, win, tp, no_display); + return switch_win_noblock(switchwin, win, tp, no_display); } // As switch_win() but without blocking autocommands. -int switch_win_noblock(win_T **save_curwin, tabpage_T **save_curtab, win_T *win, tabpage_T *tp, - bool no_display) +int switch_win_noblock(switchwin_T *switchwin, win_T *win, tabpage_T *tp, bool no_display) { - *save_curwin = curwin; + memset(switchwin, 0, sizeof(switchwin_T)); + switchwin->sw_curwin = curwin; + if (win == curwin) { + switchwin->sw_same_win = true; + } else { + // Disable Visual selection, because redrawing may fail. + switchwin->sw_visual_active = VIsual_active; + VIsual_active = false; + } + if (tp != NULL) { - *save_curtab = curtab; + switchwin->sw_curtab = curtab; if (no_display) { curtab->tp_firstwin = firstwin; curtab->tp_lastwin = lastwin; @@ -6665,28 +6734,33 @@ int switch_win_noblock(win_T **save_curwin, tabpage_T **save_curtab, win_T *win, // Restore current tabpage and window saved by switch_win(), if still valid. // When "no_display" is true the display won't be affected, no redraw is // triggered. -void restore_win(win_T *save_curwin, tabpage_T *save_curtab, bool no_display) +void restore_win(switchwin_T *switchwin, bool no_display) { - restore_win_noblock(save_curwin, save_curtab, no_display); + restore_win_noblock(switchwin, no_display); unblock_autocmds(); } // As restore_win() but without unblocking autocommands. -void restore_win_noblock(win_T *save_curwin, tabpage_T *save_curtab, bool no_display) +void restore_win_noblock(switchwin_T *switchwin, bool no_display) { - if (save_curtab != NULL && valid_tabpage(save_curtab)) { + if (switchwin->sw_curtab != NULL && valid_tabpage(switchwin->sw_curtab)) { if (no_display) { curtab->tp_firstwin = firstwin; curtab->tp_lastwin = lastwin; - curtab = save_curtab; + curtab = switchwin->sw_curtab; firstwin = curtab->tp_firstwin; lastwin = curtab->tp_lastwin; } else { - goto_tabpage_tp(save_curtab, false, false); + goto_tabpage_tp(switchwin->sw_curtab, false, false); } } - if (win_valid(save_curwin)) { - curwin = save_curwin; + + if (!switchwin->sw_same_win) { + VIsual_active = switchwin->sw_visual_active; + } + + if (win_valid(switchwin->sw_curwin)) { + curwin = switchwin->sw_curwin; curbuf = curwin->w_buffer; } // If called by win_execute() and executing the command changed the diff --git a/src/nvim/window.h b/src/nvim/window.h index 7e465a9f08..e2fd2c515d 100644 --- a/src/nvim/window.h +++ b/src/nvim/window.h @@ -4,6 +4,7 @@ #include <stdbool.h> #include "nvim/buffer_defs.h" +#include "nvim/mark.h" // Values for file_name_in_line() #define FNAME_MESS 1 // give error message @@ -32,6 +33,38 @@ #define MIN_COLUMNS 12 // minimal columns for screen #define MIN_LINES 2 // minimal lines for screen +/// Structure used by switch_win() to pass values to restore_win() +typedef struct { + win_T *sw_curwin; + tabpage_T *sw_curtab; + bool sw_same_win; ///< VIsual_active was not reset + bool sw_visual_active; +} switchwin_T; + +/// Execute a block of code in the context of window `wp` in tabpage `tp`. +/// Ensures the status line is redrawn and cursor position is valid if it is moved. +#define WIN_EXECUTE(wp, tp, block) \ + do { \ + win_T *const wp_ = (wp); \ + const pos_T curpos_ = wp_->w_cursor; \ + switchwin_T switchwin_; \ + if (switch_win_noblock(&switchwin_, wp_, (tp), true) == OK) { \ + check_cursor(); \ + block; \ + } \ + restore_win_noblock(&switchwin_, true); \ + /* Update the status line if the cursor moved. */ \ + if (win_valid(wp_) && !equalpos(curpos_, wp_->w_cursor)) { \ + wp_->w_redr_status = true; \ + } \ + /* In case the command moved the cursor or changed the Visual area, */ \ + /* check it is valid. */ \ + check_cursor(); \ + if (VIsual_active) { \ + check_pos(curbuf, &VIsual); \ + } \ + } while (false) + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "window.h.generated.h" #endif diff --git a/test/README.md b/test/README.md index c6e173ead2..cc630cb8bf 100644 --- a/test/README.md +++ b/test/README.md @@ -38,7 +38,7 @@ Layout - `/test/functional` : functional tests - `/test/unit` : unit tests - `/test/config` : contains `*.in` files which are transformed into `*.lua` - files using `configure_file` CMake command: this is for acessing CMake + files using `configure_file` CMake command: this is for accessing CMake variables in lua tests. - `/test/includes` : include-files for use by luajit `ffi.cdef` C definitions parser: normally used to make macros not accessible via this mechanism @@ -197,7 +197,7 @@ Guidelines (success + fail + error + pending) is the same in all environments. - *Note:* `pending()` is ignored if it is missing an argument, unless it is [contained in an `it()` block](https://github.com/neovim/neovim/blob/d21690a66e7eb5ebef18046c7a79ef898966d786/test/functional/ex_cmds/grep_spec.lua#L11). - Provide empty function argument if the `pending()` call is outside of `it()` + Provide empty function argument if the `pending()` call is outside `it()` ([example](https://github.com/neovim/neovim/commit/5c1dc0fbe7388528875aff9d7b5055ad718014de#diff-bf80b24c724b0004e8418102f68b0679R18)). - Really long `source([=[...]=])` blocks may break Vim's Lua syntax highlighting. Try `:syntax sync fromstart` to fix it. diff --git a/test/functional/api/autocmd_spec.lua b/test/functional/api/autocmd_spec.lua new file mode 100644 index 0000000000..e50e812b4b --- /dev/null +++ b/test/functional/api/autocmd_spec.lua @@ -0,0 +1,777 @@ +local helpers = require('test.functional.helpers')(after_each) + +local clear = helpers.clear +local command = helpers.command +local eq = helpers.eq +local neq = helpers.neq +local exec_lua = helpers.exec_lua +local matches = helpers.matches +local meths = helpers.meths +local source = helpers.source + +before_each(clear) + +describe('autocmd api', function() + describe('nvim_create_autocmd', function() + it('does not allow "command" and "callback" in the same autocmd', function() + local ok, _ = pcall(meths.create_autocmd, "BufReadPost", { + pattern = "*.py,*.pyi", + command = "echo 'Should Have Errored", + callback = "not allowed", + }) + + eq(false, ok) + end) + + it('doesnt leak when you use ++once', function() + eq(1, exec_lua([[ + local count = 0 + + vim.api.nvim_create_autocmd("FileType", { + pattern = "*", + callback = function() count = count + 1 end, + once = true + }) + + vim.cmd "set filetype=txt" + vim.cmd "set filetype=python" + + return count + ]], {})) + end) + + it('allows passing buffer by key', function() + meths.set_var('called', 0) + + meths.create_autocmd("FileType", { + command = "let g:called = g:called + 1", + buffer = 0, + }) + + meths.command "set filetype=txt" + eq(1, meths.get_var('called')) + + -- switch to a new buffer + meths.command "new" + meths.command "set filetype=python" + + eq(1, meths.get_var('called')) + end) + + it('does not allow passing buffer and patterns', function() + local ok = pcall(meths.create_autocmd, "Filetype", { + command = "let g:called = g:called + 1", + buffer = 0, + pattern = "*.py", + }) + + eq(false, ok) + end) + + it('does not allow passing invalid buffers', function() + local ok, msg = pcall(meths.create_autocmd, "Filetype", { + command = "let g:called = g:called + 1", + buffer = -1, + }) + + eq(false, ok) + matches('Invalid buffer id', msg) + end) + + it('errors on non-functions for cb', function() + eq(false, pcall(exec_lua, [[ + vim.api.nvim_create_autocmd("BufReadPost", { + pattern = "*.py,*.pyi", + callback = 5, + }) + ]])) + end) + + it('allow passing pattern and <buffer> in same pattern', function() + local ok = pcall(meths.create_autocmd, "BufReadPost", { + pattern = "*.py,<buffer>", + command = "echo 'Should Not Error'" + }) + + eq(true, ok) + end) + + it('should handle multiple values as comma separated list', function() + meths.create_autocmd("BufReadPost", { + pattern = "*.py,*.pyi", + command = "echo 'Should Not Have Errored'" + }) + + -- We should have one autocmd for *.py and one for *.pyi + eq(2, #meths.get_autocmds { event = "BufReadPost" }) + end) + + it('should handle multiple values as array', function() + meths.create_autocmd("BufReadPost", { + pattern = { "*.py", "*.pyi", }, + command = "echo 'Should Not Have Errored'" + }) + + -- We should have one autocmd for *.py and one for *.pyi + eq(2, #meths.get_autocmds { event = "BufReadPost" }) + end) + + describe('desc', function() + it('can add description to one autocmd', function() + meths.create_autocmd("BufReadPost", { + pattern = "*.py", + command = "echo 'Should Not Have Errored'", + desc = "Can show description", + }) + + eq("Can show description", meths.get_autocmds { event = "BufReadPost" }[1].desc) + end) + + it('can add description to multiple autocmd', function() + meths.create_autocmd("BufReadPost", { + pattern = {"*.py", "*.pyi"}, + command = "echo 'Should Not Have Errored'", + desc = "Can show description", + }) + + local aus = meths.get_autocmds { event = "BufReadPost" } + eq(2, #aus) + eq("Can show description", aus[1].desc) + eq("Can show description", aus[2].desc) + end) + end) + + pending('script and verbose settings', function() + it('marks API client', function() + meths.create_autocmd("BufReadPost", { + pattern = "*.py", + command = "echo 'Should Not Have Errored'", + desc = "Can show description", + }) + + local aus = meths.get_autocmds { event = "BufReadPost" } + eq(1, #aus, aus) + end) + end) + end) + + describe('nvim_get_autocmds', function() + describe('events', function() + it('should return one autocmd when there is only one for an event', function() + command [[au! InsertEnter]] + command [[au InsertEnter * :echo "1"]] + + local aus = meths.get_autocmds { event = "InsertEnter" } + eq(1, #aus) + end) + + it('should return two autocmds when there are two for an event', function() + command [[au! InsertEnter]] + command [[au InsertEnter * :echo "1"]] + command [[au InsertEnter * :echo "2"]] + + local aus = meths.get_autocmds { event = "InsertEnter" } + eq(2, #aus) + end) + + it('should return the same thing if you use string or list', function() + command [[au! InsertEnter]] + command [[au InsertEnter * :echo "1"]] + command [[au InsertEnter * :echo "2"]] + + local string_aus = meths.get_autocmds { event = "InsertEnter" } + local array_aus = meths.get_autocmds { event = { "InsertEnter" } } + eq(string_aus, array_aus) + end) + + it('should return two autocmds when there are two for an event', function() + command [[au! InsertEnter]] + command [[au! InsertLeave]] + command [[au InsertEnter * :echo "1"]] + command [[au InsertEnter * :echo "2"]] + + local aus = meths.get_autocmds { event = { "InsertEnter", "InsertLeave" } } + eq(2, #aus) + end) + + it('should return different IDs for different autocmds', function() + command [[au! InsertEnter]] + command [[au! InsertLeave]] + command [[au InsertEnter * :echo "1"]] + source [[ + call nvim_create_autocmd("InsertLeave", #{ + \ command: ":echo 2", + \ }) + ]] + + local aus = meths.get_autocmds { event = { "InsertEnter", "InsertLeave" } } + local first = aus[1] + eq(first.id, nil) + + -- TODO: Maybe don't have this number, just assert it's not nil + local second = aus[2] + neq(second.id, nil) + + meths.del_autocmd(second.id) + local new_aus = meths.get_autocmds { event = { "InsertEnter", "InsertLeave" } } + eq(1, #new_aus) + eq(first, new_aus[1]) + end) + + it('should return event name', function() + command [[au! InsertEnter]] + command [[au InsertEnter * :echo "1"]] + + local aus = meths.get_autocmds { event = "InsertEnter" } + eq({ { buflocal = false, command = ':echo "1"', event = "InsertEnter", once = false, pattern = "*" } }, aus) + end) + end) + + describe('groups', function() + before_each(function() + command [[au! InsertEnter]] + + command [[au InsertEnter * :echo "No Group"]] + + command [[augroup GroupOne]] + command [[ au InsertEnter * :echo "GroupOne:1"]] + command [[augroup END]] + + command [[augroup GroupTwo]] + command [[ au InsertEnter * :echo "GroupTwo:2"]] + command [[ au InsertEnter * :echo "GroupTwo:3"]] + command [[augroup END]] + end) + + it('should return all groups if no group is specified', function() + local aus = meths.get_autocmds { event = "InsertEnter" } + if #aus ~= 4 then + eq({}, aus) + end + + eq(4, #aus) + end) + + it('should return only the group specified', function() + local aus = meths.get_autocmds { + event = "InsertEnter", + group = "GroupOne", + } + + eq(1, #aus) + eq([[:echo "GroupOne:1"]], aus[1].command) + end) + + it('should return only the group specified, multiple values', function() + local aus = meths.get_autocmds { + event = "InsertEnter", + group = "GroupTwo", + } + + eq(2, #aus) + eq([[:echo "GroupTwo:2"]], aus[1].command) + eq([[:echo "GroupTwo:3"]], aus[2].command) + end) + end) + + describe('groups: 2', function() + it('raises error for undefined augroup', function() + local success, code = unpack(meths.exec_lua([[ + return {pcall(function() + vim.api.nvim_create_autocmd("FileType", { + pattern = "*", + group = "NotDefined", + command = "echo 'hello'", + }) + end)} + ]], {})) + + eq(false, success) + matches('invalid augroup: NotDefined', code) + end) + end) + + describe('patterns', function() + before_each(function() + command [[au! InsertEnter]] + + command [[au InsertEnter * :echo "No Group"]] + command [[au InsertEnter *.one :echo "GroupOne:1"]] + command [[au InsertEnter *.two :echo "GroupTwo:2"]] + command [[au InsertEnter *.two :echo "GroupTwo:3"]] + command [[au InsertEnter <buffer> :echo "Buffer"]] + end) + + it('should should return for literal match', function() + local aus = meths.get_autocmds { + event = "InsertEnter", + pattern = "*" + } + + eq(1, #aus) + eq([[:echo "No Group"]], aus[1].command) + end) + + it('should return for multiple matches', function() + -- vim.api.nvim_get_autocmds + local aus = meths.get_autocmds { + event = "InsertEnter", + pattern = { "*.one", "*.two" }, + } + + eq(3, #aus) + eq([[:echo "GroupOne:1"]], aus[1].command) + eq([[:echo "GroupTwo:2"]], aus[2].command) + eq([[:echo "GroupTwo:3"]], aus[3].command) + end) + + it('should work for buffer autocmds', function() + local normalized_aus = meths.get_autocmds { + event = "InsertEnter", + pattern = "<buffer=1>", + } + + local raw_aus = meths.get_autocmds { + event = "InsertEnter", + pattern = "<buffer>", + } + + local zero_aus = meths.get_autocmds { + event = "InsertEnter", + pattern = "<buffer=0>", + } + + eq(normalized_aus, raw_aus) + eq(normalized_aus, zero_aus) + eq([[:echo "Buffer"]], normalized_aus[1].command) + end) + end) + end) + + describe('nvim_do_autocmd', function() + it("can trigger builtin autocmds", function() + meths.set_var("autocmd_executed", false) + + meths.create_autocmd("BufReadPost", { + pattern = "*", + command = "let g:autocmd_executed = v:true", + }) + + eq(false, meths.get_var("autocmd_executed")) + meths.do_autocmd("BufReadPost", {}) + eq(true, meths.get_var("autocmd_executed")) + end) + + it("can pass the buffer", function() + meths.set_var("buffer_executed", -1) + eq(-1, meths.get_var("buffer_executed")) + + meths.create_autocmd("BufLeave", { + pattern = "*", + command = 'let g:buffer_executed = +expand("<abuf>")', + }) + + -- Doesn't execute for other non-matching events + meths.do_autocmd("CursorHold", { buffer = 1 }) + eq(-1, meths.get_var("buffer_executed")) + + meths.do_autocmd("BufLeave", { buffer = 1 }) + eq(1, meths.get_var("buffer_executed")) + end) + + it("can pass the filename, pattern match", function() + meths.set_var("filename_executed", 'none') + eq('none', meths.get_var("filename_executed")) + + meths.create_autocmd("BufEnter", { + pattern = "*.py", + command = 'let g:filename_executed = expand("<afile>")', + }) + + -- Doesn't execute for other non-matching events + meths.do_autocmd("CursorHold", { buffer = 1 }) + eq('none', meths.get_var("filename_executed")) + + meths.command('edit __init__.py') + eq('__init__.py', meths.get_var("filename_executed")) + end) + + it('cannot pass buf and fname', function() + local ok = pcall(meths.do_autocmd, "BufReadPre", { pattern = "literally_cannot_error.rs", buffer = 1 }) + eq(false, ok) + end) + + it("can pass the filename, exact match", function() + meths.set_var("filename_executed", 'none') + eq('none', meths.get_var("filename_executed")) + + meths.command('edit other_file.txt') + meths.command('edit __init__.py') + eq('none', meths.get_var("filename_executed")) + + meths.create_autocmd("CursorHoldI", { + pattern = "__init__.py", + command = 'let g:filename_executed = expand("<afile>")', + }) + + -- Doesn't execute for other non-matching events + meths.do_autocmd("CursorHoldI", { buffer = 1 }) + eq('none', meths.get_var("filename_executed")) + + meths.do_autocmd("CursorHoldI", { buffer = tonumber(meths.get_current_buf()) }) + eq('__init__.py', meths.get_var("filename_executed")) + + -- Reset filename + meths.set_var("filename_executed", 'none') + + meths.do_autocmd("CursorHoldI", { pattern = '__init__.py' }) + eq('__init__.py', meths.get_var("filename_executed")) + end) + + it("works with user autocmds", function() + meths.set_var("matched", 'none') + + meths.create_autocmd("User", { + pattern = "TestCommand", + command = 'let g:matched = "matched"' + }) + + meths.do_autocmd("User", { pattern = "OtherCommand" }) + eq('none', meths.get_var('matched')) + meths.do_autocmd("User", { pattern = "TestCommand" }) + eq('matched', meths.get_var('matched')) + end) + end) + + describe('nvim_create_augroup', function() + before_each(function() + clear() + + meths.set_var('executed', 0) + end) + + local make_counting_autocmd = function(opts) + opts = opts or {} + + local resulting = { + pattern = "*", + command = "let g:executed = g:executed + 1", + } + + resulting.group = opts.group + resulting.once = opts.once + + meths.create_autocmd("FileType", resulting) + end + + local set_ft = function(ft) + ft = ft or "txt" + source(string.format("set filetype=%s", ft)) + end + + local get_executed_count = function() + return meths.get_var('executed') + end + + it('can be added in a group', function() + local augroup = "TestGroup" + meths.create_augroup(augroup, { clear = true }) + make_counting_autocmd { group = augroup } + + set_ft("txt") + set_ft("python") + + eq(get_executed_count(), 2) + end) + + it('works getting called multiple times', function() + make_counting_autocmd() + set_ft() + set_ft() + set_ft() + + eq(get_executed_count(), 3) + end) + + it('handles ++once', function() + make_counting_autocmd {once = true} + set_ft('txt') + set_ft('help') + set_ft('txt') + set_ft('help') + + eq(get_executed_count(), 1) + end) + + it('errors on unexpected keys', function() + local success, code = pcall(meths.create_autocmd, "FileType", { + pattern = "*", + not_a_valid_key = "NotDefined", + }) + + eq(false, success) + matches('not_a_valid_key', code) + end) + + it('can execute simple callback', function() + exec_lua([[ + vim.g.executed = false + + vim.api.nvim_create_autocmd("FileType", { + pattern = "*", + callback = function() vim.g.executed = true end, + }) + ]], {}) + + + eq(true, exec_lua([[ + vim.cmd "set filetype=txt" + return vim.g.executed + ]], {})) + end) + + it('calls multiple lua callbacks for the same autocmd execution', function() + eq(4, exec_lua([[ + local count = 0 + local counter = function() + count = count + 1 + end + + vim.api.nvim_create_autocmd("FileType", { + pattern = "*", + callback = counter, + }) + + vim.api.nvim_create_autocmd("FileType", { + pattern = "*", + callback = counter, + }) + + vim.cmd "set filetype=txt" + vim.cmd "set filetype=txt" + + return count + ]], {})) + end) + + it('properly releases functions with ++once', function() + exec_lua([[ + WeakTable = setmetatable({}, { __mode = "k" }) + + OnceCount = 0 + + MyVal = {} + WeakTable[MyVal] = true + + vim.api.nvim_create_autocmd("FileType", { + pattern = "*", + callback = function() + OnceCount = OnceCount + 1 + MyVal = {} + end, + once = true + }) + ]]) + + command [[set filetype=txt]] + eq(1, exec_lua([[return OnceCount]], {})) + + exec_lua([[collectgarbage()]], {}) + + command [[set filetype=txt]] + eq(1, exec_lua([[return OnceCount]], {})) + + eq(0, exec_lua([[ + local count = 0 + for _ in pairs(WeakTable) do + count = count + 1 + end + + return count + ]]), "Should have no keys remaining") + end) + + it('groups can be cleared', function() + local augroup = "TestGroup" + meths.create_augroup(augroup, { clear = true }) + meths.create_autocmd("FileType", { + group = augroup, + command = "let g:executed = g:executed + 1" + }) + + set_ft("txt") + set_ft("txt") + eq(2, get_executed_count(), "should only count twice") + + meths.create_augroup(augroup, { clear = true }) + eq({}, meths.get_autocmds { group = augroup }) + + set_ft("txt") + set_ft("txt") + eq(2, get_executed_count(), "No additional counts") + end) + + it('groups work with once', function() + local augroup = "TestGroup" + + meths.create_augroup(augroup, { clear = true }) + make_counting_autocmd { group = augroup, once = true } + + set_ft("txt") + set_ft("python") + + eq(get_executed_count(), 1) + end) + + it('autocmds can be registered multiple times.', function() + local augroup = "TestGroup" + + meths.create_augroup(augroup, { clear = true }) + make_counting_autocmd { group = augroup, once = false } + make_counting_autocmd { group = augroup, once = false } + make_counting_autocmd { group = augroup, once = false } + + set_ft("txt") + set_ft("python") + + eq(get_executed_count(), 3 * 2) + end) + + it('can be deleted', function() + local augroup = "WillBeDeleted" + + meths.create_augroup(augroup, { clear = true }) + meths.create_autocmd({"Filetype"}, { + pattern = "*", + command = "echo 'does not matter'", + }) + + -- Clears the augroup from before, which erases the autocmd + meths.create_augroup(augroup, { clear = true }) + + local result = #meths.get_autocmds { group = augroup } + + eq(0, result) + end) + + it('can be used for buffer local autocmds', function() + local augroup = "WillBeDeleted" + + meths.set_var("value_set", false) + + meths.create_augroup(augroup, { clear = true }) + meths.create_autocmd("Filetype", { + pattern = "<buffer>", + command = "let g:value_set = v:true", + }) + + command "new" + command "set filetype=python" + + eq(false, meths.get_var("value_set")) + end) + + it('can accept vimscript functions', function() + source [[ + let g:vimscript_executed = 0 + + function! MyVimscriptFunction() abort + let g:vimscript_executed = g:vimscript_executed + 1 + endfunction + + call nvim_create_autocmd("FileType", #{ + \ pattern: ["python", "javascript"], + \ callback: "MyVimscriptFunction", + \ }) + + set filetype=txt + set filetype=python + set filetype=txt + set filetype=javascript + set filetype=txt + ]] + + eq(2, meths.get_var("vimscript_executed")) + end) + end) + + describe('augroup!', function() + it('legacy: should clear and not return any autocmds for delete groups', function() + command('augroup TEMP_A') + command(' autocmd! BufReadPost *.py :echo "Hello"') + command('augroup END') + + command('augroup! TEMP_A') + + eq(false, pcall(meths.get_autocmds, { group = 'TEMP_A' })) + + -- For some reason, augroup! doesn't clear the autocmds themselves, which is just wild + -- but we managed to keep this behavior. + eq(1, #meths.get_autocmds { event = 'BufReadPost' }) + end) + + it('legacy: remove augroups that have no autocmds', function() + command('augroup TEMP_AB') + command('augroup END') + + command('augroup! TEMP_AB') + + eq(false, pcall(meths.get_autocmds, { group = 'TEMP_AB' })) + eq(0, #meths.get_autocmds { event = 'BufReadPost' }) + end) + + it('legacy: multiple remove and add augroup', function() + command('augroup TEMP_ABC') + command(' au!') + command(' autocmd BufReadPost *.py echo "Hello"') + command('augroup END') + + command('augroup! TEMP_ABC') + + -- Should still have one autocmd :'( + local aus = meths.get_autocmds { event = 'BufReadPost' } + eq(1, #aus, aus) + + command('augroup TEMP_ABC') + command(' au!') + command(' autocmd BufReadPost *.py echo "Hello"') + command('augroup END') + + -- Should now have two autocmds :'( + aus = meths.get_autocmds { event = 'BufReadPost' } + eq(2, #aus, aus) + + command('augroup! TEMP_ABC') + + eq(false, pcall(meths.get_autocmds, { group = 'TEMP_ABC' })) + eq(2, #meths.get_autocmds { event = 'BufReadPost' }) + end) + + it('api: should clear and not return any autocmds for delete groups by id', function() + command('augroup TEMP_ABCD') + command('autocmd! BufReadPost *.py :echo "Hello"') + command('augroup END') + + local augroup_id = meths.create_augroup("TEMP_ABCD", { clear = false }) + meths.del_augroup_by_id(augroup_id) + + -- For good reason, we kill all the autocmds from del_augroup, + -- so now this works as expected + eq(false, pcall(meths.get_autocmds, { group = 'TEMP_ABCD' })) + eq(0, #meths.get_autocmds { event = 'BufReadPost' }) + end) + + it('api: should clear and not return any autocmds for delete groups by name', function() + command('augroup TEMP_ABCDE') + command('autocmd! BufReadPost *.py :echo "Hello"') + command('augroup END') + + meths.del_augroup_by_name("TEMP_ABCDE") + + -- For good reason, we kill all the autocmds from del_augroup, + -- so now this works as expected + eq(false, pcall(meths.get_autocmds, { group = 'TEMP_ABCDE' })) + eq(0, #meths.get_autocmds { event = 'BufReadPost' }) + end) + end) +end) 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/buffer_updates_spec.lua b/test/functional/api/buffer_updates_spec.lua index c9c9be5406..e9ad756947 100644 --- a/test/functional/api/buffer_updates_spec.lua +++ b/test/functional/api/buffer_updates_spec.lua @@ -7,6 +7,7 @@ local nvim_prog = helpers.nvim_prog local pcall_err = helpers.pcall_err local sleep = helpers.sleep local write_file = helpers.write_file +local iswin = helpers.iswin local origlines = {"original line 1", "original line 2", @@ -823,7 +824,8 @@ describe('API: buffer events:', function() end msg = next_msg() end - assert(false, 'did not match/receive expected nvim_buf_lines_event lines') + -- FIXME: Windows + assert(iswin(), 'did not match/receive expected nvim_buf_lines_event lines') end it('when :terminal lines change', function() diff --git a/test/functional/api/command_spec.lua b/test/functional/api/command_spec.lua index 6f929ad1ca..b80004f67c 100644 --- a/test/functional/api/command_spec.lua +++ b/test/functional/api/command_spec.lua @@ -6,12 +6,18 @@ local command = helpers.command local curbufmeths = helpers.curbufmeths local eq = helpers.eq local meths = helpers.meths +local bufmeths = helpers.bufmeths +local matches = helpers.matches local source = helpers.source local pcall_err = helpers.pcall_err +local exec_lua = helpers.exec_lua +local assert_alive = helpers.assert_alive +local feed = helpers.feed +local funcs = helpers.funcs describe('nvim_get_commands', function() - local cmd_dict = { addr=NIL, bang=false, bar=false, complete=NIL, complete_arg=NIL, count=NIL, definition='echo "Hello World"', name='Hello', nargs='1', range=NIL, register=false, script_id=0, } - local cmd_dict2 = { addr=NIL, bang=false, bar=false, complete=NIL, complete_arg=NIL, count=NIL, definition='pwd', name='Pwd', nargs='?', range=NIL, register=false, script_id=0, } + local cmd_dict = { addr=NIL, bang=false, bar=false, complete=NIL, complete_arg=NIL, count=NIL, definition='echo "Hello World"', name='Hello', nargs='1', range=NIL, register=false, keepscript=false, script_id=0, } + local cmd_dict2 = { addr=NIL, bang=false, bar=false, complete=NIL, complete_arg=NIL, count=NIL, definition='pwd', name='Pwd', nargs='?', range=NIL, register=false, keepscript=false, script_id=0, } before_each(clear) it('gets empty list if no commands were defined', function() @@ -53,11 +59,11 @@ describe('nvim_get_commands', function() end) it('gets various command attributes', function() - local cmd0 = { addr='arguments', bang=false, bar=false, complete='dir', complete_arg=NIL, count='10', definition='pwd <args>', name='TestCmd', nargs='1', range='10', register=false, script_id=0, } - local cmd1 = { addr=NIL, bang=false, bar=false, complete='custom', complete_arg='ListUsers', count=NIL, definition='!finger <args>', name='Finger', nargs='+', range=NIL, register=false, script_id=1, } - local cmd2 = { addr=NIL, bang=true, bar=false, complete=NIL, complete_arg=NIL, count=NIL, definition='call \128\253R2_foo(<q-args>)', name='Cmd2', nargs='*', range=NIL, register=false, script_id=2, } - local cmd3 = { addr=NIL, bang=false, bar=true, complete=NIL, complete_arg=NIL, count=NIL, definition='call \128\253R3_ohyeah()', name='Cmd3', nargs='0', range=NIL, register=false, script_id=3, } - local cmd4 = { addr=NIL, bang=false, bar=false, complete=NIL, complete_arg=NIL, count=NIL, definition='call \128\253R4_just_great()', name='Cmd4', nargs='0', range=NIL, register=true, script_id=4, } + local cmd0 = { addr='arguments', bang=false, bar=false, complete='dir', complete_arg=NIL, count='10', definition='pwd <args>', name='TestCmd', nargs='1', range='10', register=false, keepscript=false, script_id=0, } + local cmd1 = { addr=NIL, bang=false, bar=false, complete='custom', complete_arg='ListUsers', count=NIL, definition='!finger <args>', name='Finger', nargs='+', range=NIL, register=false, keepscript=false, script_id=1, } + local cmd2 = { addr=NIL, bang=true, bar=false, complete=NIL, complete_arg=NIL, count=NIL, definition='call \128\253R2_foo(<q-args>)', name='Cmd2', nargs='*', range=NIL, register=false, keepscript=false, script_id=2, } + local cmd3 = { addr=NIL, bang=false, bar=true, complete=NIL, complete_arg=NIL, count=NIL, definition='call \128\253R3_ohyeah()', name='Cmd3', nargs='0', range=NIL, register=false, keepscript=false, script_id=3, } + local cmd4 = { addr=NIL, bang=false, bar=false, complete=NIL, complete_arg=NIL, count=NIL, definition='call \128\253R4_just_great()', name='Cmd4', nargs='0', range=NIL, register=true, keepscript=false, script_id=4, } source([[ command -complete=custom,ListUsers -nargs=+ Finger !finger <args> ]]) @@ -78,3 +84,186 @@ describe('nvim_get_commands', function() eq({Cmd2=cmd2, Cmd3=cmd3, Cmd4=cmd4, Finger=cmd1, TestCmd=cmd0}, meths.get_commands({builtin=false})) end) end) + +describe('nvim_add_user_command', function() + before_each(clear) + + it('works with strings', function() + meths.add_user_command('SomeCommand', 'let g:command_fired = <args>', {nargs = 1}) + meths.command('SomeCommand 42') + eq(42, meths.eval('g:command_fired')) + end) + + it('works with Lua functions', function() + exec_lua [[ + result = {} + vim.api.nvim_add_user_command('CommandWithLuaCallback', function(opts) + result = opts + end, { + nargs = "*", + bang = true, + count = 2, + }) + ]] + + eq({ + args = [[hello my\ friend how\ are\ you?]], + fargs = {[[hello]], [[my\ friend]], [[how\ are\ you?]]}, + bang = false, + line1 = 1, + line2 = 1, + mods = "", + range = 0, + count = 2, + reg = "", + }, exec_lua [=[ + vim.api.nvim_command([[CommandWithLuaCallback hello my\ friend how\ are\ you?]]) + return result + ]=]) + + eq({ + args = 'h\tey', + fargs = {[[h]], [[ey]]}, + bang = true, + line1 = 10, + line2 = 10, + mods = "botright", + range = 1, + count = 10, + reg = "", + }, exec_lua [=[ + vim.api.nvim_command('botright 10CommandWithLuaCallback! h\tey') + return result + ]=]) + + eq({ + args = "h", + fargs = {"h"}, + bang = false, + line1 = 1, + line2 = 42, + mods = "", + range = 1, + count = 42, + reg = "", + }, exec_lua [[ + vim.api.nvim_command('CommandWithLuaCallback 42 h') + return result + ]]) + + eq({ + args = "", + fargs = {""}, -- fargs works without args + bang = false, + line1 = 1, + line2 = 1, + mods = "", + range = 0, + count = 2, + reg = "", + }, exec_lua [[ + vim.api.nvim_command('CommandWithLuaCallback') + return result + ]]) + + -- f-args doesn't split when command nargs is 1 or "?" + exec_lua [[ + result = {} + vim.api.nvim_add_user_command('CommandWithOneArg', function(opts) + result = opts + end, { + nargs = "?", + bang = true, + count = 2, + }) + ]] + + eq({ + args = "hello I'm one argmuent", + fargs = {"hello I'm one argmuent"}, -- Doesn't split args + bang = false, + line1 = 1, + line2 = 1, + mods = "", + range = 0, + count = 2, + reg = "", + }, exec_lua [[ + vim.api.nvim_command('CommandWithOneArg hello I\'m one argmuent') + return result + ]]) + + end) + + it('can define buffer-local commands', function() + local bufnr = meths.create_buf(false, false) + bufmeths.add_user_command(bufnr, "Hello", "", {}) + matches("Not an editor command: Hello", pcall_err(meths.command, "Hello")) + meths.set_current_buf(bufnr) + meths.command("Hello") + assert_alive() + end) + + it('can use a Lua complete function', function() + exec_lua [[ + vim.api.nvim_add_user_command('Test', '', { + nargs = "*", + complete = function(arg, cmdline, pos) + local options = {"aaa", "bbb", "ccc"} + local t = {} + for _, v in ipairs(options) do + if string.find(v, "^" .. arg) then + table.insert(t, v) + end + end + return t + end, + }) + ]] + + feed(':Test a<Tab>') + eq('Test aaa', funcs.getcmdline()) + 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() + before_each(clear) + + it('can delete global commands', function() + meths.add_user_command('Hello', 'echo "Hi"', {}) + meths.command('Hello') + meths.del_user_command('Hello') + matches("Not an editor command: Hello", pcall_err(meths.command, "Hello")) + end) + + it('can delete buffer-local commands', function() + bufmeths.add_user_command(0, 'Hello', 'echo "Hi"', {}) + meths.command('Hello') + bufmeths.del_user_command(0, 'Hello') + matches("Not an editor command: Hello", pcall_err(meths.command, "Hello")) + end) +end) diff --git a/test/functional/api/extmark_spec.lua b/test/functional/api/extmark_spec.lua index a8f538b951..3f96efd4ef 100644 --- a/test/functional/api/extmark_spec.lua +++ b/test/functional/api/extmark_spec.lua @@ -110,6 +110,22 @@ describe('API/extmarks', function() pcall_err(set_extmark, ns, marks[2], 0, 0, { end_col = 1, end_row = 1 })) end) + it("can end extranges past final newline when strict mode is false", function() + set_extmark(ns, marks[1], 0, 0, { + end_col = 1, + end_row = 1, + strict = false, + }) + end) + + it("can end extranges past final column when strict mode is false", function() + set_extmark(ns, marks[1], 0, 0, { + end_col = 6, + end_row = 0, + strict = false, + }) + end) + it('adds, updates and deletes marks', function() local rv = set_extmark(ns, marks[1], positions[1][1], positions[1][2]) eq(marks[1], rv) @@ -1430,7 +1446,60 @@ describe('API/extmarks', function() end_col = 0, end_line = 1 }) - eq({ {1, 0, 0, { end_col = 0, end_row = 1 }} }, get_extmarks(ns, 0, -1, {details=true})) + eq({ {1, 0, 0, { + end_col = 0, + end_row = 1, + right_gravity = true, + end_right_gravity = false, + }} }, get_extmarks(ns, 0, -1, {details=true})) + end) + + it('can get details', function() + set_extmark(ns, marks[1], 0, 0, { + end_col = 0, + end_row = 1, + right_gravity = false, + end_right_gravity = true, + priority = 0, + hl_eol = true, + hl_mode = "blend", + hl_group = "String", + virt_text = { { "text", "Statement" } }, + virt_text_pos = "right_align", + virt_text_hide = true, + virt_lines = { { { "lines", "Statement" } }}, + virt_lines_above = true, + virt_lines_leftcol = true, + }) + set_extmark(ns, marks[2], 0, 0, { + priority = 0, + virt_text = { { "text", "Statement" } }, + virt_text_win_col = 1, + }) + eq({0, 0, { + end_col = 0, + end_row = 1, + right_gravity = false, + end_right_gravity = true, + priority = 0, + hl_eol = true, + hl_mode = "blend", + hl_group = "String", + virt_text = { { "text", "Statement" } }, + virt_text_pos = "right_align", + virt_text_hide = true, + virt_lines = { { { "lines", "Statement" } }}, + virt_lines_above = true, + virt_lines_leftcol = true, + } }, get_extmark_by_id(ns, marks[1], { details = true })) + eq({0, 0, { + right_gravity = true, + priority = 0, + virt_text = { { "text", "Statement" } }, + virt_text_hide = false, + virt_text_pos = "win_col", + virt_text_win_col = 1, + } }, get_extmark_by_id(ns, marks[2], { details = true })) end) end) diff --git a/test/functional/api/highlight_spec.lua b/test/functional/api/highlight_spec.lua index 21e3094f8e..443689754c 100644 --- a/test/functional/api/highlight_spec.lua +++ b/test/functional/api/highlight_spec.lua @@ -3,6 +3,7 @@ local clear, nvim = helpers.clear, helpers.nvim local Screen = require('test.functional.ui.screen') local eq, eval = helpers.eq, helpers.eval local command = helpers.command +local exec_capture = helpers.exec_capture local meths = helpers.meths local funcs = helpers.funcs local pcall_err = helpers.pcall_err @@ -194,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 = { @@ -208,6 +211,7 @@ describe("API: set highlight", function() reverse = true, undercurl = true, underline = true, + strikethrough = true, } local highlight3_result_cterm = { background = highlight_color.ctermbg, @@ -215,6 +219,7 @@ describe("API: set highlight", function() italic = true, reverse = true, undercurl = true, + strikethrough = true, } local function get_ns() @@ -237,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) @@ -252,4 +263,69 @@ describe("API: set highlight", function() eq(highlight3_result_gui, meths.get_hl_by_name('Test_hl', true)) eq(highlight3_result_cterm, meths.get_hl_by_name('Test_hl', false)) end) + + it ("can set a highlight in the global namespace", function() + meths.set_hl(0, 'Test_hl', highlight2_config) + eq('Test_hl xxx cterm=underline,reverse ctermfg=8 ctermbg=15 gui=underline,reverse', + exec_capture('highlight Test_hl')) + + meths.set_hl(0, 'Test_hl', { background = highlight_color.bg }) + eq('Test_hl xxx guibg=#0032aa', + exec_capture('highlight Test_hl')) + + meths.set_hl(0, 'Test_hl2', highlight3_config) + 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. + meths.set_hl(0, 'Test_hl3', { bg = 'reD', fg = 'bLue'}) + 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 + + meths.set_hl(0, 'Test_hl3', {fg='#FF00FF', blend=50}) + eq('Test_hl3 xxx guifg=#FF00FF blend=50', + exec_capture('highlight Test_hl3')) + + end) end) diff --git a/test/functional/api/keymap_spec.lua b/test/functional/api/keymap_spec.lua index dd8eef7ca0..c0edcde476 100644 --- a/test/functional/api/keymap_spec.lua +++ b/test/functional/api/keymap_spec.lua @@ -5,6 +5,7 @@ local clear = helpers.clear local command = helpers.command local curbufmeths = helpers.curbufmeths local eq, neq = helpers.eq, helpers.neq +local exec_lua = helpers.exec_lua local feed = helpers.feed local funcs = helpers.funcs local meths = helpers.meths @@ -14,6 +15,9 @@ local pcall_err = helpers.pcall_err local shallowcopy = helpers.shallowcopy local sleep = helpers.sleep +local sid_api_client = -9 +local sid_lua = -8 + describe('nvim_get_keymap', function() before_each(clear) @@ -316,6 +320,55 @@ describe('nvim_get_keymap', function() command('nnoremap \\|<Char-0x20><Char-32><Space><Bar> \\|<Char-0x20><Char-32><Space> <Bar>') eq({space_table}, meths.get_keymap('n')) end) + + it('can handle lua keymaps', function() + eq(0, exec_lua [[ + GlobalCount = 0 + vim.api.nvim_set_keymap ('n', 'asdf', '', {callback = function() GlobalCount = GlobalCount + 1 end }) + return GlobalCount + ]]) + + feed('asdf\n') + eq(1, exec_lua[[return GlobalCount]]) + + eq(2, exec_lua[[ + vim.api.nvim_get_keymap('n')[1].callback() + return GlobalCount + ]]) + local mapargs = meths.get_keymap('n') + assert.Truthy(type(mapargs[1].callback) == 'number', 'callback is not luaref number') + mapargs[1].callback = nil + eq({ + lhs='asdf', + script=0, + silent=0, + expr=0, + sid=sid_lua, + buffer=0, + nowait=0, + mode='n', + noremap=0, + lnum=0, + }, mapargs[1]) + end) + + it ('can handle map descriptions', function() + meths.set_keymap('n', 'lhs', 'rhs', {desc="map description"}) + eq({ + lhs='lhs', + rhs='rhs', + script=0, + silent=0, + expr=0, + sid=sid_api_client, + buffer=0, + nowait=0, + mode='n', + noremap=0, + lnum=0, + desc='map description' + }, meths.get_keymap('n')[1]) + end) end) describe('nvim_set_keymap, nvim_del_keymap', function() @@ -350,9 +403,10 @@ describe('nvim_set_keymap, nvim_del_keymap', function() to_return.silent = not opts.silent and 0 or 1 to_return.nowait = not opts.nowait and 0 or 1 to_return.expr = not opts.expr and 0 or 1 - to_return.sid = not opts.sid and 0 or opts.sid + to_return.sid = not opts.sid and sid_api_client or opts.sid to_return.buffer = not opts.buffer and 0 or opts.buffer to_return.lnum = not opts.lnum and 0 or opts.lnum + to_return.desc = opts.desc return to_return end @@ -574,7 +628,7 @@ describe('nvim_set_keymap, nvim_del_keymap', function() it('interprets control sequences in expr-quotes correctly when called ' ..'inside vim', function() command([[call nvim_set_keymap('i', "\<space>", "\<tab>", {})]]) - eq(generate_mapargs('i', '<Space>', '\t', {}), + eq(generate_mapargs('i', '<Space>', '\t', {sid=0}), get_mapargs('i', '<Space>')) feed('i ') eq({'\t'}, curbufmeths.get_lines(0, -1, 0)) @@ -717,6 +771,115 @@ describe('nvim_set_keymap, nvim_del_keymap', function() end) end end + + it('can make lua mappings', function() + eq(0, exec_lua [[ + GlobalCount = 0 + vim.api.nvim_set_keymap ('n', 'asdf', '', {callback = function() GlobalCount = GlobalCount + 1 end }) + return GlobalCount + ]]) + + feed('asdf\n') + + eq(1, exec_lua[[return GlobalCount]]) + + end) + + it (':map command shows lua keymap correctly', function() + exec_lua [[ + vim.api.nvim_set_keymap ('n', 'asdf', '', {callback = function() print('jkl;') end }) + ]] + assert.truthy(string.match(exec_lua[[return vim.api.nvim_exec(':nmap asdf', true)]], + "^\nn asdf <Lua function %d+>")) + end) + + it ('mapcheck() returns lua keymap correctly', function() + exec_lua [[ + vim.api.nvim_set_keymap ('n', 'asdf', '', {callback = function() print('jkl;') end }) + ]] + assert.truthy(string.match(funcs.mapcheck('asdf', 'n'), + "^<Lua function %d+>")) + end) + + it ('maparg() returns lua keymap correctly', function() + exec_lua [[ + vim.api.nvim_set_keymap ('n', 'asdf', '', {callback = function() print('jkl;') end }) + ]] + assert.truthy(string.match(funcs.maparg('asdf', 'n'), + "^<Lua function %d+>")) + local mapargs = funcs.maparg('asdf', 'n', false, true) + assert.Truthy(type(mapargs.callback) == 'number', 'callback is not luaref number') + mapargs.callback = nil + eq(generate_mapargs('n', 'asdf', nil, {sid=sid_lua}), mapargs) + end) + + it('can make lua expr mappings', function() + exec_lua [[ + vim.api.nvim_set_keymap ('n', 'aa', '', {callback = function() return vim.api.nvim_replace_termcodes(':lua SomeValue = 99<cr>', true, false, true) end, expr = true }) + ]] + + feed('aa') + + eq(99, exec_lua[[return SomeValue]]) + end) + + it('does not reset pum in lua mapping', function() + eq(0, exec_lua [[ + VisibleCount = 0 + vim.api.nvim_set_keymap ('i', '<F2>', '', {callback = function() VisibleCount = VisibleCount + vim.fn.pumvisible() end}) + return VisibleCount + ]]) + feed('i<C-X><C-V><F2><F2><esc>') + eq(2, exec_lua[[return VisibleCount]]) + end) + + it('can overwrite lua mappings', function() + eq(0, exec_lua [[ + GlobalCount = 0 + vim.api.nvim_set_keymap ('n', 'asdf', '', {callback = function() GlobalCount = GlobalCount + 1 end }) + return GlobalCount + ]]) + + feed('asdf\n') + + eq(1, exec_lua[[return GlobalCount]]) + + exec_lua [[ + vim.api.nvim_set_keymap ('n', 'asdf', '', {callback = function() GlobalCount = GlobalCount - 1 end }) + ]] + + feed('asdf\n') + + eq(0, exec_lua[[return GlobalCount]]) + end) + + it('can unmap lua mappings', function() + eq(0, exec_lua [[ + GlobalCount = 0 + vim.api.nvim_set_keymap ('n', 'asdf', '', {callback = function() GlobalCount = GlobalCount + 1 end }) + return GlobalCount + ]]) + + feed('asdf\n') + + eq(1, exec_lua[[return GlobalCount]]) + + exec_lua [[ + vim.api.nvim_del_keymap('n', 'asdf' ) + ]] + + feed('asdf\n') + + eq(1, exec_lua[[return GlobalCount]]) + eq('\nNo mapping found', helpers.exec_capture('nmap asdf')) + end) + + it('can set descriptions on keymaps', function() + meths.set_keymap('n', 'lhs', 'rhs', {desc="map description"}) + eq(generate_mapargs('n', 'lhs', 'rhs', {desc="map description"}), get_mapargs('n', 'lhs')) + eq("\nn lhs rhs\n map description", + helpers.exec_capture("nmap lhs")) + end) end) describe('nvim_buf_set_keymap, nvim_buf_del_keymap', function() @@ -814,4 +977,67 @@ describe('nvim_buf_set_keymap, nvim_buf_del_keymap', function() pcall_err(bufmeths.set_keymap, 100, '', 'lsh', 'irhs<Esc>', {}) helpers.assert_alive() end) + + it('can make lua mappings', function() + eq(0, exec_lua [[ + GlobalCount = 0 + vim.api.nvim_buf_set_keymap (0, 'n', 'asdf', '', {callback = function() GlobalCount = GlobalCount + 1 end }) + return GlobalCount + ]]) + + feed('asdf\n') + + eq(1, exec_lua[[return GlobalCount]]) + end) + + it('can make lua expr mappings', function() + exec_lua [[ + vim.api.nvim_buf_set_keymap (0, 'n', 'aa', '', {callback = function() return vim.api.nvim_replace_termcodes(':lua SomeValue = 99<cr>', true, false, true) end, expr = true }) + ]] + + feed('aa') + + eq(99, exec_lua[[return SomeValue ]]) + end) + + it('can overwrite lua mappings', function() + eq(0, exec_lua [[ + GlobalCount = 0 + vim.api.nvim_buf_set_keymap (0, 'n', 'asdf', '', {callback = function() GlobalCount = GlobalCount + 1 end }) + return GlobalCount + ]]) + + feed('asdf\n') + + eq(1, exec_lua[[return GlobalCount]]) + + exec_lua [[ + vim.api.nvim_buf_set_keymap (0, 'n', 'asdf', '', {callback = function() GlobalCount = GlobalCount - 1 end }) + ]] + + feed('asdf\n') + + eq(0, exec_lua[[return GlobalCount]]) + end) + + it('can unmap lua mappings', function() + eq(0, exec_lua [[ + GlobalCount = 0 + vim.api.nvim_buf_set_keymap (0, 'n', 'asdf', '', {callback = function() GlobalCount = GlobalCount + 1 end }) + return GlobalCount + ]]) + + feed('asdf\n') + + eq(1, exec_lua[[return GlobalCount]]) + + exec_lua [[ + vim.api.nvim_buf_del_keymap(0, 'n', 'asdf' ) + ]] + + feed('asdf\n') + + eq(1, exec_lua[[return GlobalCount]]) + eq('\nNo mapping found', helpers.exec_capture('nmap asdf')) + end) end) 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 22201e21a2..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 @@ -333,6 +334,7 @@ describe('API', function() describe('nvim_command_output', function() it('does not induce hit-enter prompt', function() + nvim("ui_attach", 80, 20, {}) -- Induce a hit-enter prompt use nvim_input (non-blocking). nvim('command', 'set cmdheight=1') nvim('input', [[:echo "hi\nhi2"<CR>]]) @@ -535,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)', {}) @@ -872,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() @@ -1093,7 +1133,20 @@ describe('API', function() eq({mode='n', blocking=false}, nvim("get_mode")) end) + it("during press-enter prompt without UI returns blocking=false", function() + eq({mode='n', blocking=false}, nvim("get_mode")) + command("echom 'msg1'") + command("echom 'msg2'") + command("echom 'msg3'") + command("echom 'msg4'") + command("echom 'msg5'") + eq({mode='n', blocking=false}, nvim("get_mode")) + nvim("input", ":messages<CR>") + eq({mode='n', blocking=false}, nvim("get_mode")) + end) + it("during press-enter prompt returns blocking=true", function() + nvim("ui_attach", 80, 20, {}) eq({mode='n', blocking=false}, nvim("get_mode")) command("echom 'msg1'") command("echom 'msg2'") @@ -1117,6 +1170,7 @@ describe('API', function() -- TODO: bug #6247#issuecomment-286403810 it("batched with input", function() + nvim("ui_attach", 80, 20, {}) eq({mode='n', blocking=false}, nvim("get_mode")) command("echom 'msg1'") command("echom 'msg2'") @@ -1367,18 +1421,18 @@ describe('API', function() end) describe('nvim_feedkeys', function() - it('CSI escaping', function() + it('K_SPECIAL escaping', function() local function on_setup() -- notice the special char(…) \xe2\80\xa6 nvim('feedkeys', ':let x1="…"\n', '', true) -- Both nvim_replace_termcodes and nvim_feedkeys escape \x80 local inp = helpers.nvim('replace_termcodes', ':let x2="…"<CR>', true, true, true) - nvim('feedkeys', inp, '', true) -- escape_csi=true + nvim('feedkeys', inp, '', true) -- escape_ks=true - -- nvim_feedkeys with CSI escaping disabled + -- nvim_feedkeys with K_SPECIAL escaping disabled inp = helpers.nvim('replace_termcodes', ':let x3="…"<CR>', true, true, true) - nvim('feedkeys', inp, '', false) -- escape_csi=false + nvim('feedkeys', inp, '', false) -- escape_ks=false helpers.stop() end @@ -2187,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() @@ -2551,6 +2613,34 @@ describe('API', function() 'Should be truncated%<', { maxwidth = 15 })) end) + it('supports ASCII fillchar', function() + eq({ str = 'a~~~b', width = 5 }, + meths.eval_statusline('a%=b', { fillchar = '~', maxwidth = 5 })) + end) + it('supports single-width multibyte fillchar', function() + eq({ str = 'a━━━b', width = 5 }, + meths.eval_statusline('a%=b', { fillchar = '━', maxwidth = 5 })) + end) + it('rejects double-width fillchar', function() + eq('fillchar must be a single-width character', + pcall_err(meths.eval_statusline, '', { fillchar = '哦' })) + end) + it('rejects control character fillchar', function() + eq('fillchar must be a single-width character', + pcall_err(meths.eval_statusline, '', { fillchar = '\a' })) + end) + it('rejects multiple-character fillchar', function() + eq('fillchar must be a single-width character', + pcall_err(meths.eval_statusline, '', { fillchar = 'aa' })) + end) + it('rejects empty string fillchar', function() + eq('fillchar must be a single-width character', + pcall_err(meths.eval_statusline, '', { fillchar = '' })) + end) + it('rejects non-string fillchar', function() + eq('fillchar must be a single-width character', + pcall_err(meths.eval_statusline, '', { fillchar = 1 })) + end) describe('highlight parsing', function() it('works', function() eq({ diff --git a/test/functional/api/window_spec.lua b/test/functional/api/window_spec.lua index 4d2ffa316a..c31ab2060a 100644 --- a/test/functional/api/window_spec.lua +++ b/test/functional/api/window_spec.lua @@ -1,4 +1,5 @@ local helpers = require('test.functional.helpers')(after_each) +local Screen = require('test.functional.ui.screen') local clear, nvim, curbuf, curbuf_contents, window, curwin, eq, neq, ok, feed, insert, eval, tabpage = helpers.clear, helpers.nvim, helpers.curbuf, helpers.curbuf_contents, helpers.window, helpers.curwin, helpers.eq, @@ -73,8 +74,7 @@ describe('API/win', function() eq('typing\n some dumb text', curbuf_contents()) end) - it('does not leak memory when using invalid window ID with invalid pos', - function() + it('does not leak memory when using invalid window ID with invalid pos', function() eq('Invalid window id: 1', pcall_err(meths.win_set_cursor, 1, {"b\na"})) end) @@ -147,6 +147,46 @@ describe('API/win', function() eq({2, 5}, window('get_cursor', win)) end) + it('updates cursorline and statusline ruler in non-current window', function() + local screen = Screen.new(60, 8) + screen:set_default_attr_ids({ + [1] = {bold = true, foreground = Screen.colors.Blue}, -- NonText + [2] = {background = Screen.colors.Grey90}, -- CursorLine + [3] = {bold = true, reverse = true}, -- StatusLine + [4] = {reverse = true}, -- VertSplit, StatusLineNC + }) + screen:attach() + command('set ruler') + command('set cursorline') + insert([[ + aaa + bbb + ccc + ddd]]) + local oldwin = curwin() + command('vsplit') + screen:expect([[ + aaa {4:│}aaa | + bbb {4:│}bbb | + ccc {4:│}ccc | + {2:dd^d }{4:│}{2:ddd }| + {1:~ }{4:│}{1:~ }| + {1:~ }{4:│}{1:~ }| + {3:[No Name] [+] 4,3 All }{4:[No Name] [+] 4,3 All}| + | + ]]) + window('set_cursor', oldwin, {1, 0}) + screen:expect([[ + aaa {4:│}{2:aaa }| + bbb {4:│}bbb | + ccc {4:│}ccc | + {2:dd^d }{4:│}ddd | + {1:~ }{4:│}{1:~ }| + {1:~ }{4:│}{1:~ }| + {3:[No Name] [+] 4,3 All }{4:[No Name] [+] 1,1 All}| + | + ]]) + end) end) describe('{get,set}_height', function() diff --git a/test/functional/autocmd/autocmd_oldtest_spec.lua b/test/functional/autocmd/autocmd_oldtest_spec.lua new file mode 100644 index 0000000000..ad3687d7b0 --- /dev/null +++ b/test/functional/autocmd/autocmd_oldtest_spec.lua @@ -0,0 +1,86 @@ +local helpers = require('test.functional.helpers')(after_each) + +local clear = helpers.clear +local eq = helpers.eq +local meths = helpers.meths +local funcs = helpers.funcs + +local exec = function(str) + meths.exec(str, false) +end + +describe('oldtests', function() + before_each(clear) + + local exec_lines = function(str) + return funcs.split(funcs.execute(str), "\n") + end + + local add_an_autocmd = function() + exec [[ + augroup vimBarTest + au BufReadCmd * echo 'hello' + augroup END + ]] + + eq(3, #exec_lines('au vimBarTest')) + eq(1, #meths.get_autocmds({ group = 'vimBarTest' })) + end + + it('should recognize a bar before the {event}', function() + -- Good spacing + add_an_autocmd() + exec [[ augroup vimBarTest | au! | augroup END ]] + eq(1, #exec_lines('au vimBarTest')) + eq({}, meths.get_autocmds({ group = 'vimBarTest' })) + + -- Sad spacing + add_an_autocmd() + exec [[ augroup vimBarTest| au!| augroup END ]] + eq(1, #exec_lines('au vimBarTest')) + + + -- test that a bar is recognized after the {event} + add_an_autocmd() + exec [[ augroup vimBarTest| au!BufReadCmd| augroup END ]] + eq(1, #exec_lines('au vimBarTest')) + + add_an_autocmd() + exec [[ au! vimBarTest|echo 'hello' ]] + eq(1, #exec_lines('au vimBarTest')) + end) + + it('should fire on unload buf', function() + funcs.writefile({'Test file Xxx1'}, 'Xxx1') + funcs.writefile({'Test file Xxx2'}, 'Xxx2') + + local content = [[ + func UnloadAllBufs() + let i = 1 + while i <= bufnr('$') + if i != bufnr('%') && bufloaded(i) + exe i . 'bunload' + endif + let i += 1 + endwhile + endfunc + au BufUnload * call UnloadAllBufs() + au VimLeave * call writefile(['Test Finished'], 'Xout') + set nohidden + edit Xxx1 + split Xxx2 + q + ]] + + funcs.writefile(funcs.split(content, "\n"), 'Xtest') + + funcs.delete('Xout') + funcs.system(meths.get_vvar('progpath') .. ' -u NORC -i NONE -N -S Xtest') + eq(1, funcs.filereadable('Xout')) + + funcs.delete('Xxx1') + funcs.delete('Xxx2') + funcs.delete('Xtest') + funcs.delete('Xout') + end) +end) diff --git a/test/functional/autocmd/autocmd_spec.lua b/test/functional/autocmd/autocmd_spec.lua index 93d71a9e45..f37f04b32f 100644 --- a/test/functional/autocmd/autocmd_spec.lua +++ b/test/functional/autocmd/autocmd_spec.lua @@ -2,8 +2,10 @@ local helpers = require('test.functional.helpers')(after_each) local Screen = require('test.functional.ui.screen') local assert_visible = helpers.assert_visible +local assert_alive = helpers.assert_alive local dedent = helpers.dedent local eq = helpers.eq +local neq = helpers.neq local eval = helpers.eval local feed = helpers.feed local clear = helpers.clear @@ -13,6 +15,7 @@ local funcs = helpers.funcs local expect = helpers.expect local command = helpers.command local exc_exec = helpers.exc_exec +local exec_lua = helpers.exec_lua local curbufmeths = helpers.curbufmeths local source = helpers.source @@ -333,6 +336,68 @@ describe('autocmd', function() pcall_err(command, "call nvim_set_current_win(g:winid)")) end) + it("`aucmd_win` cannot be changed into a normal window #13699", function() + local screen = Screen.new(50, 10) + screen:attach() + screen:set_default_attr_ids { + [1] = {bold = true, foreground = Screen.colors.Blue1}, + [2] = {reverse = true}, + [3] = {bold = true, reverse = true}, + } + + -- Create specific layout and ensure it's left unchanged. + -- Use nvim_buf_call on a hidden buffer so aucmd_win is used. + exec_lua [[ + vim.cmd "wincmd s | wincmd _" + _G.buf = vim.api.nvim_create_buf(true, true) + vim.api.nvim_buf_call(_G.buf, function() vim.cmd "wincmd J" end) + ]] + screen:expect [[ + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {3:[No Name] }| + | + {2:[No Name] }| + | + ]] + -- This used to crash after making aucmd_win a normal window via the above. + exec_lua [[ + vim.cmd "tabnew | tabclose # | wincmd s | wincmd _" + vim.api.nvim_buf_call(_G.buf, function() vim.cmd "wincmd K" end) + ]] + assert_alive() + screen:expect_unchanged() + + -- Ensure splitting still works from inside the aucmd_win. + exec_lua [[vim.api.nvim_buf_call(_G.buf, function() vim.cmd "split" end)]] + screen:expect [[ + ^ | + {1:~ }| + {3:[No Name] }| + | + {1:~ }| + {2:[Scratch] }| + | + {1:~ }| + {2:[No Name] }| + | + ]] + + -- After all of our messing around, aucmd_win should still be floating. + -- Use :only to ensure _G.buf is hidden again (so the aucmd_win is used). + eq("editor", exec_lua [[ + vim.cmd "only" + vim.api.nvim_buf_call(_G.buf, function() + _G.config = vim.api.nvim_win_get_config(0) + end) + return _G.config.relative + ]]) + end) + it(':doautocmd does not warn "No matching autocommands" #10689', function() local screen = Screen.new(32, 3) screen:attach() @@ -354,4 +419,106 @@ describe('autocmd', function() :doautocmd SessionLoadPost | ]]} end) + + describe('old_tests', function() + it('vimscript: WinNew ++once', function() + source [[ + " Without ++once WinNew triggers twice + let g:did_split = 0 + augroup Testing + au! + au WinNew * let g:did_split += 1 + augroup END + split + split + call assert_equal(2, g:did_split) + call assert_true(exists('#WinNew')) + close + close + + " With ++once WinNew triggers once + let g:did_split = 0 + augroup Testing + au! + au WinNew * ++once let g:did_split += 1 + augroup END + split + split + call assert_equal(1, g:did_split) + call assert_false(exists('#WinNew')) + close + close + + call assert_fails('au WinNew * ++once ++once echo bad', 'E983:') + ]] + + meths.set_var('did_split', 0) + + source [[ + augroup Testing + au! + au WinNew * let g:did_split += 1 + augroup END + + split + split + ]] + + eq(2, meths.get_var('did_split')) + eq(1, funcs.exists('#WinNew')) + + -- Now with once + meths.set_var('did_split', 0) + + source [[ + augroup Testing + au! + au WinNew * ++once let g:did_split += 1 + augroup END + + split + split + ]] + + eq(1, meths.get_var('did_split')) + eq(0, funcs.exists('#WinNew')) + + -- call assert_fails('au WinNew * ++once ++once echo bad', 'E983:') + local ok, msg = pcall(source, [[ + au WinNew * ++once ++once echo bad + ]]) + + eq(false, ok) + eq(true, not not string.find(msg, 'E983:')) + end) + + it('should have autocmds in filetypedetect group', function() + source [[filetype on]] + neq({}, meths.get_autocmds { group = "filetypedetect" }) + end) + + it('should not access freed mem', function() + source [[ + au BufEnter,BufLeave,WinEnter,WinLeave 0 vs xxx + arg 0 + argadd + all + all + au! + bwipe xxx + ]] + end) + + it('should allow comma-separated patterns', function() + source [[ + augroup TestingPatterns + au! + autocmd BufReadCmd *.shada,*.shada.tmp.[a-z] echo 'hello' + autocmd BufReadCmd *.shada,*.shada.tmp.[a-z] echo 'hello' + augroup END + ]] + + eq(4, #meths.get_autocmds { event = "BufReadCmd", group = "TestingPatterns" }) + end) + end) end) diff --git a/test/functional/autocmd/cursormoved_spec.lua b/test/functional/autocmd/cursormoved_spec.lua index 9641d4b096..85d8628d7e 100644 --- a/test/functional/autocmd/cursormoved_spec.lua +++ b/test/functional/autocmd/cursormoved_spec.lua @@ -35,6 +35,7 @@ describe('CursorMoved', function() it("is not triggered by cursor movement prior to first CursorMoved instantiation", function() source([[ let g:cursormoved = 0 + autocmd! CursorMoved autocmd CursorMoved * let g:cursormoved += 1 ]]) eq(0, eval('g:cursormoved')) 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/autocmd/focus_spec.lua b/test/functional/autocmd/focus_spec.lua index 3f9a0ad09b..e3c9e1f9ee 100644 --- a/test/functional/autocmd/focus_spec.lua +++ b/test/functional/autocmd/focus_spec.lua @@ -56,7 +56,7 @@ describe('autoread TUI FocusGained/FocusLost', function() line 3 | line 4 | {5:xtest-foo }| - "xtest-foo" 4L, 28C | + "xtest-foo" 4L, 28B | {3:-- TERMINAL --} | ]]} end) diff --git a/test/functional/autocmd/recording_spec.lua b/test/functional/autocmd/recording_spec.lua index 81152758de..b9aec774f1 100644 --- a/test/functional/autocmd/recording_spec.lua +++ b/test/functional/autocmd/recording_spec.lua @@ -11,7 +11,7 @@ describe('RecordingEnter', function() source_vim [[ let g:recorded = 0 autocmd RecordingEnter * let g:recorded += 1 - execute "normal! qqyyq" + call feedkeys("qqyyq", 'xt') ]] eq(1, eval('g:recorded')) end) @@ -20,7 +20,7 @@ describe('RecordingEnter', function() source_vim [[ let g:recording = '' autocmd RecordingEnter * let g:recording = reg_recording() - execute "normal! qqyyq" + call feedkeys("qqyyq", 'xt') ]] eq('q', eval('g:recording')) end) @@ -32,7 +32,7 @@ describe('RecordingLeave', function() source_vim [[ let g:recorded = 0 autocmd RecordingLeave * let g:recorded += 1 - execute "normal! qqyyq" + call feedkeys("qqyyq", 'xt') ]] eq(1, eval('g:recorded')) end) @@ -43,10 +43,30 @@ describe('RecordingLeave', function() let g:recording = '' autocmd RecordingLeave * let g:recording = reg_recording() autocmd RecordingLeave * let g:recorded = reg_recorded() - execute "normal! qqyyq" + call feedkeys("qqyyq", 'xt') ]] eq('q', eval 'g:recording') eq('', eval 'g:recorded') eq('q', eval 'reg_recorded()') end) + + it('populates v:event', function() + source_vim [[ + let g:regname = '' + let g:regcontents = '' + autocmd RecordingLeave * let g:regname = v:event.regname + autocmd RecordingLeave * let g:regcontents = v:event.regcontents + call feedkeys("qqyyq", 'xt') + ]] + eq('q', eval 'g:regname') + eq('yy', eval 'g:regcontents') + end) + + it('resets v:event', function() + source_vim [[ + autocmd RecordingLeave * let g:event = v:event + call feedkeys("qqyyq", 'xt') + ]] + eq(0, eval 'len(v:event)') + end) end) diff --git a/test/functional/autocmd/show_spec.lua b/test/functional/autocmd/show_spec.lua new file mode 100644 index 0000000000..59757a7d61 --- /dev/null +++ b/test/functional/autocmd/show_spec.lua @@ -0,0 +1,35 @@ +local helpers = require('test.functional.helpers')(after_each) + +local clear = helpers.clear +local command = helpers.command +local dedent = helpers.dedent +local eq = helpers.eq +local funcs = helpers.funcs + +describe(":autocmd", function() + before_each(clear) + + it("should not segfault when you just do autocmd", function() + command ":autocmd" + end) + + it("should filter based on ++once", function() + command "autocmd! BufEnter" + command "autocmd BufEnter * :echo 'Hello'" + command [[augroup TestingOne]] + command [[ autocmd BufEnter * :echo "Line 1"]] + command [[ autocmd BufEnter * :echo "Line 2"]] + command [[augroup END]] + + eq(dedent([[ + + --- Autocommands --- + BufEnter + * :echo 'Hello' + TestingOne BufEnter + * :echo "Line 1" + :echo "Line 2"]]), + funcs.execute('autocmd BufEnter')) + + end) +end) diff --git a/test/functional/autocmd/termxx_spec.lua b/test/functional/autocmd/termxx_spec.lua index 1e8f981437..bc7a6d6c36 100644 --- a/test/functional/autocmd/termxx_spec.lua +++ b/test/functional/autocmd/termxx_spec.lua @@ -32,7 +32,7 @@ describe('autocmd TermClose', function() retry(nil, nil, function() eq(23, eval('g:test_termclose')) end) end) - it('kills job trapping SIGTERM', function() + pending('kills job trapping SIGTERM', function() if iswin() then return end nvim('set_option', 'shell', 'sh') nvim('set_option', 'shellcmdflag', '-c') @@ -52,7 +52,7 @@ describe('autocmd TermClose', function() ok(duration <= 4000) -- Epsilon for slow CI end) - it('kills PTY job trapping SIGHUP and SIGTERM', function() + pending('kills PTY job trapping SIGHUP and SIGTERM', function() if iswin() then return end nvim('set_option', 'shell', 'sh') nvim('set_option', 'shellcmdflag', '-c') diff --git a/test/functional/core/channels_spec.lua b/test/functional/core/channels_spec.lua index 93dec9fb35..c28300f0f4 100644 --- a/test/functional/core/channels_spec.lua +++ b/test/functional/core/channels_spec.lua @@ -100,6 +100,38 @@ describe('channels', function() eq({"notification", "exit", {3,0}}, next_msg()) end) + it('can use stdio channel and on_print callback', function() + source([[ + let g:job_opts = { + \ 'on_stdout': function('OnEvent'), + \ 'on_stderr': function('OnEvent'), + \ 'on_exit': function('OnEvent'), + \ } + ]]) + meths.set_var("nvim_prog", nvim_prog) + meths.set_var("code", [[ + function! OnStdin(id, data, event) dict + echo string([a:id, a:data, a:event]) + if a:data == [''] + quit + endif + endfunction + function! OnPrint(text) dict + call chansend(g:x, ['OnPrint:' .. a:text]) + endfunction + let g:x = stdioopen({'on_stdin': funcref('OnStdin'), 'on_print':'OnPrint'}) + call chansend(x, "hello") + ]]) + command("let g:id = jobstart([ g:nvim_prog, '-u', 'NONE', '-i', 'NONE', '--cmd', 'set noswapfile', '--headless', '--cmd', g:code], g:job_opts)") + local id = eval("g:id") + ok(id > 0) + + eq({ "notification", "stdout", {id, { "hello" } } }, next_msg()) + + command("call chansend(id, 'howdy')") + eq({"notification", "stdout", {id, {"OnPrint:[1, ['howdy'], 'stdin']"}}}, next_msg()) + end) + local function expect_twoline(id, stream, line1, line2, nobr) local msg = next_msg() local joined = nobr and {line1..line2} or {line1, line2} diff --git a/test/functional/core/job_spec.lua b/test/functional/core/job_spec.lua index 5e127bce26..a0df3b7767 100644 --- a/test/functional/core/job_spec.lua +++ b/test/functional/core/job_spec.lua @@ -78,6 +78,7 @@ describe('jobs', function() end) it('append environment with pty #env', function() + if helpers.pending_win32(pending) then return end nvim('command', "let $VAR = 'abc'") nvim('command', "let $TOTO = 'goodbye world'") nvim('command', "let g:job_opts.pty = v:true") @@ -295,12 +296,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/core/startup_spec.lua b/test/functional/core/startup_spec.lua index 4220d68ee1..3da7f6ffde 100644 --- a/test/functional/core/startup_spec.lua +++ b/test/functional/core/startup_spec.lua @@ -53,6 +53,7 @@ describe('startup', function() ]]) end) it('in a TTY: has("ttyin")==1 has("ttyout")==1', function() + if helpers.pending_win32(pending) then return end local screen = Screen.new(25, 4) screen:attach() if iswin() then @@ -104,6 +105,7 @@ describe('startup', function() end) end) it('input from pipe (implicit) #7679', function() + if helpers.pending_win32(pending) then return end local screen = Screen.new(25, 4) screen:attach() if iswin() then @@ -224,6 +226,23 @@ describe('startup', function() end end) + it('-e sets ex mode', function() + local screen = Screen.new(25, 3) + clear('-e') + screen:attach() + -- Verify we set the proper mode both before and after :vi. + feed("put =mode(1)<CR>vi<CR>:put =mode(1)<CR>") + screen:expect([[ + cv | + ^n | + :put =mode(1) | + ]]) + + eq('cv\n', + funcs.system({nvim_prog, '-n', '-es' }, + { 'put =mode(1)', 'print', '' })) + end) + it('fails on --embed with -es/-Es', function() matches('nvim[.exe]*: %-%-embed conflicts with %-es/%-Es', funcs.system({nvim_prog, '--embed', '-es' })) @@ -242,6 +261,7 @@ describe('startup', function() end) it('ENTER dismisses early message #7967', function() + if helpers.pending_win32(pending) then return end local screen screen = Screen.new(60, 6) screen:attach() @@ -474,6 +494,7 @@ describe('sysinit', function() end) it('fixed hang issue with -D (#12647)', function() + if helpers.pending_win32(pending) then return end local screen screen = Screen.new(60, 6) screen:attach() @@ -651,11 +672,7 @@ describe('runtime:', function() mkdir_p(ftdetect_folder) write_file(ftdetect_file , [[vim.g.lua_ftdetect = 1]]) - -- TODO(shadmansaleh): Figure out why this test fails without - -- setting VIMRUNTIME - clear{ args_rm={'-u'}, env={XDG_CONFIG_HOME=xconfig, - XDG_DATA_HOME=xdata, - VIMRUNTIME='runtime/'}} + clear{ args_rm={'-u'}, env=xenv } eq(1, eval('g:lua_ftdetect')) rmdir(ftdetect_folder) diff --git a/test/functional/editor/K_spec.lua b/test/functional/editor/K_spec.lua index 40f36491e4..8ad81ac3d6 100644 --- a/test/functional/editor/K_spec.lua +++ b/test/functional/editor/K_spec.lua @@ -33,6 +33,29 @@ describe('K', function() feed('i'..test_file..'<ESC>K') retry(nil, nil, function() eq(1, eval('filereadable("'..test_file..'")')) end) eq({'fnord'}, eval("readfile('"..test_file.."')")) + -- Confirm that Neovim is still in terminal mode after K is pressed (#16692). + helpers.sleep(500) + eq('t', eval('mode()')) + feed('<space>') -- Any key, not just <space>, can be used here to escape. + eq('n', eval('mode()')) + end) + + it("<esc> kills the buffer for a running 'keywordprg' command", function() + helpers.source('set keywordprg=less') + eval('writefile(["hello", "world"], "' .. test_file .. '")') + feed('i' .. test_file .. '<esc>K') + eq('t', eval('mode()')) + -- Confirm that an arbitrary keypress doesn't escape (i.e., the process is + -- still running). If the process were no longer running, an arbitrary + -- keypress would escape. + helpers.sleep(500) + feed('<space>') + eq('t', eval('mode()')) + -- Confirm that <esc> kills the buffer for the running command. + local bufnr = eval('bufnr()') + feed('<esc>') + eq('n', eval('mode()')) + helpers.neq(bufnr, eval('bufnr()')) end) end) diff --git a/test/functional/editor/meta_key_spec.lua b/test/functional/editor/meta_key_spec.lua index 2280f5bb24..8efcc81616 100644 --- a/test/functional/editor/meta_key_spec.lua +++ b/test/functional/editor/meta_key_spec.lua @@ -27,6 +27,14 @@ describe('meta-keys #8226 #13042', function() command('nnoremap <A-j> Aalt-j<Esc>') feed('<A-j><M-l>') expect('llo<ESC>;<ESC>;alt-jmeta-l') + -- Unmapped ALT-chord with characters containing K_SPECIAL bytes + command('nnoremap … A…<Esc>') + feed('<A-…><M-…>') + expect('llo<ESC>;<ESC>;alt-jmeta-l<ESC>…<ESC>…') + command("execute 'nnoremap' nr2char(0x40000000) 'AMAX<Esc>'") + command("call nvim_input('<A-'.nr2char(0x40000000).'>')") + command("call nvim_input('<M-'.nr2char(0x40000000).'>')") + expect('llo<ESC>;<ESC>;alt-jmeta-l<ESC>…<ESC>…<ESC>MAX<ESC>MAX') end) it('ALT/META, visual-mode', function() @@ -36,13 +44,20 @@ describe('meta-keys #8226 #13042', function() expect('peach') -- Unmapped ALT-chord resolves isolated (non-ALT) ESC mapping. #13086 #15869 command('vnoremap <ESC> A<lt>ESC>') - feed('viw<A-;><ESC>viw<M-;><ESC>') + feed('viw<A-;><Esc>viw<M-;><Esc>') expect('peach<ESC>;<ESC>;') -- Mapped ALT-chord behaves as mapped. command('vnoremap <M-l> Ameta-l<Esc>') command('vnoremap <A-j> Aalt-j<Esc>') feed('viw<A-j>viw<M-l>') expect('peach<ESC>;<ESC>;alt-jmeta-l') + -- Unmapped ALT-chord with characters containing K_SPECIAL bytes + feed('viw<A-…><Esc>viw<M-…><Esc>') + expect('peach<ESC>;<ESC>;alt-jmeta-l<ESC>…<ESC>…') + command("execute 'inoremap' nr2char(0x40000000) 'MAX'") + command("call nvim_input('viw<A-'.nr2char(0x40000000).'><Esc>')") + command("call nvim_input('viw<M-'.nr2char(0x40000000).'><Esc>')") + expect('peach<ESC>;<ESC>;alt-jmeta-l<ESC>…<ESC>…<ESC>MAX<ESC>MAX') end) it('ALT/META insert-mode', function() @@ -89,4 +104,20 @@ describe('meta-keys #8226 #13042', function() eq({ 0, 2, 1, 0, }, funcs.getpos('.')) eq('nt', eval('mode(1)')) end) + + it('ALT/META when recording a macro #13235', function() + feed('ifoo<CR>bar<CR>baz<Esc>gg0') + -- <M-"> is reinterpreted as <Esc>" + feed('qrviw"ayC// This is some text: <M-">apq') + expect([[ + // This is some text: foo + bar + baz]]) + -- Should not insert an extra double quote when replaying + feed('j0@rj0@@') + expect([[ + // This is some text: foo + // This is some text: bar + // This is some text: baz]]) + end) end) diff --git a/test/functional/editor/mode_insert_spec.lua b/test/functional/editor/mode_insert_spec.lua index f03508035d..528e228121 100644 --- a/test/functional/editor/mode_insert_spec.lua +++ b/test/functional/editor/mode_insert_spec.lua @@ -75,4 +75,57 @@ describe('insert-mode', function() expect('hello oooworld') end) end) + + describe('Ctrl-V', function() + it('supports entering the decimal value of a character', function() + feed('i<C-V>076<C-V>167') + expect('L§') + end) + + it('supports entering the octal value of a character with "o"', function() + feed('i<C-V>o114<C-V>o247<Esc>') + expect('L§') + end) + + it('supports entering the octal value of a character with "O"', function() + feed('i<C-V>O114<C-V>O247<Esc>') + expect('L§') + end) + + it('supports entering the hexadecimal value of a character with "x"', function() + feed('i<C-V>x4c<C-V>xA7<Esc>') + expect('L§') + end) + + it('supports entering the hexadecimal value of a character with "X"', function() + feed('i<C-V>X4c<C-V>XA7<Esc>') + expect('L§') + end) + + it('supports entering the hexadecimal value of a character with "u"', function() + feed('i<C-V>u25ba<C-V>u25C7<Esc>') + expect('►◇') + end) + + it('supports entering the hexadecimal value of a character with "U"', function() + feed('i<C-V>U0001f600<C-V>U0001F601<Esc>') + expect('😀😁') + end) + + it('entering character by value is interrupted by invalid character', function() + feed('i<C-V>76c<C-V>76<C-F2><C-V>u3c0j<C-V>u3c0<M-F3><C-V>U1f600j<C-V>U1f600<D-F4><Esc>') + expect('LcL<C-F2>πjπ<M-F3>😀j😀<D-F4>') + end) + + it('shows o, O, u, U, x, X, and digits with modifiers', function() + feed('i<C-V><M-o><C-V><D-o><C-V><M-O><C-V><D-O><Esc>') + expect('<M-o><D-o><M-O><D-O>') + feed('cc<C-V><M-u><C-V><D-u><C-V><M-U><C-V><D-U><Esc>') + expect('<M-u><D-u><M-U><D-U>') + feed('cc<C-V><M-x><C-V><D-x><C-V><M-X><C-V><D-X><Esc>') + expect('<M-x><D-x><M-X><D-X>') + feed('cc<C-V><M-1><C-V><D-2><C-V><M-7><C-V><D-8><Esc>') + expect('<M-1><D-2><M-7><D-8>') + end) + end) end) diff --git a/test/functional/editor/put_spec.lua b/test/functional/editor/put_spec.lua index 26967ecbba..c367f8fdd0 100644 --- a/test/functional/editor/put_spec.lua +++ b/test/functional/editor/put_spec.lua @@ -64,7 +64,7 @@ describe('put command', function() -- one place to the right (unless we were at the end of the -- line when we pasted). if not (exception_table.undo_position and after_undo) then - eq(funcs.getcurpos(), init_cursorpos) + eq(init_cursorpos, funcs.getcurpos()) end end @@ -86,7 +86,7 @@ describe('put command', function() -- If we paste the ". register with a count we can't avoid -- changing this register, hence avoid this check. if not test.exception_table.dot_reg_changed then - eq(funcs.getreg('.'), orig_dotstr) + eq(orig_dotstr, funcs.getreg('.')) end -- Doing something, undoing it, and then redoing it should @@ -507,9 +507,11 @@ describe('put command', function() return function(exception_table, after_redo) test_expect(exception_table, after_redo) if selection_string then - eq(getreg('"'), selection_string) + if not conversion_table.put_backwards then + eq(selection_string, getreg('"')) + end else - eq(getreg('"'), 'test_string"') + eq('test_string"', getreg('"')) end end 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(getreg('"'), 'Line of words 1\n') + 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(getreg('"'), 'Lin\nLin') + if not conversion_table.put_backwards then + eq('Lin\nLin', getreg('"')) + end end end @@ -800,9 +806,9 @@ describe('put command', function() feed('u') -- Have to use feed('u') here to set curswant, because -- ex_undo() doesn't do that. - eq(funcs.getcurpos(), {0, 1, 1, 0, 1}) + eq({0, 1, 1, 0, 1}, funcs.getcurpos()) feed('<C-r>') - eq(funcs.getcurpos(), {0, 1, 1, 0, 1}) + eq({0, 1, 1, 0, 1}, funcs.getcurpos()) end end diff --git a/test/functional/ex_cmds/append_spec.lua b/test/functional/ex_cmds/append_spec.lua index 0a4d701794..fadb5c9b42 100644 --- a/test/functional/ex_cmds/append_spec.lua +++ b/test/functional/ex_cmds/append_spec.lua @@ -1,23 +1,26 @@ local helpers = require('test.functional.helpers')(after_each) local eq = helpers.eq +local dedent = helpers.dedent +local exec = helpers.exec local feed = helpers.feed local clear = helpers.clear local funcs = helpers.funcs local command = helpers.command local curbufmeths = helpers.curbufmeths - -before_each(function() - clear() - curbufmeths.set_lines(0, 1, true, { 'foo', 'bar', 'baz' }) -end) - -local buffer_contents = function() - return curbufmeths.get_lines(0, -1, false) -end +local Screen = require('test.functional.ui.screen') local cmdtest = function(cmd, prep, ret1) describe(':' .. cmd, function() + before_each(function() + clear() + curbufmeths.set_lines(0, 1, true, { 'foo', 'bar', 'baz' }) + end) + + local buffer_contents = function() + return curbufmeths.get_lines(0, -1, false) + end + it(cmd .. 's' .. prep .. ' the current line by default', function() command(cmd .. '\nabc\ndef\n') eq(ret1, buffer_contents()) @@ -52,3 +55,52 @@ end cmdtest('insert', ' before', { 'abc', 'def', 'foo', 'bar', 'baz' }) cmdtest('append', ' after', { 'foo', 'abc', 'def', 'bar', 'baz' }) cmdtest('change', '', { 'abc', 'def', 'bar', 'baz' }) + +describe('the first line is redrawn correctly after inserting text in an empty buffer', function() + local screen + before_each(function() + clear() + screen = Screen.new(20, 8) + screen:set_default_attr_ids({ + [1] = {bold = true, foreground = Screen.colors.Blue}, + [2] = {bold = true, reverse = true}, + }) + screen:attach() + end) + + it('using :append', function() + exec(dedent([[ + append + aaaaa + bbbbb + .]])) + screen:expect([[ + aaaaa | + ^bbbbb | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]) + end) + + it('using :insert', function() + exec(dedent([[ + insert + aaaaa + bbbbb + .]])) + screen:expect([[ + aaaaa | + ^bbbbb | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]) + 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/ex_cmds/map_spec.lua b/test/functional/ex_cmds/map_spec.lua index 84d5bc2335..eae36b9ae9 100644 --- a/test/functional/ex_cmds/map_spec.lua +++ b/test/functional/ex_cmds/map_spec.lua @@ -1,11 +1,15 @@ local helpers = require("test.functional.helpers")(after_each) +local Screen = require('test.functional.ui.screen') local eq = helpers.eq +local exec = helpers.exec local feed = helpers.feed local meths = helpers.meths local clear = helpers.clear local command = helpers.command local expect = helpers.expect +local insert = helpers.insert +local eval = helpers.eval describe(':*map', function() before_each(clear) @@ -25,4 +29,221 @@ describe(':*map', function() feed('i-<M-">-') expect('-foo-') end) + + it('shows <nop> as mapping rhs', function() + command('nmap asdf <Nop>') + eq([[ + +n asdf <Nop>]], + helpers.exec_capture('nmap asdf')) + end) + + it('mappings with description can be filtered', 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('<Plug> mappings ignore nore', function() + command('let x = 0') + eq(0, meths.eval('x')) + command [[ + nnoremap <Plug>(Increase_x) <cmd>let x+=1<cr> + nmap increase_x_remap <Plug>(Increase_x) + nnoremap increase_x_noremap <Plug>(Increase_x) + ]] + feed('increase_x_remap') + eq(1, meths.eval('x')) + feed('increase_x_noremap') + eq(2, meths.eval('x')) + end) + + it("Doesn't auto ignore nore for keys before or after <Plug> mapping", function() + command('let x = 0') + eq(0, meths.eval('x')) + command [[ + nnoremap x <nop> + nnoremap <Plug>(Increase_x) <cmd>let x+=1<cr> + nmap increase_x_remap x<Plug>(Increase_x)x + nnoremap increase_x_noremap x<Plug>(Increase_x)x + ]] + insert("Some text") + eq('Some text', eval("getline('.')")) + + feed('increase_x_remap') + eq(1, meths.eval('x')) + eq('Some text', eval("getline('.')")) + feed('increase_x_noremap') + eq(2, meths.eval('x')) + eq('Some te', eval("getline('.')")) + end) +end) + +describe(':*map cursor and redrawing', function() + local screen + before_each(function() + clear() + screen = Screen.new(20, 5) + screen:attach() + end) + + it('cursor is restored after :map <expr> which calls input()', function() + command('map <expr> x input("> ")') + screen:expect([[ + ^ | + ~ | + ~ | + ~ | + | + ]]) + feed('x') + screen:expect([[ + | + ~ | + ~ | + ~ | + > ^ | + ]]) + feed('\n') + screen:expect([[ + ^ | + ~ | + ~ | + ~ | + > | + ]]) + end) + + it('cursor is restored after :imap <expr> which calls input()', function() + command('imap <expr> x input("> ")') + feed('i') + screen:expect([[ + ^ | + ~ | + ~ | + ~ | + -- INSERT -- | + ]]) + feed('x') + screen:expect([[ + | + ~ | + ~ | + ~ | + > ^ | + ]]) + feed('\n') + screen:expect([[ + ^ | + ~ | + ~ | + ~ | + > | + ]]) + end) + + it('cursor is restored after :map <expr> which redraws statusline vim-patch:8.1.2336', function() + exec([[ + call setline(1, ['one', 'two', 'three']) + 2 + set ls=2 + hi! link StatusLine ErrorMsg + noremap <expr> <C-B> Func() + func Func() + let g:on = !get(g:, 'on', 0) + redraws + return '' + endfunc + func Status() + return get(g:, 'on', 0) ? '[on]' : '' + endfunc + set stl=%{Status()} + ]]) + feed('<C-B>') + screen:expect([[ + one | + ^two | + three | + [on] | + | + ]]) + end) + + it('error in :nmap <expr> does not mess up display vim-patch:4.2.4338', function() + screen:try_resize(40, 5) + command('nmap <expr> <F2> execute("throw 42")') + feed('<F2>') + screen:expect([[ + | + | + Error detected while processing : | + E605: Exception not caught: 42 | + Press ENTER or type command to continue^ | + ]]) + feed('<CR>') + screen:expect([[ + ^ | + ~ | + ~ | + ~ | + | + ]]) + end) + + it('error in :cmap <expr> handled correctly vim-patch:4.2.4338', function() + screen:try_resize(40, 5) + command('cmap <expr> <F2> execute("throw 42")') + feed(':echo "foo') + screen:expect([[ + | + ~ | + ~ | + ~ | + :echo "foo^ | + ]]) + feed('<F2>') + screen:expect([[ + | + :echo "foo | + Error detected while processing : | + E605: Exception not caught: 42 | + :echo "foo^ | + ]]) + feed('"') + screen:expect([[ + | + :echo "foo | + Error detected while processing : | + E605: Exception not caught: 42 | + :echo "foo"^ | + ]]) + feed('\n') + screen:expect([[ + :echo "foo | + Error detected while processing : | + E605: Exception not caught: 42 | + foo | + Press ENTER or type command to continue^ | + ]]) + end) + + it('listing mappings clears command line vim-patch:8.2.4401', function() + screen:try_resize(40, 5) + command('nmap a b') + feed(': nmap a<CR>') + screen:expect([[ + ^ | + ~ | + ~ | + ~ | + n a b | + ]]) + end) end) diff --git a/test/functional/ex_cmds/mksession_spec.lua b/test/functional/ex_cmds/mksession_spec.lua index 09eaa36686..2702fb196f 100644 --- a/test/functional/ex_cmds/mksession_spec.lua +++ b/test/functional/ex_cmds/mksession_spec.lua @@ -5,6 +5,7 @@ local clear = helpers.clear local command = helpers.command local get_pathsep = helpers.get_pathsep local eq = helpers.eq +local neq = helpers.neq local funcs = helpers.funcs local matches = helpers.matches local pesc = helpers.pesc @@ -13,6 +14,7 @@ local rmdir = helpers.rmdir local file_prefix = 'Xtest-functional-ex_cmds-mksession_spec' describe(':mksession', function() + if helpers.pending_win32(pending) then return end local session_file = file_prefix .. '.vim' local tab_dir = file_prefix .. '.d' @@ -30,7 +32,8 @@ describe(':mksession', function() -- If the same :terminal is displayed in multiple windows, :mksession -- should restore it as such. - -- Create two windows showing the same :terminal buffer. + -- Create three windows: first two from top show same terminal, third - + -- another one (created earlier). command('terminal') command('split') command('terminal') @@ -43,8 +46,8 @@ describe(':mksession', function() -- Restore session. command('source '..session_file) - eq({2,2,4}, - {funcs.winbufnr(1), funcs.winbufnr(2), funcs.winbufnr(3)}) + eq(funcs.winbufnr(1), funcs.winbufnr(2)) + neq(funcs.winbufnr(1), funcs.winbufnr(3)) end) it('restores tab-local working directories', function() @@ -91,12 +94,7 @@ describe(':mksession', function() command('tabnext 1') eq(cwd_dir .. get_pathsep() .. tmpfile_base .. '1', funcs.expand('%:p')) command('tabnext 2') - -- :mksession stores paths using unix slashes, but Nvim doesn't adjust these - -- for absolute paths in all cases yet. Absolute paths are used in the - -- session file after :tcd, so we need to expect unix slashes here for now - -- eq(cwd_dir .. get_pathsep() .. tmpfile_base .. '2', funcs.expand('%:p')) - eq(cwd_dir:gsub([[\]], '/') .. '/' .. tmpfile_base .. '2', - funcs.expand('%:p')) + eq(cwd_dir .. get_pathsep() .. tmpfile_base .. '2', funcs.expand('%:p')) end) it('restores CWD for :terminal buffers #11288', function() diff --git a/test/functional/ex_cmds/script_spec.lua b/test/functional/ex_cmds/script_spec.lua index 0a772c559b..f249b9ba7c 100644 --- a/test/functional/ex_cmds/script_spec.lua +++ b/test/functional/ex_cmds/script_spec.lua @@ -62,10 +62,10 @@ describe('script_get-based command', function() -- Provider-based scripts test_garbage_exec('ruby', not missing_provider('ruby')) - test_garbage_exec('python', not missing_provider('python')) test_garbage_exec('python3', not missing_provider('python3')) -- Missing scripts + test_garbage_exec('python', false) test_garbage_exec('tcl', false) test_garbage_exec('mzscheme', false) test_garbage_exec('perl', false) diff --git a/test/functional/ex_cmds/verbose_spec.lua b/test/functional/ex_cmds/verbose_spec.lua new file mode 100644 index 0000000000..326104dc3c --- /dev/null +++ b/test/functional/ex_cmds/verbose_spec.lua @@ -0,0 +1,168 @@ +local helpers = require('test.functional.helpers')(after_each) + +local clear = helpers.clear +local eq = helpers.eq +local exec = helpers.exec +local exec_capture = helpers.exec_capture +local write_file = helpers.write_file +local call_viml_function = helpers.meths.call_function + +describe('lua :verbose', function() + local script_location, script_file + -- All test cases below use the same nvim instance. + setup(function() + clear{args={'-V1'}} + script_file = 'test_verbose.lua' + local current_dir = call_viml_function('getcwd', {}) + current_dir = call_viml_function('fnamemodify', {current_dir, ':~'}) + script_location = table.concat{current_dir, helpers.get_pathsep(), script_file} + + write_file(script_file, [[ +vim.api.nvim_set_option('hlsearch', false) +vim.bo.expandtab = true +vim.opt.number = true +vim.api.nvim_set_keymap('n', '<leader>key1', ':echo "test"<cr>', {noremap = true}) +vim.keymap.set('n', '<leader>key2', ':echo "test"<cr>') + +vim.api.nvim_exec("augroup test_group\ + autocmd!\ + autocmd FileType c setl cindent\ + augroup END\ + ", false) + +vim.api.nvim_command("command Bdelete :bd") +vim.api.nvim_add_user_command("TestCommand", ":echo 'Hello'", {}) + +vim.api.nvim_exec ("\ +function Close_Window() abort\ + wincmd -\ +endfunction\ +", false) + +local ret = vim.api.nvim_exec ("\ +function! s:return80()\ + return 80\ +endfunction\ +let &tw = s:return80()\ +", true) +]]) + exec(':source '..script_file) + end) + + teardown(function() + os.remove(script_file) + end) + + it('"Last set" for option set by Lua', function() + local result = exec_capture(':verbose set hlsearch?') + eq(string.format([[ +nohlsearch + Last set from %s line 1]], + script_location), result) + end) + + it('"Last set" for option set by vim.o', function() + local result = exec_capture(':verbose set expandtab?') + eq(string.format([[ + expandtab + Last set from %s line 2]], + script_location), result) + end) + + it('"Last set" for option set by vim.opt', function() + local result = exec_capture(':verbose set number?') + eq(string.format([[ + number + Last set from %s line 3]], + script_location), result) + end) + + it('"Last set" for keymap set by Lua', function() + local result = exec_capture(':verbose map <leader>key1') + eq(string.format([[ + +n \key1 * :echo "test"<CR> + Last set from %s line 4]], + script_location), result) + end) + + it('"Last set" for keymap set by vim.keymap', function() + local result = exec_capture(':verbose map <leader>key2') + eq(string.format([[ + +n \key2 * :echo "test"<CR> + Last set from %s line 5]], + script_location), result) + end) + + it('"Last set" for autocmd by vim.api.nvim_exec', function() + local result = exec_capture(':verbose autocmd test_group Filetype c') + eq(string.format([[ +--- Autocommands --- +test_group FileType + c setl cindent + Last set from %s line 7]], + script_location), result) + end) + + it('"Last set" for command defined by nvim_command', function() + local result = exec_capture(':verbose command Bdelete') + eq(string.format([[ + Name Args Address Complete Definition + Bdelete 0 :bd + Last set from %s line 13]], + script_location), result) + end) + + it('"Last set" for command defined by nvim_add_user_command', function() + local result = exec_capture(':verbose command TestCommand') + eq(string.format([[ + Name Args Address Complete Definition + TestCommand 0 :echo 'Hello' + Last set from %s line 14]], + script_location), result) + end) + + it('"Last set for function', function() + local result = exec_capture(':verbose function Close_Window') + eq(string.format([[ + function Close_Window() abort + Last set from %s line 16 +1 wincmd - + endfunction]], + script_location), result) + end) + + it('"Last set" works with anonymous sid', function() + local result = exec_capture(':verbose set tw?') + eq(string.format([[ + textwidth=80 + Last set from %s line 22]], + script_location), result) + end) +end) + +describe('lua verbose:', function() + local script_file + + setup(function() + clear() + script_file = 'test_luafile.lua' + write_file(script_file, [[ + vim.api.nvim_set_option('hlsearch', false) + ]]) + exec(':source '..script_file) + end) + + teardown(function() + os.remove(script_file) + end) + + it('is disabled when verbose = 0', function() + local result = exec_capture(':verbose set hlsearch?') + eq([[ +nohlsearch + Last set from Lua]], result) + end) +end) + diff --git a/test/functional/fixtures/autoload/provider/python.vim b/test/functional/fixtures/autoload/provider/python3.vim index d68360ac30..8ed4330a35 100644 --- a/test/functional/fixtures/autoload/provider/python.vim +++ b/test/functional/fixtures/autoload/provider/python3.vim @@ -1,6 +1,6 @@ " Dummy test provider, missing this required variable: " let g:loaded_brokenenabled_provider = 0 -function! provider#python#Call(method, args) +function! provider#python3#Call(method, args) return 42 endfunction 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/fixtures/pack/foo/start/bar/lua/baz-quux.lua b/test/functional/fixtures/pack/foo/start/bar/lua/baz-quux.lua new file mode 100644 index 0000000000..c1c33d787e --- /dev/null +++ b/test/functional/fixtures/pack/foo/start/bar/lua/baz-quux.lua @@ -0,0 +1 @@ +return {doit=function() return 9004 end} diff --git a/test/functional/helpers.lua b/test/functional/helpers.lua index f152a487af..1845786c4b 100644 --- a/test/functional/helpers.lua +++ b/test/functional/helpers.lua @@ -451,6 +451,7 @@ function module.new_argv(...) 'GCOV_ERROR_FILE', 'XDG_DATA_DIRS', 'TMPDIR', + 'VIMRUNTIME', }) do if not env_tbl[k] then env_tbl[k] = os.getenv(k) diff --git a/test/functional/legacy/003_cindent_spec.lua b/test/functional/legacy/003_cindent_spec.lua deleted file mode 100644 index 061904c42f..0000000000 --- a/test/functional/legacy/003_cindent_spec.lua +++ /dev/null @@ -1,4774 +0,0 @@ --- Test for 'cindent'. --- For new tests, consider putting them in test_cindent.vim. --- --- There are 50+ test command blocks (the stuff between STARTTEST and ENDTEST) --- in the original test. These have been converted to "it" test cases here. - -local helpers = require('test.functional.helpers')(after_each) -local feed, insert = helpers.feed, helpers.insert -local clear, feed_command, expect = helpers.clear, helpers.feed_command, helpers.expect - --- Inserts text as usual, and additionally positions the cursor on line 1 and --- sets 'cindent' and tab settings. (In the original "test3.in" the modeline at --- the top of the file takes care of this.) -local function insert_(content) - insert(content) - feed_command('1', 'set cin ts=4 sw=4') -end - --- luacheck: ignore 621 (Indentation) --- luacheck: ignore 613 (Trailing whitespace in a string) -describe('cindent', function() - before_each(clear) - - it('1 is working', function() - insert_([=[ - - /* start of AUTO matically checked vim: set ts=4 : */ - { - if (test) - cmd1; - cmd2; - } - - { - if (test) - cmd1; - else - cmd2; - } - - { - if (test) - { - cmd1; - cmd2; - } - } - - { - if (test) - { - cmd1; - else - } - } - - { - while (this) - if (test) - cmd1; - cmd2; - } - - { - while (this) - if (test) - cmd1; - else - cmd2; - } - - { - if (test) - { - cmd; - } - - if (test) - cmd; - } - - { - if (test) { - cmd; - } - - if (test) cmd; - } - - { - cmd1; - for (blah) - while (this) - if (test) - cmd2; - cmd3; - } - - { - cmd1; - for (blah) - while (this) - if (test) - cmd2; - cmd3; - - if (test) - { - cmd1; - cmd2; - cmd3; - } - } - - - /* Test for 'cindent' do/while mixed with if/else: */ - - { - do - if (asdf) - asdfasd; - while (cond); - - do - if (asdf) - while (asdf) - asdf; - while (asdf); - } - - /* Test for 'cindent' with two ) on a continuation line */ - { - if (asdfasdf;asldkfj asdlkfj as;ldkfj sal;d - aal;sdkjf ( ;asldfkja;sldfk - al;sdjfka ;slkdf ) sa;ldkjfsa dlk;) - line up here; - } - - - /* C++ tests: */ - - // foo() these three lines should remain in column 0 - // { - // } - - /* Test for continuation and unterminated lines: */ - { - i = 99 + 14325 + - 21345 + - 21345 + - 21345 + ( 21345 + - 21345) + - 2345 + - 1234; - c = 1; - } - - /* - testje for indent with empty line - - here */ - - { - if (testing && - not a joke || - line up here) - hay; - if (testing && - (not a joke || testing - )line up here) - hay; - if (testing && - (not a joke || testing - line up here)) - hay; - } - - - { - switch (c) - { - case xx: - do - if (asdf) - do - asdfasdf; - while (asdf); - else - asdfasdf; - while (cond); - case yy: - case xx: - case zz: - testing; - } - } - - { - if (cond) { - foo; - } - else - { - bar; - } - } - - { - if (alskdfj ;alsdkfjal;skdjf (;sadlkfsa ;dlkf j;alksdfj ;alskdjf - alsdkfj (asldk;fj - awith cino=(0 ;lf this one goes to below the paren with == - ;laksjfd ;lsakdjf ;alskdf asd) - asdfasdf;))) - asdfasdf; - } - - int - func(a, b) - int a; - int c; - { - if (c1 && (c2 || - c3)) - foo; - if (c1 && - (c2 || c3) - ) - } - - { - while (asd) - { - if (asdf) - if (test) - if (that) - { - if (asdf) - do - cdasd; - while (as - df); - } - else - if (asdf) - asdf; - else - asdf; - asdf; - } - } - - { - s = "/*"; b = ';' - s = "/*"; b = ';'; - a = b; - } - - { - switch (a) - { - case a: - switch (t) - { - case 1: - cmd; - break; - case 2: - cmd; - break; - } - cmd; - break; - case b: - { - int i; - cmd; - } - break; - case c: { - int i; - cmd; - } - case d: if (cond && - test) { /* this line doesn't work right */ - int i; - cmd; - } - break; - } - } - - { - if (!(vim_strchr(p_cpo, CPO_BUFOPTGLOB) != NULL && entering) && - (bp_to->b_p_initialized || - (!entering && vim_strchr(p_cpo, CPO_BUFOPT) != NULL))) - return; - label : - asdf = asdf ? - asdf : asdf; - asdf = asdf ? - asdf: asdf; - } - - /* Special Comments : This function has the added complexity (compared */ - /* : to addtolist) of having to check for a detail */ - /* : texture and add that to the list first. */ - - char *(array[100]) = { - "testje", - "foo", - "bar", - } - - enum soppie - { - yes = 0, - no, - maybe - }; - - typedef enum soppie - { - yes = 0, - no, - maybe - }; - - static enum - { - yes = 0, - no, - maybe - } soppie; - - public static enum - { - yes = 0, - no, - maybe - } soppie; - - static private enum - { - yes = 0, - no, - maybe - } soppie; - - { - int a, - b; - } - - { - struct Type - { - int i; - char *str; - } var[] = - { - 0, "zero", - 1, "one", - 2, "two", - 3, "three" - }; - - float matrix[3][3] = - { - { - 0, - 1, - 2 - }, - { - 3, - 4, - 5 - }, - { - 6, - 7, - 8 - } - }; - } - - { - /* blah ( blah */ - /* where does this go? */ - - /* blah ( blah */ - cmd; - - func(arg1, - /* comment */ - arg2); - a; - { - b; - { - c; /* Hey, NOW it indents?! */ - } - } - - { - func(arg1, - arg2, - arg3); - /* Hey, what am I doing here? Is this coz of the ","? */ - } - } - - main () - { - if (cond) - { - a = b; - } - if (cond) { - a = c; - } - if (cond) - a = d; - return; - } - - { - case 2: if (asdf && - asdfasdf) - aasdf; - a = 9; - case 3: if (asdf) - aasdf; - a = 9; - case 4: x = 1; - y = 2; - - label: if (asdf) - here; - - label: if (asdf && - asdfasdf) - { - } - - label: if (asdf && - asdfasdf) { - there; - } - - label: if (asdf && - asdfasdf) - there; - } - - { - /* - hello with ":set comments= cino=c5" - */ - - /* - hello with ":set comments= cino=" - */ - } - - - { - if (a < b) { - a = a + 1; - } else - a = a + 2; - - if (a) - do { - testing; - } while (asdfasdf); - a = b + 1; - asdfasdf - } - - { - for ( int i = 0; - i < 10; i++ ) - { - } - i = 0; - } - - class bob - { - int foo() {return 1;} - int bar; - } - - main() - { - while(1) - if (foo) - { - bar; - } - else { - asdf; - } - misplacedline; - } - - { - if (clipboard.state == SELECT_DONE - && ((row == clipboard.start.lnum - && col >= clipboard.start.col) - || row > clipboard.start.lnum)) - } - - { - if (1) {i += 4;} - where_am_i; - return 0; - } - - { - { - } // sdf(asdf - if (asdf) - asd; - } - - { - label1: - label2: - } - - { - int fooRet = foo(pBar1, false /*fKB*/, - true /*fPTB*/, 3 /*nT*/, false /*fDF*/); - f() { - for ( i = 0; - i < m; - /* c */ i++ ) { - a = b; - } - } - } - - { - f1(/*comment*/); - f2(); - } - - { - do { - if (foo) { - } else - ; - } while (foo); - foo(); // was wrong - } - - int x; // no extra indent because of the ; - void func() - { - } - - char *tab[] = {"aaa", - "};", /* }; */ NULL} - int indented; - {} - - char *a[] = {"aaa", "bbb", - "ccc", NULL}; - // here - - char *tab[] = {"aaa", - "xx", /* xx */}; /* asdf */ - int not_indented; - - { - do { - switch (bla) - { - case 1: if (foo) - bar; - } - } while (boo); - wrong; - } - - int foo, - bar; - int foo; - - #if defined(foo) \ - && defined(bar) - char * xx = "asdf\ - foo\ - bor"; - int x; - - char *foo = "asdf\ - asdf\ - asdf", - *bar; - - void f() - { - #if defined(foo) \ - && defined(bar) - char *foo = "asdf\ - asdf\ - asdf", - *bar; - { - int i; - char *foo = "asdf\ - asdf\ - asdf", - *bar; - } - #endif - } - #endif - - int y; // comment - // comment - - // comment - - { - Constructor(int a, - int b ) : BaseClass(a) - { - } - } - - void foo() - { - char one, - two; - struct bla piet, - jan; - enum foo kees, - jannie; - static unsigned sdf, - krap; - unsigned int piet, - jan; - int - kees, - jan; - } - - { - t(int f, - int d); // ) - d(); - } - - Constructor::Constructor(int a, - int b - ) : - BaseClass(a, - b, - c), - mMember(b), - { - } - - Constructor::Constructor(int a, - int b ) : - BaseClass(a) - { - } - - Constructor::Constructor(int a, - int b ) /*x*/ : /*x*/ BaseClass(a), - member(b) - { - } - - A::A(int a, int b) - : aa(a), - bb(b), - cc(c) - { - } - - class CAbc : - public BaseClass1, - protected BaseClass2 - { - int Test() { return FALSE; } - int Test1() { return TRUE; } - - CAbc(int a, int b ) : - BaseClass(a) - { - switch(xxx) - { - case abc: - asdf(); - break; - - case 999: - baer(); - break; - } - } - - public: // <-- this was incoreectly indented before!! - void testfall(); - protected: - void testfall(); - }; - - class CAbc : public BaseClass1, - protected BaseClass2 - { - }; - - static struct - { - int a; - int b; - } variable[COUNT] = - { - { - 123, - 456 - }, - { - 123, - 456 - } - }; - - static struct - { - int a; - int b; - } variable[COUNT] = - { - { 123, 456 }, - { 123, 456 } - }; - - void asdf() /* ind_maxparen may cause trouble here */ - { - if ((0 - && 1 - && 1 - && 1 - && 1 - && 1 - && 1 - && 1 - && 1 - && 1 - && 1 - && 1 - && 1 - && 1 - && 1 - && 1 - && 1 - && 1 - && 1 - && 1 - && 1 - && 1 - && 1 - && 1 - && 1 - && 1)) break; - } - - foo() - { - a = cond ? foo() : asdf - + asdf; - - a = cond ? - foo() : asdf - + asdf; - } - - int main(void) - { - if (a) - if (b) - 2; - else 3; - next_line_of_code(); - } - - barry() - { - Foo::Foo (int one, - int two) - : something(4) - {} - } - - barry() - { - Foo::Foo (int one, int two) - : something(4) - {} - } - - Constructor::Constructor(int a, - int b - ) : - BaseClass(a, - b, - c), - mMember(b) - { - } - int main () - { - if (lala) - do - ++(*lolo); - while (lili - && lele); - lulu; - } - - int main () - { - switch (c) - { - case 'c': if (cond) - { - } - } - } - - main() - { - (void) MyFancyFuasdfadsfnction( - argument); - } - - main() - { - char foo[] = "/*"; - /* as - df */ - hello - } - - /* valid namespaces with normal indent */ - namespace - { - { - 111111111111; - } - } - namespace /* test */ - { - 11111111111111111; - } - namespace // test - { - 111111111111111111; - } - namespace - { - 111111111111111111; - } - namespace test - { - 111111111111111111; - } - namespace{ - 111111111111111111; - } - namespace test{ - 111111111111111111; - } - namespace { - 111111111111111111; - } - namespace test { - 111111111111111111; - namespace test2 { - 22222222222222222; - } - } - - /* invalid namespaces use block indent */ - namespace test test2 { - 111111111111111111111; - } - namespace11111111111 { - 111111111111; - } - namespace() { - 1111111111111; - } - namespace() - { - 111111111111111111; - } - namespace test test2 - { - 1111111111111111111; - } - namespace111111111 - { - 111111111111111111; - } - void getstring() { - /* Raw strings */ - const char* s = R"( - test { - # comment - field: 123 - } - )"; - } - void getstring() { - const char* s = R"foo( - test { - # comment - field: 123 - } - )foo"; - } - - { - int a[4] = { - [0] = 0, - [1] = 1, - [2] = 2, - [3] = 3, - }; - } - - { - a = b[2] - + 3; - } - - { - if (1) - /* aaaaa - * bbbbb - */ - a = 1; - } - - void func() - { - switch (foo) - { - case (bar): - if (baz()) - quux(); - break; - case (shmoo): - if (!bar) - { - } - case (foo1): - switch (bar) - { - case baz: - baz_f(); - break; - } - break; - default: - baz(); - baz(); - break; - } - } - - /* end of AUTO */ - ]=]) - - feed_command('/start of AUTO') - feed('=/end of AUTO<cr>') - - expect([=[ - - /* start of AUTO matically checked vim: set ts=4 : */ - { - if (test) - cmd1; - cmd2; - } - - { - if (test) - cmd1; - else - cmd2; - } - - { - if (test) - { - cmd1; - cmd2; - } - } - - { - if (test) - { - cmd1; - else - } - } - - { - while (this) - if (test) - cmd1; - cmd2; - } - - { - while (this) - if (test) - cmd1; - else - cmd2; - } - - { - if (test) - { - cmd; - } - - if (test) - cmd; - } - - { - if (test) { - cmd; - } - - if (test) cmd; - } - - { - cmd1; - for (blah) - while (this) - if (test) - cmd2; - cmd3; - } - - { - cmd1; - for (blah) - while (this) - if (test) - cmd2; - cmd3; - - if (test) - { - cmd1; - cmd2; - cmd3; - } - } - - - /* Test for 'cindent' do/while mixed with if/else: */ - - { - do - if (asdf) - asdfasd; - while (cond); - - do - if (asdf) - while (asdf) - asdf; - while (asdf); - } - - /* Test for 'cindent' with two ) on a continuation line */ - { - if (asdfasdf;asldkfj asdlkfj as;ldkfj sal;d - aal;sdkjf ( ;asldfkja;sldfk - al;sdjfka ;slkdf ) sa;ldkjfsa dlk;) - line up here; - } - - - /* C++ tests: */ - - // foo() these three lines should remain in column 0 - // { - // } - - /* Test for continuation and unterminated lines: */ - { - i = 99 + 14325 + - 21345 + - 21345 + - 21345 + ( 21345 + - 21345) + - 2345 + - 1234; - c = 1; - } - - /* - testje for indent with empty line - - here */ - - { - if (testing && - not a joke || - line up here) - hay; - if (testing && - (not a joke || testing - )line up here) - hay; - if (testing && - (not a joke || testing - line up here)) - hay; - } - - - { - switch (c) - { - case xx: - do - if (asdf) - do - asdfasdf; - while (asdf); - else - asdfasdf; - while (cond); - case yy: - case xx: - case zz: - testing; - } - } - - { - if (cond) { - foo; - } - else - { - bar; - } - } - - { - if (alskdfj ;alsdkfjal;skdjf (;sadlkfsa ;dlkf j;alksdfj ;alskdjf - alsdkfj (asldk;fj - awith cino=(0 ;lf this one goes to below the paren with == - ;laksjfd ;lsakdjf ;alskdf asd) - asdfasdf;))) - asdfasdf; - } - - int - func(a, b) - int a; - int c; - { - if (c1 && (c2 || - c3)) - foo; - if (c1 && - (c2 || c3) - ) - } - - { - while (asd) - { - if (asdf) - if (test) - if (that) - { - if (asdf) - do - cdasd; - while (as - df); - } - else - if (asdf) - asdf; - else - asdf; - asdf; - } - } - - { - s = "/*"; b = ';' - s = "/*"; b = ';'; - a = b; - } - - { - switch (a) - { - case a: - switch (t) - { - case 1: - cmd; - break; - case 2: - cmd; - break; - } - cmd; - break; - case b: - { - int i; - cmd; - } - break; - case c: { - int i; - cmd; - } - case d: if (cond && - test) { /* this line doesn't work right */ - int i; - cmd; - } - break; - } - } - - { - if (!(vim_strchr(p_cpo, CPO_BUFOPTGLOB) != NULL && entering) && - (bp_to->b_p_initialized || - (!entering && vim_strchr(p_cpo, CPO_BUFOPT) != NULL))) - return; - label : - asdf = asdf ? - asdf : asdf; - asdf = asdf ? - asdf: asdf; - } - - /* Special Comments : This function has the added complexity (compared */ - /* : to addtolist) of having to check for a detail */ - /* : texture and add that to the list first. */ - - char *(array[100]) = { - "testje", - "foo", - "bar", - } - - enum soppie - { - yes = 0, - no, - maybe - }; - - typedef enum soppie - { - yes = 0, - no, - maybe - }; - - static enum - { - yes = 0, - no, - maybe - } soppie; - - public static enum - { - yes = 0, - no, - maybe - } soppie; - - static private enum - { - yes = 0, - no, - maybe - } soppie; - - { - int a, - b; - } - - { - struct Type - { - int i; - char *str; - } var[] = - { - 0, "zero", - 1, "one", - 2, "two", - 3, "three" - }; - - float matrix[3][3] = - { - { - 0, - 1, - 2 - }, - { - 3, - 4, - 5 - }, - { - 6, - 7, - 8 - } - }; - } - - { - /* blah ( blah */ - /* where does this go? */ - - /* blah ( blah */ - cmd; - - func(arg1, - /* comment */ - arg2); - a; - { - b; - { - c; /* Hey, NOW it indents?! */ - } - } - - { - func(arg1, - arg2, - arg3); - /* Hey, what am I doing here? Is this coz of the ","? */ - } - } - - main () - { - if (cond) - { - a = b; - } - if (cond) { - a = c; - } - if (cond) - a = d; - return; - } - - { - case 2: if (asdf && - asdfasdf) - aasdf; - a = 9; - case 3: if (asdf) - aasdf; - a = 9; - case 4: x = 1; - y = 2; - - label: if (asdf) - here; - - label: if (asdf && - asdfasdf) - { - } - - label: if (asdf && - asdfasdf) { - there; - } - - label: if (asdf && - asdfasdf) - there; - } - - { - /* - hello with ":set comments= cino=c5" - */ - - /* - hello with ":set comments= cino=" - */ - } - - - { - if (a < b) { - a = a + 1; - } else - a = a + 2; - - if (a) - do { - testing; - } while (asdfasdf); - a = b + 1; - asdfasdf - } - - { - for ( int i = 0; - i < 10; i++ ) - { - } - i = 0; - } - - class bob - { - int foo() {return 1;} - int bar; - } - - main() - { - while(1) - if (foo) - { - bar; - } - else { - asdf; - } - misplacedline; - } - - { - if (clipboard.state == SELECT_DONE - && ((row == clipboard.start.lnum - && col >= clipboard.start.col) - || row > clipboard.start.lnum)) - } - - { - if (1) {i += 4;} - where_am_i; - return 0; - } - - { - { - } // sdf(asdf - if (asdf) - asd; - } - - { - label1: - label2: - } - - { - int fooRet = foo(pBar1, false /*fKB*/, - true /*fPTB*/, 3 /*nT*/, false /*fDF*/); - f() { - for ( i = 0; - i < m; - /* c */ i++ ) { - a = b; - } - } - } - - { - f1(/*comment*/); - f2(); - } - - { - do { - if (foo) { - } else - ; - } while (foo); - foo(); // was wrong - } - - int x; // no extra indent because of the ; - void func() - { - } - - char *tab[] = {"aaa", - "};", /* }; */ NULL} - int indented; - {} - - char *a[] = {"aaa", "bbb", - "ccc", NULL}; - // here - - char *tab[] = {"aaa", - "xx", /* xx */}; /* asdf */ - int not_indented; - - { - do { - switch (bla) - { - case 1: if (foo) - bar; - } - } while (boo); - wrong; - } - - int foo, - bar; - int foo; - - #if defined(foo) \ - && defined(bar) - char * xx = "asdf\ - foo\ - bor"; - int x; - - char *foo = "asdf\ - asdf\ - asdf", - *bar; - - void f() - { - #if defined(foo) \ - && defined(bar) - char *foo = "asdf\ - asdf\ - asdf", - *bar; - { - int i; - char *foo = "asdf\ - asdf\ - asdf", - *bar; - } - #endif - } - #endif - - int y; // comment - // comment - - // comment - - { - Constructor(int a, - int b ) : BaseClass(a) - { - } - } - - void foo() - { - char one, - two; - struct bla piet, - jan; - enum foo kees, - jannie; - static unsigned sdf, - krap; - unsigned int piet, - jan; - int - kees, - jan; - } - - { - t(int f, - int d); // ) - d(); - } - - Constructor::Constructor(int a, - int b - ) : - BaseClass(a, - b, - c), - mMember(b), - { - } - - Constructor::Constructor(int a, - int b ) : - BaseClass(a) - { - } - - Constructor::Constructor(int a, - int b ) /*x*/ : /*x*/ BaseClass(a), - member(b) - { - } - - A::A(int a, int b) - : aa(a), - bb(b), - cc(c) - { - } - - class CAbc : - public BaseClass1, - protected BaseClass2 - { - int Test() { return FALSE; } - int Test1() { return TRUE; } - - CAbc(int a, int b ) : - BaseClass(a) - { - switch(xxx) - { - case abc: - asdf(); - break; - - case 999: - baer(); - break; - } - } - - public: // <-- this was incoreectly indented before!! - void testfall(); - protected: - void testfall(); - }; - - class CAbc : public BaseClass1, - protected BaseClass2 - { - }; - - static struct - { - int a; - int b; - } variable[COUNT] = - { - { - 123, - 456 - }, - { - 123, - 456 - } - }; - - static struct - { - int a; - int b; - } variable[COUNT] = - { - { 123, 456 }, - { 123, 456 } - }; - - void asdf() /* ind_maxparen may cause trouble here */ - { - if ((0 - && 1 - && 1 - && 1 - && 1 - && 1 - && 1 - && 1 - && 1 - && 1 - && 1 - && 1 - && 1 - && 1 - && 1 - && 1 - && 1 - && 1 - && 1 - && 1 - && 1 - && 1 - && 1 - && 1 - && 1 - && 1)) break; - } - - foo() - { - a = cond ? foo() : asdf - + asdf; - - a = cond ? - foo() : asdf - + asdf; - } - - int main(void) - { - if (a) - if (b) - 2; - else 3; - next_line_of_code(); - } - - barry() - { - Foo::Foo (int one, - int two) - : something(4) - {} - } - - barry() - { - Foo::Foo (int one, int two) - : something(4) - {} - } - - Constructor::Constructor(int a, - int b - ) : - BaseClass(a, - b, - c), - mMember(b) - { - } - int main () - { - if (lala) - do - ++(*lolo); - while (lili - && lele); - lulu; - } - - int main () - { - switch (c) - { - case 'c': if (cond) - { - } - } - } - - main() - { - (void) MyFancyFuasdfadsfnction( - argument); - } - - main() - { - char foo[] = "/*"; - /* as - df */ - hello - } - - /* valid namespaces with normal indent */ - namespace - { - { - 111111111111; - } - } - namespace /* test */ - { - 11111111111111111; - } - namespace // test - { - 111111111111111111; - } - namespace - { - 111111111111111111; - } - namespace test - { - 111111111111111111; - } - namespace{ - 111111111111111111; - } - namespace test{ - 111111111111111111; - } - namespace { - 111111111111111111; - } - namespace test { - 111111111111111111; - namespace test2 { - 22222222222222222; - } - } - - /* invalid namespaces use block indent */ - namespace test test2 { - 111111111111111111111; - } - namespace11111111111 { - 111111111111; - } - namespace() { - 1111111111111; - } - namespace() - { - 111111111111111111; - } - namespace test test2 - { - 1111111111111111111; - } - namespace111111111 - { - 111111111111111111; - } - void getstring() { - /* Raw strings */ - const char* s = R"( - test { - # comment - field: 123 - } - )"; - } - void getstring() { - const char* s = R"foo( - test { - # comment - field: 123 - } - )foo"; - } - - { - int a[4] = { - [0] = 0, - [1] = 1, - [2] = 2, - [3] = 3, - }; - } - - { - a = b[2] - + 3; - } - - { - if (1) - /* aaaaa - * bbbbb - */ - a = 1; - } - - void func() - { - switch (foo) - { - case (bar): - if (baz()) - quux(); - break; - case (shmoo): - if (!bar) - { - } - case (foo1): - switch (bar) - { - case baz: - baz_f(); - break; - } - break; - default: - baz(); - baz(); - break; - } - } - - /* end of AUTO */ - ]=]) - end) - - it('2 is working', function() - insert_([=[ - - { - - /* this is - * a real serious important big - * comment - */ - /* insert " about life, the universe, and the rest" after "serious" */ - } - ]=]) - - feed_command('set tw=0 noai fo=croq') - feed_command('let &wm = &columns - 20') - feed_command('/serious/e') - feed('a about life, the universe, and the rest<esc>') - - expect([=[ - - { - - /* this is - * a real serious - * about life, the - * universe, and the - * rest important big - * comment - */ - /* insert " about life, the universe, and the rest" after "serious" */ - } - ]=]) - end) - - it('3 is working', function() - insert_([=[ - - { - /* - * Testing for comments, without 'cin' set - */ - - /* - * what happens here? - */ - - /* - the end of the comment, try inserting a line below */ - - /* how about - this one */ - } - ]=]) - - feed_command('set nocin') - feed_command('/comments') - feed('joabout life<esc>/happens<cr>') - feed('jothere<esc>/below<cr>') - feed('oline<esc>/this<cr>') - feed('Ohello<esc>') - - expect([=[ - - { - /* - * Testing for comments, without 'cin' set - */ - about life - - /* - * what happens here? - */ - there - - /* - the end of the comment, try inserting a line below */ - line - - /* how about - hello - this one */ - } - ]=]) - end) - - it('4 is working', function() - insert_([=[ - - { - var = this + that + vec[0] * vec[0] - + vec[1] * vec[1] - + vec2[2] * vec[2]; - } - ]=]) - feed_command('set cin') - feed_command('/vec2') - feed('==<cr>') - - expect([=[ - - { - var = this + that + vec[0] * vec[0] - + vec[1] * vec[1] - + vec2[2] * vec[2]; - } - ]=]) - end) - - it('5 is working', function() - insert_([=[ - - { - asdf asdflkajds f; - if (tes & ting) { - asdf asdf asdf ; - asdfa sdf asdf; - } - testing1; - if (tes & ting) - { - asdf asdf asdf ; - asdfa sdf asdf; - } - testing2; - } - ]=]) - - feed_command('set cin') - feed_command('set cino=}4') - feed_command('/testing1') - feed('k2==/testing2<cr>') - feed('k2==<cr>') - - expect([=[ - - { - asdf asdflkajds f; - if (tes & ting) { - asdf asdf asdf ; - asdfa sdf asdf; - } - testing1; - if (tes & ting) - { - asdf asdf asdf ; - asdfa sdf asdf; - } - testing2; - } - ]=]) - end) - - it('6 is working', function() - insert_([=[ - - main ( int first_par, /* - * Comment for - * first par - */ - int second_par /* - * Comment for - * second par - */ - ) - { - func( first_par, /* - * Comment for - * first par - */ - second_par /* - * Comment for - * second par - */ - ); - - } - ]=]) - - feed_command('set cin') - feed_command('set cino=(0,)20') - feed_command('/main') - feed('=][<cr>') - - expect([=[ - - main ( int first_par, /* - * Comment for - * first par - */ - int second_par /* - * Comment for - * second par - */ - ) - { - func( first_par, /* - * Comment for - * first par - */ - second_par /* - * Comment for - * second par - */ - ); - - } - ]=]) - end) - - it('7 is working', function() - insert_([=[ - - main(void) - { - /* Make sure that cino=X0s is not parsed like cino=Xs. */ - if (cond) - foo(); - else - { - bar(); - } - } - ]=]) - - feed_command('set cin') - feed_command('set cino=es,n0s') - feed_command('/main') - feed('=][<cr>') - - expect([=[ - - main(void) - { - /* Make sure that cino=X0s is not parsed like cino=Xs. */ - if (cond) - foo(); - else - { - bar(); - } - } - ]=]) - end) - - it('8 is working', function() - insert_([=[ - - { - do - { - if () - { - if () - asdf; - else - asdf; - } - } while (); - cmd; /* this should go under the } */ - } - ]=]) - - feed_command('set cin') - feed_command('set cino=') - feed(']]=][<cr>') - - expect([=[ - - { - do - { - if () - { - if () - asdf; - else - asdf; - } - } while (); - cmd; /* this should go under the } */ - } - ]=]) - end) - - it('9 is working', function() - insert_([=[ - - void f() - { - if ( k() ) { - l(); - - } else { /* Start (two words) end */ - m(); - } - - n(); - } - ]=]) - - feed(']]=][<cr>') - - expect([=[ - - void f() - { - if ( k() ) { - l(); - - } else { /* Start (two words) end */ - m(); - } - - n(); - } - ]=]) - end) - - it('10 is working', function() - -- This is nasty. This is the only test case where the buffer content - -- differs from the original. Why? Proper behaviour of this test depends on - -- the fact that the setup code contains an (unbalanced) opening curly - -- bracket in "set cino={s,e-s". This bracket actually affects the outcome - -- of the test: without it the curly bracket under "void f()" would not be - -- indented properly. And that's why we've had to add one explicitly. - insert_([=[ - { <= THIS IS THE CURLY BRACKET EXPLAINED IN THE COMMENT. - - void f() - { - if ( k() ) - { - l(); - } else { /* Start (two words) end */ - m(); - } - n(); /* should be under the if () */ - } - ]=]) - - feed_command('set cino={s,e-s') - feed(']]=][<cr>') - - expect([=[ - { <= THIS IS THE CURLY BRACKET EXPLAINED IN THE COMMENT. - - void f() - { - if ( k() ) - { - l(); - } else { /* Start (two words) end */ - m(); - } - n(); /* should be under the if () */ - } - ]=]) - end) - - it('11 is working', function() - insert_([=[ - - void bar(void) - { - static array[2][2] = - { - { 1, 2 }, - { 3, 4 }, - } - - while (a) - { - foo(&a); - } - - { - int a; - { - a = a + 1; - } - } - b = a; - } - - void func(void) - { - a = 1; - { - b = 2; - } - c = 3; - d = 4; - } - /* foo */ - ]=]) - - feed_command('set cino={s,fs') - feed(']]=/ foo<cr>') - - expect([=[ - - void bar(void) - { - static array[2][2] = - { - { 1, 2 }, - { 3, 4 }, - } - - while (a) - { - foo(&a); - } - - { - int a; - { - a = a + 1; - } - } - b = a; - } - - void func(void) - { - a = 1; - { - b = 2; - } - c = 3; - d = 4; - } - /* foo */ - ]=]) - end) - - it('12 is working', function() - insert_([=[ - - a() - { - do { - a = a + - a; - } while ( a ); /* add text under this line */ - if ( a ) - a; - } - ]=]) - - feed_command('set cino=') - feed_command('/while') - feed('ohere<esc>') - - expect([=[ - - a() - { - do { - a = a + - a; - } while ( a ); /* add text under this line */ - here - if ( a ) - a; - } - ]=]) - end) - - it('13 is working', function() - insert_([=[ - - a() - { - label1: - /* hmm */ - // comment - } - ]=]) - - feed_command('set cino= com=') - feed_command('/comment') - feed('olabel2: b();<cr>label3 /* post */:<cr>/* pre */ label4:<cr>f(/*com*/);<cr>if (/*com*/)<cr>cmd();<esc>') - - expect([=[ - - a() - { - label1: - /* hmm */ - // comment - label2: b(); - label3 /* post */: - /* pre */ label4: - f(/*com*/); - if (/*com*/) - cmd(); - } - ]=]) - end) - - it('14 is working', function() - insert_([=[ - - /* - * A simple comment - */ - - /* - ** A different comment - */ - ]=]) - - feed_command('set comments& comments^=s:/*,m:**,ex:*/') - feed_command('/simple') - feed('=5j<cr>') - - expect([=[ - - /* - * A simple comment - */ - - /* - ** A different comment - */ - ]=]) - end) - - it('15 is working', function() - insert_([=[ - - - void f() - { - - /********* - A comment. - *********/ - } - ]=]) - - feed_command('set cino=c0') - feed_command('set comments& comments-=s1:/* comments^=s0:/*') - feed('2kdd]]=][<cr>') - - expect([=[ - - void f() - { - - /********* - A comment. - *********/ - } - ]=]) - end) - - it('16 is working', function() - insert_([=[ - - - void f() - { - - /********* - A comment. - *********/ - } - ]=]) - - feed_command('set cino=c0,C1') - feed_command('set comments& comments-=s1:/* comments^=s0:/*') - feed('2kdd]]=][<cr>') - - expect([=[ - - void f() - { - - /********* - A comment. - *********/ - } - ]=]) - end) - - it('17 is working', function() - insert_([=[ - - void f() - { - c = c1 && - ( - c2 || - c3 - ) && c4; - } - ]=]) - - feed_command('set cino=') - feed(']]=][<cr>') - - expect([=[ - - void f() - { - c = c1 && - ( - c2 || - c3 - ) && c4; - } - ]=]) - end) - - it('18 is working', function() - insert_([=[ - - - void f() - { - c = c1 && - ( - c2 || - c3 - ) && c4; - } - ]=]) - - feed_command('set cino=(s') - feed('2kdd]]=][<cr>') - - expect([=[ - - void f() - { - c = c1 && - ( - c2 || - c3 - ) && c4; - } - ]=]) - end) - - it('19 is working', function() - insert_([=[ - - - void f() - { - c = c1 && - ( - c2 || - c3 - ) && c4; - } - ]=]) - - feed_command('set cino=(s,U1 ') - feed('2kdd]]=][<cr>') - - expect([=[ - - void f() - { - c = c1 && - ( - c2 || - c3 - ) && c4; - } - ]=]) - end) - - it('20 is working', function() - insert_([=[ - - - void f() - { - if ( c1 - && ( c2 - || c3)) - foo; - } - ]=]) - - feed_command('set cino=(0') - feed('2kdd]]=][<cr>') - - expect([=[ - - void f() - { - if ( c1 - && ( c2 - || c3)) - foo; - } - ]=]) - end) - - it('21 is working', function() - insert_([=[ - - - void f() - { - if ( c1 - && ( c2 - || c3)) - foo; - } - ]=]) - - feed_command('set cino=(0,w1 ') - feed('2kdd]]=][<cr>') - - expect([=[ - - void f() - { - if ( c1 - && ( c2 - || c3)) - foo; - } - ]=]) - end) - - it('22 is working', function() - insert_([=[ - - - void f() - { - c = c1 && ( - c2 || - c3 - ) && c4; - if ( - c1 && c2 - ) - foo; - } - ]=]) - - feed_command('set cino=(s') - feed('2kdd]]=][<cr>') - - expect([=[ - - void f() - { - c = c1 && ( - c2 || - c3 - ) && c4; - if ( - c1 && c2 - ) - foo; - } - ]=]) - end) - - it('23 is working', function() - insert_([=[ - - - void f() - { - c = c1 && ( - c2 || - c3 - ) && c4; - if ( - c1 && c2 - ) - foo; - } - ]=]) - - feed_command('set cino=(s,m1 ') - feed('2kdd]]=][<cr>') - - expect([=[ - - void f() - { - c = c1 && ( - c2 || - c3 - ) && c4; - if ( - c1 && c2 - ) - foo; - } - ]=]) - end) - - it('24 is working', function() - insert_([=[ - - - void f() - { - switch (x) - { - case 1: - a = b; - break; - default: - a = 0; - break; - } - } - ]=]) - - feed_command('set cino=b1') - feed('2kdd]]=][<cr>') - - expect([=[ - - void f() - { - switch (x) - { - case 1: - a = b; - break; - default: - a = 0; - break; - } - } - ]=]) - end) - - it('25 is working', function() - insert_([=[ - - - void f() - { - invokeme( - argu, - ment); - invokeme( - argu, - ment - ); - invokeme(argu, - ment - ); - } - ]=]) - - feed_command('set cino=(0,W5') - feed('2kdd]]=][<cr>') - - expect([=[ - - void f() - { - invokeme( - argu, - ment); - invokeme( - argu, - ment - ); - invokeme(argu, - ment - ); - } - ]=]) - end) - - it('26 is working', function() - insert_([=[ - - - void f() - { - statement; - // comment 1 - // comment 2 - } - ]=]) - - feed_command('set cino=/6') - feed('2kdd]]=][<cr>') - - expect([=[ - - void f() - { - statement; - // comment 1 - // comment 2 - } - ]=]) - end) - - it('27 is working', function() - insert_([=[ - - - void f() - { - statement; - // comment 1 - // comment 2 - } - ]=]) - - feed_command('set cino=') - feed('2kdd]]/comment 1/+1<cr>') - feed('==<cr>') - - expect([=[ - - void f() - { - statement; - // comment 1 - // comment 2 - } - ]=]) - end) - - it('28 is working', function() - insert_([=[ - - - class CAbc - { - int Test() { return FALSE; } - - public: // comment - void testfall(); - protected: - void testfall(); - }; - ]=]) - - feed_command('set cino=g0') - feed('2kdd]]=][<cr>') - - expect([=[ - - class CAbc - { - int Test() { return FALSE; } - - public: // comment - void testfall(); - protected: - void testfall(); - }; - ]=]) - end) - - it('29 is working', function() - insert_([=[ - - - class Foo : public Bar - { - public: - virtual void method1(void) = 0; - virtual void method2(int arg1, - int arg2, - int arg3) = 0; - }; - ]=]) - - feed_command('set cino=(0,gs,hs') - feed('2kdd]]=][<cr>') - - expect([=[ - - class Foo : public Bar - { - public: - virtual void method1(void) = 0; - virtual void method2(int arg1, - int arg2, - int arg3) = 0; - }; - ]=]) - end) - - it('30 is working', function() - insert_([=[ - - - void - foo() - { - if (a) - { - } else - asdf; - } - ]=]) - - feed_command('set cino=+20') - feed('2kdd]]=][<cr>') - - expect([=[ - - void - foo() - { - if (a) - { - } else - asdf; - } - ]=]) - end) - - it('31 is working', function() - insert_([=[ - - - { - averylongfunctionnamelongfunctionnameaverylongfunctionname()->asd( - asdasdf, - func(asdf, - asdfadsf), - asdfasdf - ); - - /* those are ugly, but consequent */ - - func()->asd(asdasdf, - averylongfunctionname( - abc, - dec)->averylongfunctionname( - asdfadsf, - asdfasdf, - asdfasdf, - ), - func(asdfadf, - asdfasdf - ), - asdasdf - ); - - averylongfunctionnameaverylongfunctionnameavery()->asd(fasdf( - abc, - dec)->asdfasdfasdf( - asdfadsf, - asdfasdf, - asdfasdf, - ), - func(asdfadf, - asdfasdf), - asdasdf - ); - } - ]=]) - - feed_command('set cino=(0,W2s') - feed('2kdd]]=][<cr>') - - expect([=[ - - { - averylongfunctionnamelongfunctionnameaverylongfunctionname()->asd( - asdasdf, - func(asdf, - asdfadsf), - asdfasdf - ); - - /* those are ugly, but consequent */ - - func()->asd(asdasdf, - averylongfunctionname( - abc, - dec)->averylongfunctionname( - asdfadsf, - asdfasdf, - asdfasdf, - ), - func(asdfadf, - asdfasdf - ), - asdasdf - ); - - averylongfunctionnameaverylongfunctionnameavery()->asd(fasdf( - abc, - dec)->asdfasdfasdf( - asdfadsf, - asdfasdf, - asdfasdf, - ), - func(asdfadf, - asdfasdf), - asdasdf - ); - } - ]=]) - end) - - it('32 is working', function() - insert_([=[ - - - int main () - { - if (cond1 && - cond2 - ) - foo; - } - ]=]) - - feed_command('set cino=M1') - feed('2kdd]]=][<cr>') - - expect([=[ - - int main () - { - if (cond1 && - cond2 - ) - foo; - } - ]=]) - end) - - it('33 is working', function() - insert_([=[ - - - void func(int a - #if defined(FOO) - , int b - , int c - #endif - ) - { - } - ]=]) - - feed_command('set cino=(0,ts') - feed('2kdd2j=][<cr>') - - expect([=[ - - void func(int a - #if defined(FOO) - , int b - , int c - #endif - ) - { - } - ]=]) - end) - - it('34 is working', function() - insert_([=[ - - - - void - func(int a - #if defined(FOO) - , int b - , int c - #endif - ) - { - } - ]=]) - - feed_command('set cino=(0') - feed('2kdd2j=][<cr>') - - expect([=[ - - - void - func(int a - #if defined(FOO) - , int b - , int c - #endif - ) - { - } - ]=]) - end) - - it('35 is working', function() - insert_([=[ - - - void func(void) - { - if(x==y) - if(y==z) - foo=1; - else { bar=1; - baz=2; - } - printf("Foo!\n"); - } - - void func1(void) - { - char* tab[] = {"foo", "bar", - "baz", "quux", - "this line used", "to be indented incorrectly"}; - foo(); - } - - void func2(void) - { - int tab[] = - {1, 2, - 3, 4, - 5, 6}; - - printf("This line used to be indented incorrectly.\n"); - } - - int foo[] - #ifdef BAR - - = { 1, 2, 3, - 4, 5, 6 } - - #endif - ; - int baz; - - void func3(void) - { - int tab[] = { - 1, 2, - 3, 4, - 5, 6}; - - printf("Don't you dare indent this line incorrectly!\n"); - } - - void - func4(a, b, - c) - int a; - int b; - int c; - { - } - - void - func5( - int a, - int b) - { - } - - void - func6( - int a) - { - } - ]=]) - - feed_command('set cino&') - feed('2kdd2j=7][<cr>') - - expect([=[ - - void func(void) - { - if(x==y) - if(y==z) - foo=1; - else { bar=1; - baz=2; - } - printf("Foo!\n"); - } - - void func1(void) - { - char* tab[] = {"foo", "bar", - "baz", "quux", - "this line used", "to be indented incorrectly"}; - foo(); - } - - void func2(void) - { - int tab[] = - {1, 2, - 3, 4, - 5, 6}; - - printf("This line used to be indented incorrectly.\n"); - } - - int foo[] - #ifdef BAR - - = { 1, 2, 3, - 4, 5, 6 } - - #endif - ; - int baz; - - void func3(void) - { - int tab[] = { - 1, 2, - 3, 4, - 5, 6}; - - printf("Don't you dare indent this line incorrectly!\n"); - } - - void - func4(a, b, - c) - int a; - int b; - int c; - { - } - - void - func5( - int a, - int b) - { - } - - void - func6( - int a) - { - } - ]=]) - end) - - it('36 is working', function() - insert_([=[ - - - void func(void) - { - int tab[] = - { - 1, 2, 3, - 4, 5, 6}; - - printf("Indent this line correctly!\n"); - - switch (foo) - { - case bar: - printf("bar"); - break; - case baz: { - printf("baz"); - break; - } - case quux: - printf("But don't break the indentation of this instruction\n"); - break; - } - } - ]=]) - - feed_command('set cino&') - feed_command('set cino+=l1') - feed('2kdd2j=][<cr>') - - expect([=[ - - void func(void) - { - int tab[] = - { - 1, 2, 3, - 4, 5, 6}; - - printf("Indent this line correctly!\n"); - - switch (foo) - { - case bar: - printf("bar"); - break; - case baz: { - printf("baz"); - break; - } - case quux: - printf("But don't break the indentation of this instruction\n"); - break; - } - } - ]=]) - end) - - it('37 is working', function() - insert_([=[ - - - void func(void) - { - cout << "a" - << "b" - << ") :" - << "c"; - } - ]=]) - - feed_command('set cino&') - feed('2kdd2j=][<cr>') - - expect([=[ - - void func(void) - { - cout << "a" - << "b" - << ") :" - << "c"; - } - ]=]) - end) - - it('38 is working', function() - insert_([=[ - - void func(void) - { - /* - * This is a comment. - */ - } - ]=]) - - feed_command('set com=s1:/*,m:*,ex:*/') - feed(']]3jofoo();<esc>') - - expect([=[ - - void func(void) - { - /* - * This is a comment. - */ - foo(); - } - ]=]) - end) - - it('39 is working', function() - insert_([=[ - - - void func(void) - { - for (int i = 0; i < 10; ++i) - if (i & 1) { - foo(1); - } else - foo(0); - baz(); - } - ]=]) - - feed_command('set cino&') - feed('2kdd2j=][<cr>') - - expect([=[ - - void func(void) - { - for (int i = 0; i < 10; ++i) - if (i & 1) { - foo(1); - } else - foo(0); - baz(); - } - ]=]) - end) - - it('40 is working', function() - insert_([=[ - - - void func(void) - { - if (condition1 - && condition2) - action(); - function(argument1 - && argument2); - - if (c1 && (c2 || - c3)) - foo; - if (c1 && - (c2 || c3)) - { - } - - if ( c1 - && ( c2 - || c3)) - foo; - func( c1 - && ( c2 - || c3)) - foo; - } - ]=]) - - feed_command('set cino=k2s,(0') - feed('2kdd3j=][<cr>') - - expect([=[ - - void func(void) - { - if (condition1 - && condition2) - action(); - function(argument1 - && argument2); - - if (c1 && (c2 || - c3)) - foo; - if (c1 && - (c2 || c3)) - { - } - - if ( c1 - && ( c2 - || c3)) - foo; - func( c1 - && ( c2 - || c3)) - foo; - } - ]=]) - end) - - it('41 is working', function() - insert_([=[ - - - void func(void) - { - if (condition1 - && condition2) - action(); - function(argument1 - && argument2); - - if (c1 && (c2 || - c3)) - foo; - if (c1 && - (c2 || c3)) - { - } - - if ( c1 - && ( c2 - || c3)) - foo; - func( c1 - && ( c2 - || c3)) - foo; - } - ]=]) - - feed_command('set cino=k2s,(s') - feed('2kdd3j=][<cr>') - - expect([=[ - - void func(void) - { - if (condition1 - && condition2) - action(); - function(argument1 - && argument2); - - if (c1 && (c2 || - c3)) - foo; - if (c1 && - (c2 || c3)) - { - } - - if ( c1 - && ( c2 - || c3)) - foo; - func( c1 - && ( c2 - || c3)) - foo; - } - ]=]) - end) - - it('42 is working', function() - insert_([=[ - - - void func(void) - { - if (condition1 - && condition2) - action(); - function(argument1 - && argument2); - - if (c1 && (c2 || - c3)) - foo; - if (c1 && - (c2 || c3)) - { - } - if (c123456789 - && (c22345 - || c3)) - printf("foo\n"); - - c = c1 && - ( - c2 || - c3 - ) && c4; - } - ]=]) - - feed_command('set cino=k2s,(s,U1') - feed('2kdd3j=][<cr>') - - expect([=[ - - void func(void) - { - if (condition1 - && condition2) - action(); - function(argument1 - && argument2); - - if (c1 && (c2 || - c3)) - foo; - if (c1 && - (c2 || c3)) - { - } - if (c123456789 - && (c22345 - || c3)) - printf("foo\n"); - - c = c1 && - ( - c2 || - c3 - ) && c4; - } - ]=]) - end) - - it('43 is working', function() - insert_([=[ - - - void func(void) - { - if (condition1 - && condition2) - action(); - function(argument1 - && argument2); - - if (c1 && (c2 || - c3)) - foo; - if (c1 && - (c2 || c3)) - { - } - if (c123456789 - && (c22345 - || c3)) - printf("foo\n"); - - if ( c1 - && ( c2 - || c3)) - foo; - - a_long_line( - argument, - argument); - a_short_line(argument, - argument); - } - ]=]) - - feed_command('set cino=k2s,(0,W4') - feed('2kdd3j=][<cr>') - - expect([=[ - - void func(void) - { - if (condition1 - && condition2) - action(); - function(argument1 - && argument2); - - if (c1 && (c2 || - c3)) - foo; - if (c1 && - (c2 || c3)) - { - } - if (c123456789 - && (c22345 - || c3)) - printf("foo\n"); - - if ( c1 - && ( c2 - || c3)) - foo; - - a_long_line( - argument, - argument); - a_short_line(argument, - argument); - } - ]=]) - end) - - it('44 is working', function() - insert_([=[ - - - void func(void) - { - if (condition1 - && condition2) - action(); - function(argument1 - && argument2); - - if (c1 && (c2 || - c3)) - foo; - if (c1 && - (c2 || c3)) - { - } - if (c123456789 - && (c22345 - || c3)) - printf("foo\n"); - } - ]=]) - - feed_command('set cino=k2s,u2') - feed('2kdd3j=][<cr>') - - expect([=[ - - void func(void) - { - if (condition1 - && condition2) - action(); - function(argument1 - && argument2); - - if (c1 && (c2 || - c3)) - foo; - if (c1 && - (c2 || c3)) - { - } - if (c123456789 - && (c22345 - || c3)) - printf("foo\n"); - } - ]=]) - end) - - it('45 is working', function() - insert_([=[ - - - void func(void) - { - if (condition1 - && condition2) - action(); - function(argument1 - && argument2); - - if (c1 && (c2 || - c3)) - foo; - if (c1 && - (c2 || c3)) - { - } - if (c123456789 - && (c22345 - || c3)) - printf("foo\n"); - - if ( c1 - && ( c2 - || c3)) - foo; - func( c1 - && ( c2 - || c3)) - foo; - } - ]=]) - - feed_command('set cino=k2s,(0,w1') - feed('2kdd3j=][<cr>') - - expect([=[ - - void func(void) - { - if (condition1 - && condition2) - action(); - function(argument1 - && argument2); - - if (c1 && (c2 || - c3)) - foo; - if (c1 && - (c2 || c3)) - { - } - if (c123456789 - && (c22345 - || c3)) - printf("foo\n"); - - if ( c1 - && ( c2 - || c3)) - foo; - func( c1 - && ( c2 - || c3)) - foo; - } - ]=]) - end) - - it('46 is working', function() - insert_([=[ - - - void func(void) - { - if (condition1 - && condition2) - action(); - function(argument1 - && argument2); - - if (c1 && (c2 || - c3)) - foo; - if (c1 && - (c2 || c3)) - { - } - } - ]=]) - - feed_command('set cino=k2,(s') - feed('2kdd3j=][<cr>') - - expect([=[ - - void func(void) - { - if (condition1 - && condition2) - action(); - function(argument1 - && argument2); - - if (c1 && (c2 || - c3)) - foo; - if (c1 && - (c2 || c3)) - { - } - } - ]=]) - end) - - it('47 is working', function() - insert_([=[ - - NAMESPACESTART - /* valid namespaces with normal indent */ - namespace - { - { - 111111111111; - } - } - namespace /* test */ - { - 11111111111111111; - } - namespace // test - { - 111111111111111111; - } - namespace - { - 111111111111111111; - } - namespace test - { - 111111111111111111; - } - namespace test::cpp17 - { - 111111111111111111; - } - namespace ::incorrectcpp17 - { - 111111111111111111; - } - namespace test::incorrectcpp17:: - { - 111111111111111111; - } - namespace test:incorrectcpp17 - { - 111111111111111111; - } - namespace test:::incorrectcpp17 - { - 111111111111111111; - } - namespace{ - 111111111111111111; - } - namespace test{ - 111111111111111111; - } - namespace { - 111111111111111111; - } - namespace test { - 111111111111111111; - namespace test2 { - 22222222222222222; - } - } - - /* invalid namespaces use block indent */ - namespace test test2 { - 111111111111111111111; - } - namespace11111111111 { - 111111111111; - } - namespace() { - 1111111111111; - } - namespace() - { - 111111111111111111; - } - namespace test test2 - { - 1111111111111111111; - } - namespace111111111 - { - 111111111111111111; - } - NAMESPACEEND - ]=]) - - feed_command('set cino=N-s') - feed_command('/^NAMESPACESTART') - feed('=/^NAMESPACEEND<cr>') - - expect([=[ - - NAMESPACESTART - /* valid namespaces with normal indent */ - namespace - { - { - 111111111111; - } - } - namespace /* test */ - { - 11111111111111111; - } - namespace // test - { - 111111111111111111; - } - namespace - { - 111111111111111111; - } - namespace test - { - 111111111111111111; - } - namespace test::cpp17 - { - 111111111111111111; - } - namespace ::incorrectcpp17 - { - 111111111111111111; - } - namespace test::incorrectcpp17:: - { - 111111111111111111; - } - namespace test:incorrectcpp17 - { - 111111111111111111; - } - namespace test:::incorrectcpp17 - { - 111111111111111111; - } - namespace{ - 111111111111111111; - } - namespace test{ - 111111111111111111; - } - namespace { - 111111111111111111; - } - namespace test { - 111111111111111111; - namespace test2 { - 22222222222222222; - } - } - - /* invalid namespaces use block indent */ - namespace test test2 { - 111111111111111111111; - } - namespace11111111111 { - 111111111111; - } - namespace() { - 1111111111111; - } - namespace() - { - 111111111111111111; - } - namespace test test2 - { - 1111111111111111111; - } - namespace111111111 - { - 111111111111111111; - } - NAMESPACEEND - ]=]) - end) - - it('48 is working', function() - insert_([=[ - - JSSTART - var bar = { - foo: { - that: this, - some: ok, - }, - "bar":{ - a : 2, - b: "123abc", - x: 4, - "y": 5 - } - } - JSEND - ]=]) - - feed_command('set cino=j1,J1') - feed_command('/^JSSTART') - feed('=/^JSEND<cr>') - - expect([=[ - - JSSTART - var bar = { - foo: { - that: this, - some: ok, - }, - "bar":{ - a : 2, - b: "123abc", - x: 4, - "y": 5 - } - } - JSEND - ]=]) - end) - - it('49 is working', function() - insert_([=[ - - JSSTART - var foo = [ - 1, - 2, - 3 - ]; - JSEND - ]=]) - - feed_command('set cino=j1,J1') - feed_command('/^JSSTART') - feed('=/^JSEND<cr>') - - expect([=[ - - JSSTART - var foo = [ - 1, - 2, - 3 - ]; - JSEND - ]=]) - end) - - it('50 is working', function() - insert_([=[ - - JSSTART - function bar() { - var foo = [ - 1, - 2, - 3 - ]; - } - JSEND - ]=]) - - feed_command('set cino=j1,J1') - feed_command('/^JSSTART') - feed('=/^JSEND<cr>') - - expect([=[ - - JSSTART - function bar() { - var foo = [ - 1, - 2, - 3 - ]; - } - JSEND - ]=]) - end) - - it('51 is working', function() - insert_([=[ - - JSSTART - (function($){ - - if (cond && - cond) { - stmt; - } - window.something.left = - (width - 50 + offset) + "px"; - var class_name='myclass'; - - function private_method() { - } - - var public_method={ - method: function(options,args){ - private_method(); - } - } - - function init(options) { - - $(this).data(class_name+'_public',$.extend({},{ - foo: 'bar', - bar: 2, - foobar: [ - 1, - 2, - 3 - ], - callback: function(){ - return true; - } - }, options||{})); - } - - $.fn[class_name]=function() { - - var _arguments=arguments; - return this.each(function(){ - - var options=$(this).data(class_name+'_public'); - if (!options) { - init.apply(this,_arguments); - - } else { - var method=public_method[_arguments[0]]; - - if (typeof(method)!='function') { - console.log(class_name+' has no method "'+_arguments[0]+'"'); - return false; - } - _arguments[0]=options; - method.apply(this,_arguments); - } - }); - } - - })(jQuery); - JSEND - ]=]) - - feed_command('set cino=j1,J1') - feed_command('/^JSSTART') - feed('=/^JSEND<cr>') - - expect([=[ - - JSSTART - (function($){ - - if (cond && - cond) { - stmt; - } - window.something.left = - (width - 50 + offset) + "px"; - var class_name='myclass'; - - function private_method() { - } - - var public_method={ - method: function(options,args){ - private_method(); - } - } - - function init(options) { - - $(this).data(class_name+'_public',$.extend({},{ - foo: 'bar', - bar: 2, - foobar: [ - 1, - 2, - 3 - ], - callback: function(){ - return true; - } - }, options||{})); - } - - $.fn[class_name]=function() { - - var _arguments=arguments; - return this.each(function(){ - - var options=$(this).data(class_name+'_public'); - if (!options) { - init.apply(this,_arguments); - - } else { - var method=public_method[_arguments[0]]; - - if (typeof(method)!='function') { - console.log(class_name+' has no method "'+_arguments[0]+'"'); - return false; - } - _arguments[0]=options; - method.apply(this,_arguments); - } - }); - } - - })(jQuery); - JSEND - ]=]) - end) - - it('52 is working', function() - insert_([=[ - - JSSTART - function init(options) { - $(this).data(class_name+'_public',$.extend({},{ - foo: 'bar', - bar: 2, - foobar: [ - 1, - 2, - 3 - ], - callback: function(){ - return true; - } - }, options||{})); - } - JSEND - ]=]) - - feed_command('set cino=j1,J1') - feed_command('/^JSSTART') - feed('=/^JSEND<cr>') - - expect([=[ - - JSSTART - function init(options) { - $(this).data(class_name+'_public',$.extend({},{ - foo: 'bar', - bar: 2, - foobar: [ - 1, - 2, - 3 - ], - callback: function(){ - return true; - } - }, options||{})); - } - JSEND - ]=]) - end) - - it('53 is working', function() - insert_([=[ - - JSSTART - (function($){ - function init(options) { - $(this).data(class_name+'_public',$.extend({},{ - foo: 'bar', - bar: 2, - foobar: [ - 1, - 2, - 3 - ], - callback: function(){ - return true; - } - }, options||{})); - } - })(jQuery); - JSEND - ]=]) - - feed_command('set cino=j1,J1') - feed_command('/^JSSTART') - feed('=/^JSEND<cr>') - - expect([=[ - - JSSTART - (function($){ - function init(options) { - $(this).data(class_name+'_public',$.extend({},{ - foo: 'bar', - bar: 2, - foobar: [ - 1, - 2, - 3 - ], - callback: function(){ - return true; - } - }, options||{})); - } - })(jQuery); - JSEND - ]=]) - end) - - it('javascript indent / vim-patch 7.4.670', function() - insert_([=[ - - JSSTART - // Results of JavaScript indent - // 1 - (function(){ - var a = [ - 'a', - 'b', - 'c', - 'd', - 'e', - 'f', - 'g', - 'h', - 'i' - ]; - }()) - - // 2 - (function(){ - var a = [ - 0 + - 5 * - 9 * - 'a', - 'b', - 0 + - 5 * - 9 * - 'c', - 'd', - 'e', - 'f', - 'g', - 'h', - 'i' - ]; - }()) - - // 3 - (function(){ - var a = [ - 0 + - // comment 1 - 5 * - /* comment 2 */ - 9 * - 'a', - 'b', - 0 + - 5 * - 9 * - 'c', - 'd', - 'e', - 'f', - 'g', - 'h', - 'i' - ]; - }()) - - // 4 - { - var a = [ - 0, - 1 - ]; - var b; - var c; - } - - // 5 - { - var a = [ - [ - 0 - ], - 2, - 3 - ]; - } - - // 6 - { - var a = [ - [ - 0, - 1 - ], - 2, - 3 - ]; - } - - // 7 - { - var a = [ - // [ - 0, - // 1 - // ], - 2, - 3 - ]; - } - - // 8 - var x = [ - (function(){ - var a, - b, - c, - d, - e, - f, - g, - h, - i; - }) - ]; - - // 9 - var a = [ - 0 + - 5 * - 9 * - 'a', - 'b', - 0 + - 5 * - 9 * - 'c', - 'd', - 'e', - 'f', - 'g', - 'h', - 'i' - ]; - - // 10 - var a, - b, - c, - d, - e, - f, - g, - h, - i; - JSEND - ]=]) - - -- :set cino=j1,J1,+2 - feed_command('set cino=j1,J1,+2') - feed_command('/^JSSTART') - feed('=/^JSEND<cr>') - - expect([=[ - - JSSTART - // Results of JavaScript indent - // 1 - (function(){ - var a = [ - 'a', - 'b', - 'c', - 'd', - 'e', - 'f', - 'g', - 'h', - 'i' - ]; - }()) - - // 2 - (function(){ - var a = [ - 0 + - 5 * - 9 * - 'a', - 'b', - 0 + - 5 * - 9 * - 'c', - 'd', - 'e', - 'f', - 'g', - 'h', - 'i' - ]; - }()) - - // 3 - (function(){ - var a = [ - 0 + - // comment 1 - 5 * - /* comment 2 */ - 9 * - 'a', - 'b', - 0 + - 5 * - 9 * - 'c', - 'd', - 'e', - 'f', - 'g', - 'h', - 'i' - ]; - }()) - - // 4 - { - var a = [ - 0, - 1 - ]; - var b; - var c; - } - - // 5 - { - var a = [ - [ - 0 - ], - 2, - 3 - ]; - } - - // 6 - { - var a = [ - [ - 0, - 1 - ], - 2, - 3 - ]; - } - - // 7 - { - var a = [ - // [ - 0, - // 1 - // ], - 2, - 3 - ]; - } - - // 8 - var x = [ - (function(){ - var a, - b, - c, - d, - e, - f, - g, - h, - i; - }) - ]; - - // 9 - var a = [ - 0 + - 5 * - 9 * - 'a', - 'b', - 0 + - 5 * - 9 * - 'c', - 'd', - 'e', - 'f', - 'g', - 'h', - 'i' - ]; - - // 10 - var a, - b, - c, - d, - e, - f, - g, - h, - i; - JSEND - ]=]) - end) - - it('line continuations in macros / vim-patch 8.0.0148', function() - insert_([=[ - /* start of define */ - { - } - #define AAA \ - BBB\ - CCC - - #define CNT \ - 1 + \ - 2 + \ - 4 - /* end of define */]=]) - - feed_command('set cino&') - feed_command('/start of define') - feed('=/end of define<cr>') - - expect([=[ - /* start of define */ - { - } - #define AAA \ - BBB\ - CCC - - #define CNT \ - 1 + \ - 2 + \ - 4 - /* end of define */]=]) - end) - - it('* immediately follows comment / vim-patch 8.0.1291', function() - insert_([=[ - { - a = second/*bug*/*line; - }]=]) - - feed_command('set cin cino&') - feed_command('/a = second') - feed('ox') - - expect([=[ - { - a = second/*bug*/*line; - x - }]=]) - end) -end) 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/eval_spec.lua b/test/functional/legacy/eval_spec.lua index b5de5cd232..05d853622e 100644 --- a/test/functional/legacy/eval_spec.lua +++ b/test/functional/legacy/eval_spec.lua @@ -666,7 +666,7 @@ describe('eval', function() source([[ " Vim script used in test_eval.in. Needed for script-local function. - func! s:Testje() + func s:Testje() return "foo" endfunc 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/expand_spec.lua b/test/functional/legacy/expand_spec.lua deleted file mode 100644 index cd3713eabe..0000000000 --- a/test/functional/legacy/expand_spec.lua +++ /dev/null @@ -1,129 +0,0 @@ --- Test for expanding file names - -local helpers = require('test.functional.helpers')(after_each) -local eq = helpers.eq -local call = helpers.call -local nvim = helpers.meths -local clear = helpers.clear -local source = helpers.source - -local function expected_empty() - eq({}, nvim.get_vvar('errors')) -end - -describe('expand file name', function() - after_each(function() - helpers.rmdir('Xdir1') - helpers.rmdir('Xdir2') - helpers.rmdir('Xdir3') - helpers.rmdir('Xdir4') - end) - - before_each(function() - clear() - - source([[ - func Test_with_directories() - call mkdir('Xdir1') - call mkdir('Xdir2') - call mkdir('Xdir3') - cd Xdir3 - call mkdir('Xdir4') - cd .. - - split Xdir1/file - call setline(1, ['a', 'b']) - w - w Xdir3/Xdir4/file - close - - next Xdir?/*/file - call assert_equal('Xdir3/Xdir4/file', expand('%')) - if has('unix') - next! Xdir?/*/nofile - call assert_equal('Xdir?/*/nofile', expand('%')) - endif - " Edit another file, on MS-Windows the swap file would be in use and can't - " be deleted - edit foo - - call assert_equal(0, delete('Xdir1', 'rf')) - call assert_equal(0, delete('Xdir2', 'rf')) - call assert_equal(0, delete('Xdir3', 'rf')) - endfunc - - func Test_with_tilde() - let dir = getcwd() - call mkdir('Xdir ~ dir') - call assert_true(isdirectory('Xdir ~ dir')) - cd Xdir\ ~\ dir - call assert_true(getcwd() =~ 'Xdir \~ dir') - exe 'cd ' . fnameescape(dir) - call delete('Xdir ~ dir', 'd') - call assert_false(isdirectory('Xdir ~ dir')) - endfunc - - func Test_expand_tilde_filename() - split ~ - call assert_equal('~', expand('%')) - call assert_notequal(expand('%:p'), expand('~/')) - call assert_match('\~', expand('%:p')) - bwipe! - endfunc - - func Test_expandcmd() - let $FOO = 'Test' - call assert_equal('e x/Test/y', expandcmd('e x/$FOO/y')) - unlet $FOO - - new - edit Xfile1 - call assert_equal('e Xfile1', expandcmd('e %')) - edit Xfile2 - edit Xfile1 - call assert_equal('e Xfile2', 'e #'->expandcmd()) - edit Xfile2 - edit Xfile3 - edit Xfile4 - let bnum = bufnr('Xfile2') - call assert_equal('e Xfile2', expandcmd('e #' . bnum)) - call setline('.', 'Vim!@#') - call assert_equal('e Vim', expandcmd('e <cword>')) - call assert_equal('e Vim!@#', expandcmd('e <cWORD>')) - enew! - edit Xfile.java - call assert_equal('e Xfile.py', expandcmd('e %:r.py')) - call assert_equal('make abc.java', expandcmd('make abc.%:e')) - call assert_equal('make Xabc.java', expandcmd('make %:s?file?abc?')) - edit a1a2a3.rb - call assert_equal('make b1b2b3.rb a1a2a3 Xfile.o', expandcmd('make %:gs?a?b? %< #<.o')) - - call assert_fails('call expandcmd("make <afile>")', 'E495:') - call assert_fails('call expandcmd("make <afile>")', 'E495:') - enew - call assert_fails('call expandcmd("make %")', 'E499:') - close - endfunc - ]]) - end) - - it('works with directories', function() - call('Test_with_directories') - expected_empty() - end) - - it('works with tilde', function() - call('Test_with_tilde') - expected_empty() - end) - - it('does not expand tilde if it is a filename', function() - call('Test_expand_tilde_filename') - expected_empty() - end) - - it('works with expandcmd()', function() - call('Test_expandcmd') - expected_empty() - 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/listchars_spec.lua b/test/functional/legacy/listchars_spec.lua index dc6ccd3628..7a1afa1fd6 100644 --- a/test/functional/legacy/listchars_spec.lua +++ b/test/functional/legacy/listchars_spec.lua @@ -1,7 +1,8 @@ -- Tests for 'listchars' display with 'list' and :list. local helpers = require('test.functional.helpers')(after_each) -local feed, insert, source = helpers.feed, helpers.insert, helpers.source +local Screen = require('test.functional.ui.screen') +local feed, insert, exec = helpers.feed, helpers.insert, helpers.exec local clear, feed_command, expect = helpers.clear, helpers.feed_command, helpers.expect -- luacheck: ignore 621 (Indentation) @@ -13,7 +14,7 @@ describe("'listchars'", function() -- luacheck: ignore 613 (Trailing whitespace in a string) it("works with 'list'", function() - source([[ + exec([[ function GetScreenCharsForLine(lnum) return join(map(range(1, virtcol('$')), 'nr2char(screenchar(a:lnum, v:val))'), '') endfunction @@ -98,4 +99,80 @@ describe("'listchars'", function() .....h>-$ iii<<<<><<$]]) end) + + it('"exceeds" character does not appear in foldcolumn vim-patch:8.2.3121', function() + local screen = Screen.new(60, 10) + screen:attach() + exec([[ + call setline(1, ['aaa', '', 'a', 'aaaaaa']) + vsplit + vsplit + windo set signcolumn=yes foldcolumn=1 winminwidth=0 nowrap list listchars=extends:>,precedes:< + ]]) + feed('13<C-W>>') + screen:expect([[ + aaa │ a>│ ^aaa | + │ │ | + a │ a │ a | + aaaaaa │ a>│ aaaaaa | + ~ │~ │~ | + ~ │~ │~ | + ~ │~ │~ | + ~ │~ │~ | + [No Name] [+] <[+] [No Name] [+] | + | + ]]) + feed('<C-W>>') + screen:expect([[ + aaa │ >│ ^aaa | + │ │ | + a │ a│ a | + aaaaaa │ >│ aaaaaa | + ~ │~ │~ | + ~ │~ │~ | + ~ │~ │~ | + ~ │~ │~ | + [No Name] [+] <+] [No Name] [+] | + | + ]]) + feed('<C-W>>') + screen:expect([[ + aaa │ │ ^aaa | + │ │ | + a │ │ a | + aaaaaa │ │ aaaaaa | + ~ │~ │~ | + ~ │~ │~ | + ~ │~ │~ | + ~ │~ │~ | + [No Name] [+] <] [No Name] [+] | + | + ]]) + feed('<C-W>>') + screen:expect([[ + aaa │ │ ^aaa | + │ │ | + a │ │ a | + aaaaaa │ │ aaaaaa | + ~ │~ │~ | + ~ │~ │~ | + ~ │~ │~ | + ~ │~ │~ | + [No Name] [+] < [No Name] [+] | + | + ]]) + feed('<C-W>>') + screen:expect([[ + aaa │ │ ^aaa | + │ │ | + a │ │ a | + aaaaaa │ │ aaaaaa | + ~ │~│~ | + ~ │~│~ | + ~ │~│~ | + ~ │~│~ | + [No Name] [+] < [No Name] [+] | + | + ]]) + end) end) diff --git a/test/functional/legacy/memory_usage_spec.lua b/test/functional/legacy/memory_usage_spec.lua index d86caca0e9..8d25b9d927 100644 --- a/test/functional/legacy/memory_usage_spec.lua +++ b/test/functional/legacy/memory_usage_spec.lua @@ -195,10 +195,10 @@ describe('memory usage', function() local after = monitor_memory_usage(pid) source('bwipe!') poke_eventloop() - -- Allow for an increase of 5% in memory usage, which accommodates minor fluctuation, + -- Allow for an increase of 10% in memory usage, which accommodates minor fluctuation, -- but is small enough that if memory were not released (prior to PR #14884), the test -- would fail. - local upper = before.last * 1.05 + local upper = before.last * 1.10 check_result({before=before, after=after}, pcall(ok, after.last <= upper)) 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/search_stat_spec.lua b/test/functional/legacy/search_stat_spec.lua new file mode 100644 index 0000000000..fdd46c0cb9 --- /dev/null +++ b/test/functional/legacy/search_stat_spec.lua @@ -0,0 +1,121 @@ +local helpers = require('test.functional.helpers')(after_each) +local Screen = require('test.functional.ui.screen') +local clear, feed, exec, command = helpers.clear, helpers.feed, helpers.exec, helpers.command +local poke_eventloop = helpers.poke_eventloop + +describe('search stat', function() + local screen + before_each(function() + clear() + screen = Screen.new(30, 10) + screen:set_default_attr_ids({ + [1] = {bold = true, foreground = Screen.colors.Blue}, -- NonText + [2] = {background = Screen.colors.Yellow}, -- Search + [3] = {foreground = Screen.colors.Blue4, background = Screen.colors.LightGrey}, -- Folded + }) + screen:attach() + end) + + it('right spacing with silent mapping vim-patch:8.1.1970', function() + exec([[ + set shortmess-=S + " Append 50 lines with text to search for, "foobar" appears 20 times + call append(0, repeat(['foobar', 'foo', 'fooooobar', 'foba', 'foobar'], 20)) + call setline(2, 'find this') + call setline(70, 'find this') + nnoremap n n + let @/ = 'find this' + call cursor(1,1) + norm n + ]]) + screen:expect([[ + foobar | + {2:^find this} | + fooooobar | + foba | + foobar | + foobar | + foo | + fooooobar | + foba | + /find this [1/2] | + ]]) + command('nnoremap <silent> n n') + feed('gg0n') + screen:expect([[ + foobar | + {2:^find this} | + fooooobar | + foba | + foobar | + foobar | + foo | + fooooobar | + foba | + [1/2] | + ]]) + end) + + it('when only match is in fold vim-patch:8.2.0840', function() + exec([[ + set shortmess-=S + setl foldenable foldmethod=indent foldopen-=search + call append(0, ['if', "\tfoo", "\tfoo", 'endif']) + let @/ = 'foo' + call cursor(1,1) + norm n + ]]) + screen:expect([[ + if | + {3:^+-- 2 lines: foo·············}| + endif | + | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + /foo [1/2] | + ]]) + feed('n') + poke_eventloop() + screen:expect_unchanged() + feed('n') + poke_eventloop() + screen:expect_unchanged() + end) + + it('is cleared by gd and gD vim-patch:8.2.3583', function() + exec([[ + call setline(1, ['int cat;', 'int dog;', 'cat = dog;']) + set shortmess-=S + set hlsearch + ]]) + feed('/dog<CR>') + screen:expect([[ + int cat; | + int {2:^dog}; | + cat = {2:dog}; | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + /dog [1/2] | + ]]) + feed('G0gD') + screen:expect([[ + int {2:^cat}; | + int dog; | + {2:cat} = dog; | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]) + 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/legacy/visual_mode_spec.lua b/test/functional/legacy/visual_mode_spec.lua index c8e83ed649..8b5dd0c2dc 100644 --- a/test/functional/legacy/visual_mode_spec.lua +++ b/test/functional/legacy/visual_mode_spec.lua @@ -1,5 +1,3 @@ --- Test visual line mode selection redraw after scrolling - local helpers = require('test.functional.helpers')(after_each) local Screen = require('test.functional.ui.screen') @@ -10,6 +8,7 @@ local feed_command = helpers.feed_command local funcs = helpers.funcs local meths = helpers.meths local eq = helpers.eq +local exec = helpers.exec describe('visual line mode', function() local screen @@ -40,3 +39,44 @@ describe('visual line mode', function() ]]) end) end) + +describe('visual block mode', function() + it('shows selection correctly with virtualedit=block', function() + clear() + local screen = Screen.new(30, 7) + screen:set_default_attr_ids({ + [1] = {bold = true}, -- ModeMsg + [2] = {background = Screen.colors.LightGrey}, -- Visual + [3] = {foreground = Screen.colors.Blue, bold = true} -- NonText + }) + screen:attach() + + exec([[ + call setline(1, ['aaaaaa', 'bbbb', 'cc']) + set virtualedit=block + normal G + ]]) + + feed('<C-V>gg$') + screen:expect([[ + {2:aaaaaa}^ | + {2:bbbb } | + {2:cc } | + {3:~ }| + {3:~ }| + {3:~ }| + {1:-- VISUAL BLOCK --} | + ]]) + + feed('<Esc>gg<C-V>G$') + screen:expect([[ + {2:aaaaaa } | + {2:bbbb } | + {2:cc}^ {2: } | + {3:~ }| + {3:~ }| + {3:~ }| + {1:-- VISUAL BLOCK --} | + ]]) + end) +end) diff --git a/test/functional/lua/api_spec.lua b/test/functional/lua/api_spec.lua index 81e00bba6d..f173a15d32 100644 --- a/test/functional/lua/api_spec.lua +++ b/test/functional/lua/api_spec.lua @@ -8,6 +8,7 @@ local clear = helpers.clear local eval = helpers.eval local NIL = helpers.NIL local eq = helpers.eq +local exec_lua = helpers.exec_lua before_each(clear) @@ -101,6 +102,13 @@ describe('luaeval(vim.api.…)', function() eq(false, funcs.luaeval('vim.api.nvim__id(false)')) eq(NIL, funcs.luaeval('vim.api.nvim__id(nil)')) + -- API strings from Blobs can work as NUL-terminated C strings + eq('Vim(call):E5555: API call: Vim:E15: Invalid expression: ', + exc_exec('call nvim_eval(v:_null_blob)')) + eq('Vim(call):E5555: API call: Vim:E15: Invalid expression: ', + exc_exec('call nvim_eval(0z)')) + eq(1, eval('nvim_eval(0z31)')) + eq(0, eval([[type(luaeval('vim.api.nvim__id(1)'))]])) eq(1, eval([[type(luaeval('vim.api.nvim__id("1")'))]])) eq(3, eval([[type(luaeval('vim.api.nvim__id({1})'))]])) @@ -111,6 +119,12 @@ describe('luaeval(vim.api.…)', function() eq(7, eval([[type(luaeval('vim.api.nvim__id(nil)'))]])) eq({foo=1, bar={42, {{baz=true}, 5}}}, funcs.luaeval('vim.api.nvim__id({foo=1, bar={42, {{baz=true}, 5}}})')) + + eq(true, funcs.luaeval('vim.api.nvim__id(vim.api.nvim__id)(true)')) + eq(42, exec_lua [[ + local f = vim.api.nvim__id({42, vim.api.nvim__id}) + return f[2](f[1]) + ]]) end) it('correctly converts container objects with type_idx to API objects', function() @@ -159,12 +173,8 @@ describe('luaeval(vim.api.…)', function() it('errors out correctly when working with API', function() -- Conversion errors - eq('Vim(call):E5108: Error executing lua [string "luaeval()"]:1: Cannot convert given lua type', - remove_trace(exc_exec([[call luaeval("vim.api.nvim__id(vim.api.nvim__id)")]]))) eq('Vim(call):E5108: Error executing lua [string "luaeval()"]:1: Cannot convert given lua table', remove_trace(exc_exec([[call luaeval("vim.api.nvim__id({1, foo=42})")]]))) - eq('Vim(call):E5108: Error executing lua [string "luaeval()"]:1: Cannot convert given lua type', - remove_trace(exc_exec([[call luaeval("vim.api.nvim__id({42, vim.api.nvim__id})")]]))) -- Errors in number of arguments eq('Vim(call):E5108: Error executing lua [string "luaeval()"]:1: Expected 1 argument', remove_trace(exc_exec([[call luaeval("vim.api.nvim__id()")]]))) @@ -180,6 +190,8 @@ describe('luaeval(vim.api.…)', function() remove_trace(exc_exec([[call luaeval("vim.api.nvim_buf_get_lines(0, 'test', 1, false)")]]))) eq('Vim(call):E5108: Error executing lua [string "luaeval()"]:1: Number is not integral', remove_trace(exc_exec([[call luaeval("vim.api.nvim_buf_get_lines(0, 1.5, 1, false)")]]))) + eq('Vim(call):E5108: Error executing lua [string "luaeval()"]:1: Expected Lua number', + remove_trace(exc_exec([[call luaeval("vim.api.nvim_win_is_valid(nil)")]]))) eq('Vim(call):E5108: Error executing lua [string "luaeval()"]:1: Expected lua table', remove_trace(exc_exec([[call luaeval("vim.api.nvim__id_float('test')")]]))) diff --git a/test/functional/lua/commands_spec.lua b/test/functional/lua/commands_spec.lua index 9b9ba531b0..b8346df290 100644 --- a/test/functional/lua/commands_spec.lua +++ b/test/functional/lua/commands_spec.lua @@ -141,6 +141,24 @@ describe(':lua command', function() {4:Press ENTER or type command to continue}^ | ]]} end) + + it('Can print results of =expr', function() + helpers.exec_lua("x = 5") + eq("5", helpers.exec_capture(':lua =x')) + helpers.exec_lua("function x() return 'hello' end") + eq([["hello"]], helpers.exec_capture(':lua = x()')) + helpers.exec_lua("x = {a = 1, b = 2}") + eq("{\n a = 1,\n b = 2\n}", helpers.exec_capture(':lua =x')) + helpers.exec_lua([[function x(success) + if success then + return true, "Return value" + else + return false, nil, "Error message" + end + end]]) + eq([[true "Return value"]], helpers.exec_capture(':lua =x(true)')) + eq([[false nil "Error message"]], helpers.exec_capture(':lua =x(false)')) + end) end) describe(':luado command', function() diff --git a/test/functional/lua/diagnostic_spec.lua b/test/functional/lua/diagnostic_spec.lua index a88da63e90..b58fad1cab 100644 --- a/test/functional/lua/diagnostic_spec.lua +++ b/test/functional/lua/diagnostic_spec.lua @@ -208,10 +208,10 @@ describe('vim.diagnostic', function() eq(all_highlights, exec_lua [[ local ns_1_diags = { make_error("Error 1", 1, 1, 1, 5), - make_warning("Warning on Server 1", 2, 1, 2, 5), + make_warning("Warning on Server 1", 2, 1, 2, 3), } local ns_2_diags = { - make_warning("Warning 1", 2, 1, 2, 5), + make_warning("Warning 1", 2, 1, 2, 3), } vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, ns_1_diags) @@ -255,10 +255,10 @@ describe('vim.diagnostic', function() eq({0, 2}, exec_lua [[ local ns_1_diags = { make_error("Error 1", 1, 1, 1, 5), - make_warning("Warning on Server 1", 2, 1, 2, 5), + make_warning("Warning on Server 1", 2, 1, 2, 3), } local ns_2_diags = { - make_warning("Warning 1", 2, 1, 2, 5), + make_warning("Warning 1", 2, 1, 2, 3), } vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, ns_1_diags) @@ -599,10 +599,10 @@ describe('vim.diagnostic', function() eq(all_highlights, exec_lua [[ local ns_1_diags = { make_error("Error 1", 1, 1, 1, 5), - make_warning("Warning on Server 1", 2, 1, 2, 5), + make_warning("Warning on Server 1", 2, 1, 2, 3), } local ns_2_diags = { - make_warning("Warning 1", 2, 1, 2, 5), + make_warning("Warning 1", 2, 1, 2, 3), } vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, ns_1_diags) @@ -787,7 +787,7 @@ describe('vim.diagnostic', function() eq(2, exec_lua [[ vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, { make_error("Error 1", 1, 1, 1, 5), - make_warning("Warning on Server 1", 1, 1, 2, 5), + make_warning("Warning on Server 1", 1, 1, 2, 3), }) return #vim.diagnostic.get(diagnostic_bufnr) @@ -798,9 +798,9 @@ describe('vim.diagnostic', function() eq({2, 3, 2}, exec_lua [[ vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, { make_error("Error 1", 1, 1, 1, 5), - make_warning("Warning on Server 1", 1, 1, 2, 5), - make_info("Ignored information", 1, 1, 2, 5), - make_hint("Here's a hint", 1, 1, 2, 5), + make_warning("Warning on Server 1", 1, 1, 2, 3), + make_info("Ignored information", 1, 1, 2, 3), + make_hint("Here's a hint", 1, 1, 2, 3), }) return { @@ -820,8 +820,8 @@ describe('vim.diagnostic', function() eq(1, exec_lua [[ vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, { make_error("Error 1", 1, 1, 1, 5), - make_warning("Warning on Server 1", 1, 1, 2, 5), - make_info("Ignored information", 1, 1, 2, 5), + make_warning("Warning on Server 1", 1, 1, 2, 3), + make_info("Ignored information", 1, 1, 2, 3), make_error("Error On Other Line", 2, 1, 1, 5), }) @@ -1119,6 +1119,11 @@ describe('vim.diagnostic', function() end) describe('set()', function() + it('validates its arguments', function() + matches("expected a list of diagnostics", + pcall_err(exec_lua, [[vim.diagnostic.set(1, 0, {lnum = 1, col = 2})]])) + end) + it('can perform updates after insert_leave', function() exec_lua [[vim.api.nvim_set_current_buf(diagnostic_bufnr)]] nvim("input", "o") diff --git a/test/functional/lua/filetype_spec.lua b/test/functional/lua/filetype_spec.lua new file mode 100644 index 0000000000..729ebc601b --- /dev/null +++ b/test/functional/lua/filetype_spec.lua @@ -0,0 +1,98 @@ +local helpers = require('test.functional.helpers')(after_each) +local exec_lua = helpers.exec_lua +local eq = helpers.eq +local clear = helpers.clear +local pathroot = helpers.pathroot + +local root = pathroot() + +describe('vim.filetype', function() + before_each(function() + clear() + + exec_lua [[ + local bufnr = vim.api.nvim_create_buf(true, false) + vim.api.nvim_set_current_buf(bufnr) + ]] + end) + + it('works with extensions', function() + eq('radicalscript', exec_lua [[ + vim.filetype.add({ + extension = { + rs = 'radicalscript', + }, + }) + vim.filetype.match('main.rs') + return vim.bo.filetype + ]]) + end) + + it('prioritizes filenames over extensions', function() + eq('somethingelse', exec_lua [[ + vim.filetype.add({ + extension = { + rs = 'radicalscript', + }, + filename = { + ['main.rs'] = 'somethingelse', + }, + }) + vim.filetype.match('main.rs') + return vim.bo.filetype + ]]) + end) + + it('works with filenames', function() + eq('nim', exec_lua [[ + vim.filetype.add({ + filename = { + ['s_O_m_e_F_i_l_e'] = 'nim', + }, + }) + vim.filetype.match('s_O_m_e_F_i_l_e') + return vim.bo.filetype + ]]) + + eq('dosini', exec_lua([[ + local root = ... + vim.filetype.add({ + filename = { + ['config'] = 'toml', + [root .. '/.config/fun/config'] = 'dosini', + }, + }) + vim.filetype.match(root .. '/.config/fun/config') + return vim.bo.filetype + ]], root)) + end) + + it('works with patterns', function() + eq('markdown', exec_lua([[ + local root = ... + vim.filetype.add({ + pattern = { + ['~/blog/.*%.txt'] = 'markdown', + } + }) + vim.filetype.match('~/blog/why_neovim_is_awesome.txt') + return vim.bo.filetype + ]], root)) + end) + + it('works with functions', function() + eq('foss', exec_lua [[ + vim.filetype.add({ + pattern = { + ["relevant_to_(%a+)"] = function(path, bufnr, capture) + if capture == "me" then + return "foss" + end + end, + } + }) + vim.filetype.match('relevant_to_me') + return vim.bo.filetype + ]]) + end) +end) diff --git a/test/functional/lua/luaeval_spec.lua b/test/functional/lua/luaeval_spec.lua index c543dd1995..1322155595 100644 --- a/test/functional/lua/luaeval_spec.lua +++ b/test/functional/lua/luaeval_spec.lua @@ -532,6 +532,7 @@ describe('v:lua', function() command('set pp+=test/functional/fixtures') eq('\tbadval', eval("v:lua.require'leftpad'('badval')")) eq(9003, eval("v:lua.require'bar'.doit()")) + eq(9004, eval("v:lua.require'baz-quux'.doit()")) end) it('throw errors for invalid use', function() diff --git a/test/functional/lua/thread_spec.lua b/test/functional/lua/thread_spec.lua new file mode 100644 index 0000000000..2e0ab7bdff --- /dev/null +++ b/test/functional/lua/thread_spec.lua @@ -0,0 +1,386 @@ +local helpers = require('test.functional.helpers')(after_each) +local Screen = require('test.functional.ui.screen') +local assert_alive = helpers.assert_alive +local clear = helpers.clear +local feed = helpers.feed +local eq = helpers.eq +local exec_lua = helpers.exec_lua +local next_msg = helpers.next_msg +local NIL = helpers.NIL +local pcall_err = helpers.pcall_err + +describe('thread', function() + local screen + + before_each(function() + clear() + screen = Screen.new(50, 10) + screen:attach() + screen:set_default_attr_ids({ + [1] = {bold = true, foreground = Screen.colors.Blue1}, + [2] = {bold = true, reverse = true}, + [3] = {foreground = Screen.colors.Grey100, background = Screen.colors.Red}, + [4] = {bold = true, foreground = Screen.colors.SeaGreen4}, + [5] = {bold = true}, + }) + end) + + it('entry func is executed in protected mode', function() + exec_lua [[ + local thread = vim.loop.new_thread(function() + error('Error in thread entry func') + end) + vim.loop.thread_join(thread) + ]] + + screen:expect([[ + | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {2: }| + {3:Error in luv thread:} | + {3:[string "<nvim>"]:2: Error in thread entry func} | + {4:Press ENTER or type command to continue}^ | + ]]) + feed('<cr>') + assert_alive() + end) + + it('callback is executed in protected mode', function() + exec_lua [[ + local thread = vim.loop.new_thread(function() + local timer = vim.loop.new_timer() + local function ontimeout() + timer:stop() + timer:close() + error('Error in thread callback') + end + timer:start(10, 0, ontimeout) + vim.loop.run() + end) + vim.loop.thread_join(thread) + ]] + + screen:expect([[ + | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {2: }| + {3:Error in luv callback, thread:} | + {3:[string "<nvim>"]:6: Error in thread callback} | + {4:Press ENTER or type command to continue}^ | + ]]) + feed('<cr>') + assert_alive() + end) + + describe('print', function() + it('works', function() + exec_lua [[ + local thread = vim.loop.new_thread(function() + print('print in thread') + end) + vim.loop.thread_join(thread) + ]] + + screen:expect([[ + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + print in thread | + ]]) + end) + end) + + describe('vim.*', function() + before_each(function() + clear() + exec_lua [[ + Thread_Test = {} + + Thread_Test.entry_func = function(async, entry_str, args) + local decoded_args = vim.mpack.decode(args) + assert(loadstring(entry_str))(async, decoded_args) + end + + function Thread_Test:do_test() + local async + local on_async = self.on_async + async = vim.loop.new_async(function(ret) + on_async(ret) + async:close() + end) + local thread = + vim.loop.new_thread(self.entry_func, async, self.entry_str, self.args) + vim.loop.thread_join(thread) + end + + Thread_Test.new = function(entry, on_async, ...) + self = {} + setmetatable(self, {__index = Thread_Test}) + self.args = vim.mpack.encode({...}) + self.entry_str = string.dump(entry) + self.on_async = on_async + return self + end + ]] + end) + + it('is_thread', function() + exec_lua [[ + local entry = function(async) + async:send(vim.is_thread()) + end + local on_async = function(ret) + vim.rpcnotify(1, 'result', ret) + end + local thread_test = Thread_Test.new(entry, on_async) + thread_test:do_test() + ]] + + eq({'notification', 'result', {true}}, next_msg()) + end) + + it('loop', function() + exec_lua [[ + local entry = function(async) + async:send(vim.loop.version()) + end + local on_async = function(ret) + vim.rpcnotify(1, ret) + end + local thread_test = Thread_Test.new(entry, on_async) + thread_test:do_test() + ]] + + local msg = next_msg() + eq(msg[1], 'notification') + assert(tonumber(msg[2]) >= 72961) + end) + + it('mpack', function() + exec_lua [[ + local entry = function(async) + async:send(vim.mpack.encode({33, vim.NIL, 'text'})) + end + local on_async = function(ret) + vim.rpcnotify(1, 'result', vim.mpack.decode(ret)) + end + local thread_test = Thread_Test.new(entry, on_async) + thread_test:do_test() + ]] + + eq({'notification', 'result', {{33, NIL, 'text'}}}, next_msg()) + end) + + it('json', function() + exec_lua [[ + local entry = function(async) + async:send(vim.json.encode({33, vim.NIL, 'text'})) + end + local on_async = function(ret) + vim.rpcnotify(1, 'result', vim.json.decode(ret)) + end + local thread_test = Thread_Test.new(entry, on_async) + thread_test:do_test() + ]] + + eq({'notification', 'result', {{33, NIL, 'text'}}}, next_msg()) + end) + + it('diff', function() + exec_lua [[ + local entry = function(async) + async:send(vim.diff('Hello\n', 'Helli\n')) + end + local on_async = function(ret) + vim.rpcnotify(1, 'result', ret) + end + local thread_test = Thread_Test.new(entry, on_async) + thread_test:do_test() + ]] + + eq({'notification', 'result', + {table.concat({ + '@@ -1 +1 @@', + '-Hello', + '+Helli', + '' + }, '\n')}}, + next_msg()) + end) + end) +end) + +describe('threadpool', function() + before_each(clear) + + it('is_thread', function() + eq(false, exec_lua [[return vim.is_thread()]]) + + exec_lua [[ + local work_fn = function() + return vim.is_thread() + end + local after_work_fn = function(ret) + vim.rpcnotify(1, 'result', ret) + end + local work = vim.loop.new_work(work_fn, after_work_fn) + work:queue() + ]] + + eq({'notification', 'result', {true}}, next_msg()) + end) + + it('with invalid argument', function() + local status = pcall_err(exec_lua, [[ + local work = vim.loop.new_thread(function() end, function() end) + work:queue({}) + ]]) + + eq([[Error executing lua: [string "<nvim>"]:0: Error: thread arg not support type 'function' at 1]], + status) + end) + + it('with invalid return value', function() + local screen = Screen.new(50, 10) + screen:attach() + screen:set_default_attr_ids({ + [1] = {bold = true, foreground = Screen.colors.Blue1}, + [2] = {bold = true, reverse = true}, + [3] = {foreground = Screen.colors.Grey100, background = Screen.colors.Red}, + [4] = {bold = true, foreground = Screen.colors.SeaGreen4}, + [5] = {bold = true}, + }) + + exec_lua [[ + local work = vim.loop.new_work(function() return {} end, function() end) + work:queue() + ]] + + screen:expect([[ + | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {2: }| + {3:Error in luv thread:} | + {3:Error: thread arg not support type 'table' at 1} | + {4:Press ENTER or type command to continue}^ | + ]]) + end) + + describe('vim.*', function() + before_each(function() + clear() + exec_lua [[ + Threadpool_Test = {} + + Threadpool_Test.work_fn = function(work_fn_str, args) + local decoded_args = vim.mpack.decode(args) + return assert(loadstring(work_fn_str))(decoded_args) + end + + function Threadpool_Test:do_test() + local work = + vim.loop.new_work(self.work_fn, self.after_work) + work:queue(self.work_fn_str, self.args) + end + + Threadpool_Test.new = function(work_fn, after_work, ...) + self = {} + setmetatable(self, {__index = Threadpool_Test}) + self.args = vim.mpack.encode({...}) + self.work_fn_str = string.dump(work_fn) + self.after_work = after_work + return self + end + ]] + end) + + it('loop', function() + exec_lua [[ + local work_fn = function() + return vim.loop.version() + end + local after_work_fn = function(ret) + vim.rpcnotify(1, ret) + end + local threadpool_test = Threadpool_Test.new(work_fn, after_work_fn) + threadpool_test:do_test() + ]] + + local msg = next_msg() + eq(msg[1], 'notification') + assert(tonumber(msg[2]) >= 72961) + end) + + it('mpack', function() + exec_lua [[ + local work_fn = function() + local var = vim.mpack.encode({33, vim.NIL, 'text'}) + return var + end + local after_work_fn = function(ret) + vim.rpcnotify(1, 'result', vim.mpack.decode(ret)) + end + local threadpool_test = Threadpool_Test.new(work_fn, after_work_fn) + threadpool_test:do_test() + ]] + + eq({'notification', 'result', {{33, NIL, 'text'}}}, next_msg()) + end) + + it('json', function() + exec_lua [[ + local work_fn = function() + local var = vim.json.encode({33, vim.NIL, 'text'}) + return var + end + local after_work_fn = function(ret) + vim.rpcnotify(1, 'result', vim.json.decode(ret)) + end + local threadpool_test = Threadpool_Test.new(work_fn, after_work_fn) + threadpool_test:do_test() + ]] + + eq({'notification', 'result', {{33, NIL, 'text'}}}, next_msg()) + end) + + it('work', function() + exec_lua [[ + local work_fn = function() + return vim.diff('Hello\n', 'Helli\n') + end + local after_work_fn = function(ret) + vim.rpcnotify(1, 'result', ret) + end + local threadpool_test = Threadpool_Test.new(work_fn, after_work_fn) + threadpool_test:do_test() + ]] + + eq({'notification', 'result', + {table.concat({ + '@@ -1 +1 @@', + '-Hello', + '+Helli', + '' + }, '\n')}}, + next_msg()) + end) + end) +end) diff --git a/test/functional/lua/uri_spec.lua b/test/functional/lua/uri_spec.lua index fa11fdf794..4635f17557 100644 --- a/test/functional/lua/uri_spec.lua +++ b/test/functional/lua/uri_spec.lua @@ -101,7 +101,7 @@ describe('URI methods', function() eq('C:\\Foo\\Bar\\Baz.txt', exec_lua(test_case)) end) - it('file path includes only ascii charactors with encoded colon character', function() + it('file path includes only ascii characters with encoded colon character', function() local test_case = [[ local uri = 'file:///C%3A/Foo/Bar/Baz.txt' return vim.uri_to_fname(uri) diff --git a/test/functional/lua/vim_spec.lua b/test/functional/lua/vim_spec.lua index 317f92fcdc..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) @@ -906,6 +909,7 @@ describe('lua stdlib', function() exec_lua("vim.validate{arg1={nil, 'thread', true }}") exec_lua("vim.validate{arg1={{}, 't' }, arg2={ 'foo', 's' }}") exec_lua("vim.validate{arg1={2, function(a) return (a % 2) == 0 end, 'even number' }}") + exec_lua("vim.validate{arg1={5, {'n', 's'} }, arg2={ 'foo', {'n', 's'} }}") matches('expected table, got number', pcall_err(exec_lua, "vim.validate{ 1, 'x' }")) @@ -935,6 +939,8 @@ describe('lua stdlib', function() pcall_err(exec_lua, "vim.validate{arg1={3, function(a) return a == 1 end, 'even number'}}")) matches('arg1: expected %?, got 3', pcall_err(exec_lua, "vim.validate{arg1={3, function(a) return a == 1 end}}")) + matches('arg1: expected number|string, got nil', + pcall_err(exec_lua, "vim.validate{ arg1={ nil, {'n', 's'} }}")) -- Pass an additional message back. matches('arg1: expected %?, got 3. Info: TEST_MSG', @@ -984,6 +990,52 @@ describe('lua stdlib', function() matches([[attempt to index .* nil value]], pcall_err(exec_lua, 'return vim.g[0].testing')) + + exec_lua [[ + local counter = 0 + vim.g.AddCounter = function() counter = counter + 1 end + vim.g.GetCounter = function() return counter end + ]] + + eq(0, eval('g:GetCounter()')) + eval('g:AddCounter()') + eq(1, eval('g:GetCounter()')) + eval('g:AddCounter()') + eq(2, eval('g:GetCounter()')) + exec_lua([[vim.g.AddCounter()]]) + 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')()]])) + + exec_lua [[ + local counter = 0 + vim.api.nvim_set_var('AddCounter', function() counter = counter + 1 end) + vim.api.nvim_set_var('GetCounter', function() return counter end) + ]] + + eq(0, eval('g:GetCounter()')) + eval('g:AddCounter()') + eq(1, eval('g:GetCounter()')) + eval('g:AddCounter()') + eq(2, eval('g:GetCounter()')) + exec_lua([[vim.g.AddCounter()]]) + 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() @@ -1019,6 +1071,38 @@ describe('lua stdlib', function() eq(NIL, funcs.luaeval "vim.b.to_delete") exec_lua [[ + local counter = 0 + vim.b.AddCounter = function() counter = counter + 1 end + vim.b.GetCounter = function() return counter end + ]] + + eq(0, eval('b:GetCounter()')) + eval('b:AddCounter()') + eq(1, eval('b:GetCounter()')) + eval('b:AddCounter()') + eq(2, eval('b:GetCounter()')) + exec_lua([[vim.b.AddCounter()]]) + eq(3, exec_lua([[return vim.b.GetCounter()]])) + exec_lua([[vim.api.nvim_buf_get_var(0, 'AddCounter')()]]) + eq(4, exec_lua([[return vim.api.nvim_buf_get_var(0, 'GetCounter')()]])) + + exec_lua [[ + local counter = 0 + vim.api.nvim_buf_set_var(0, 'AddCounter', function() counter = counter + 1 end) + vim.api.nvim_buf_set_var(0, 'GetCounter', function() return counter end) + ]] + + eq(0, eval('b:GetCounter()')) + eval('b:AddCounter()') + eq(1, eval('b:GetCounter()')) + eval('b:AddCounter()') + eq(2, eval('b:GetCounter()')) + exec_lua([[vim.b.AddCounter()]]) + eq(3, exec_lua([[return vim.b.GetCounter()]])) + exec_lua([[vim.api.nvim_buf_get_var(0, 'AddCounter')()]]) + eq(4, exec_lua([[return vim.api.nvim_buf_get_var(0, 'GetCounter')()]])) + + exec_lua [[ vim.cmd "vnew" ]] @@ -1056,6 +1140,38 @@ describe('lua stdlib', function() eq(NIL, funcs.luaeval "vim.w.to_delete") exec_lua [[ + local counter = 0 + vim.w.AddCounter = function() counter = counter + 1 end + vim.w.GetCounter = function() return counter end + ]] + + eq(0, eval('w:GetCounter()')) + eval('w:AddCounter()') + eq(1, eval('w:GetCounter()')) + eval('w:AddCounter()') + eq(2, eval('w:GetCounter()')) + exec_lua([[vim.w.AddCounter()]]) + eq(3, exec_lua([[return vim.w.GetCounter()]])) + exec_lua([[vim.api.nvim_win_get_var(0, 'AddCounter')()]]) + eq(4, exec_lua([[return vim.api.nvim_win_get_var(0, 'GetCounter')()]])) + + exec_lua [[ + local counter = 0 + vim.api.nvim_win_set_var(0, 'AddCounter', function() counter = counter + 1 end) + vim.api.nvim_win_set_var(0, 'GetCounter', function() return counter end) + ]] + + eq(0, eval('w:GetCounter()')) + eval('w:AddCounter()') + eq(1, eval('w:GetCounter()')) + eval('w:AddCounter()') + eq(2, eval('w:GetCounter()')) + exec_lua([[vim.w.AddCounter()]]) + eq(3, exec_lua([[return vim.w.GetCounter()]])) + exec_lua([[vim.api.nvim_win_get_var(0, 'AddCounter')()]]) + eq(4, exec_lua([[return vim.api.nvim_win_get_var(0, 'GetCounter')()]])) + + exec_lua [[ vim.cmd "vnew" ]] @@ -1088,6 +1204,38 @@ describe('lua stdlib', function() eq(NIL, funcs.luaeval "vim.t.to_delete") exec_lua [[ + local counter = 0 + vim.t.AddCounter = function() counter = counter + 1 end + vim.t.GetCounter = function() return counter end + ]] + + eq(0, eval('t:GetCounter()')) + eval('t:AddCounter()') + eq(1, eval('t:GetCounter()')) + eval('t:AddCounter()') + eq(2, eval('t:GetCounter()')) + exec_lua([[vim.t.AddCounter()]]) + eq(3, exec_lua([[return vim.t.GetCounter()]])) + exec_lua([[vim.api.nvim_tabpage_get_var(0, 'AddCounter')()]]) + eq(4, exec_lua([[return vim.api.nvim_tabpage_get_var(0, 'GetCounter')()]])) + + exec_lua [[ + local counter = 0 + vim.api.nvim_tabpage_set_var(0, 'AddCounter', function() counter = counter + 1 end) + vim.api.nvim_tabpage_set_var(0, 'GetCounter', function() return counter end) + ]] + + eq(0, eval('t:GetCounter()')) + eval('t:AddCounter()') + eq(1, eval('t:GetCounter()')) + eval('t:AddCounter()') + eq(2, eval('t:GetCounter()')) + exec_lua([[vim.t.AddCounter()]]) + eq(3, exec_lua([[return vim.t.GetCounter()]])) + exec_lua([[vim.api.nvim_tabpage_get_var(0, 'AddCounter')()]]) + eq(4, exec_lua([[return vim.api.nvim_tabpage_get_var(0, 'GetCounter')()]])) + + exec_lua [[ vim.cmd "tabnew" ]] @@ -2200,6 +2348,40 @@ describe('lua stdlib', function() end) end) + it('vim.notify_once', function() + local screen = Screen.new(60,5) + screen:set_default_attr_ids({ + [0] = {bold=true, foreground=Screen.colors.Blue}, + [1] = {foreground=Screen.colors.Red}, + }) + screen:attach() + screen:expect{grid=[[ + ^ | + {0:~ }| + {0:~ }| + {0:~ }| + | + ]]} + exec_lua [[vim.notify_once("I'll only tell you this once...", vim.log.levels.WARN)]] + screen:expect{grid=[[ + ^ | + {0:~ }| + {0:~ }| + {0:~ }| + {1:I'll only tell you this once...} | + ]]} + feed('<C-l>') + screen:expect{grid=[[ + ^ | + {0:~ }| + {0:~ }| + {0:~ }| + | + ]]} + exec_lua [[vim.notify_once("I'll only tell you this once...")]] + screen:expect_unchanged() + end) + describe('vim.schedule_wrap', function() it('preserves argument lists', function() exec_lua [[ @@ -2241,6 +2423,17 @@ describe('lua stdlib', function() eq(buf1, meths.get_current_buf()) eq(buf2, val) end) + + it('does not cause ml_get errors with invalid visual selection', function() + -- Should be fixed by vim-patch:8.2.4028. + exec_lua [[ + local a = vim.api + local t = function(s) return a.nvim_replace_termcodes(s, true, true, true) end + a.nvim_buf_set_lines(0, 0, -1, true, {"a", "b", "c"}) + a.nvim_feedkeys(t "G<C-V>", "txn", false) + a.nvim_buf_call(a.nvim_create_buf(false, true), function() vim.cmd "redraw" end) + ]] + end) end) describe('vim.api.nvim_win_call', function() @@ -2269,6 +2462,75 @@ describe('lua stdlib', function() eq(win1, meths.get_current_win()) eq(win2, val) end) + + it('does not cause ml_get errors with invalid visual selection', function() + -- Add lines to the current buffer and make another window looking into an empty buffer. + exec_lua [[ + _G.a = vim.api + _G.t = function(s) return a.nvim_replace_termcodes(s, true, true, true) end + _G.win_lines = a.nvim_get_current_win() + vim.cmd "new" + _G.win_empty = a.nvim_get_current_win() + a.nvim_set_current_win(win_lines) + a.nvim_buf_set_lines(0, 0, -1, true, {"a", "b", "c"}) + ]] + + -- Start Visual in current window, redraw in other window with fewer lines. + -- Should be fixed by vim-patch:8.2.4018. + exec_lua [[ + a.nvim_feedkeys(t "G<C-V>", "txn", false) + a.nvim_win_call(win_empty, function() vim.cmd "redraw" end) + ]] + + -- Start Visual in current window, extend it in other window with more lines. + -- Fixed for win_execute by vim-patch:8.2.4026, but nvim_win_call should also not be affected. + exec_lua [[ + a.nvim_feedkeys(t "<Esc>gg", "txn", false) + a.nvim_set_current_win(win_empty) + a.nvim_feedkeys(t "gg<C-V>", "txn", false) + a.nvim_win_call(win_lines, function() a.nvim_feedkeys(t "G<C-V>", "txn", false) end) + vim.cmd "redraw" + ]] + end) + + it('updates ruler if cursor moved', function() + -- Fixed for win_execute in vim-patch:8.1.2124, but should've applied to nvim_win_call too! + local screen = Screen.new(30, 5) + screen:set_default_attr_ids { + [1] = {reverse = true}, + [2] = {bold = true, reverse = true}, + } + screen:attach() + exec_lua [[ + _G.a = vim.api + vim.opt.ruler = true + local lines = {} + for i = 0, 499 do lines[#lines + 1] = tostring(i) end + a.nvim_buf_set_lines(0, 0, -1, true, lines) + a.nvim_win_set_cursor(0, {20, 0}) + vim.cmd "split" + _G.win = a.nvim_get_current_win() + vim.cmd "wincmd w | redraw" + ]] + screen:expect [[ + 19 | + {1:[No Name] [+] 20,1 3%}| + ^19 | + {2:[No Name] [+] 20,1 3%}| + | + ]] + exec_lua [[ + a.nvim_win_call(win, function() a.nvim_win_set_cursor(0, {100, 0}) end) + vim.cmd "redraw" + ]] + screen:expect [[ + 99 | + {1:[No Name] [+] 100,1 19%}| + ^19 | + {2:[No Name] [+] 20,1 3%}| + | + ]] + end) end) end) @@ -2295,3 +2557,82 @@ describe('lua: require("mod") from packages', function() eq('I am fancy_z.lua', exec_lua [[ return require'fancy_z' ]]) end) end) + +describe('vim.keymap', function() + it('can make a mapping', function() + eq(0, exec_lua [[ + GlobalCount = 0 + vim.keymap.set('n', 'asdf', function() GlobalCount = GlobalCount + 1 end) + return GlobalCount + ]]) + + feed('asdf\n') + + eq(1, exec_lua[[return GlobalCount]]) + end) + + it('can make an expr mapping', function() + exec_lua [[ + vim.keymap.set('n', 'aa', function() return ':lua SomeValue = 99<cr>' end, {expr = true}) + ]] + + feed('aa') + + eq(99, exec_lua[[return SomeValue]]) + end) + + it('can overwrite a mapping', function() + eq(0, exec_lua [[ + GlobalCount = 0 + vim.keymap.set('n', 'asdf', function() GlobalCount = GlobalCount + 1 end) + return GlobalCount + ]]) + + feed('asdf\n') + + eq(1, exec_lua[[return GlobalCount]]) + + exec_lua [[ + vim.keymap.set('n', 'asdf', function() GlobalCount = GlobalCount - 1 end) + ]] + + feed('asdf\n') + + eq(0, exec_lua[[return GlobalCount]]) + end) + + it('can unmap a mapping', function() + eq(0, exec_lua [[ + GlobalCount = 0 + vim.keymap.set('n', 'asdf', function() GlobalCount = GlobalCount + 1 end) + return GlobalCount + ]]) + + feed('asdf\n') + + eq(1, exec_lua[[return GlobalCount]]) + + exec_lua [[ + vim.keymap.del('n', 'asdf') + ]] + + feed('asdf\n') + + eq(1, exec_lua[[return GlobalCount]]) + eq('\nNo mapping found', helpers.exec_capture('nmap asdf')) + end) + + it('can do <Plug> mappings', function() + eq(0, exec_lua [[ + GlobalCount = 0 + vim.keymap.set('n', '<plug>(asdf)', function() GlobalCount = GlobalCount + 1 end) + vim.keymap.set('n', 'ww', '<plug>(asdf)') + return GlobalCount + ]]) + + feed('ww\n') + + eq(1, exec_lua[[return GlobalCount]]) + end) + +end) diff --git a/test/functional/lua/xdiff_spec.lua b/test/functional/lua/xdiff_spec.lua index 4f28f84c01..d55268fc78 100644 --- a/test/functional/lua/xdiff_spec.lua +++ b/test/functional/lua/xdiff_spec.lua @@ -90,6 +90,48 @@ describe('xdiff bindings', function() exec_lua([[return vim.diff(a2, b2, {result_type = 'indices'})]])) end) + it('can run different algorithms', function() + local a = table.concat({ + '.foo1 {', + ' margin: 0;', + '}', + '', + '.bar {', + ' margin: 0;', + '}', + ''}, '\n') + + local b = table.concat({ + '.bar {', + ' margin: 0;', + '}', + '', + '.foo1 {', + ' margin: 0;', + ' color: green;', + '}', + ''}, '\n') + + eq( + table.concat({'@@ -1,4 +0,0 @@', + '-.foo1 {', + '- margin: 0;', + '-}', + '-', + '@@ -7,0 +4,5 @@', + '+', + '+.foo1 {', + '+ margin: 0;', + '+ color: green;', + '+}', + ''}, '\n'), + exec_lua([[ + local args = {...} + return vim.diff(args[1], args[2], { + algorithm = 'patience' + }) + ]], a, b)) + end) end) it('can handle bad args', function() diff --git a/test/functional/plugin/health_spec.lua b/test/functional/plugin/health_spec.lua index 37de5d0ce6..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) @@ -230,3 +244,14 @@ describe('health.vim', function() end) end) end) + +describe(':checkhealth provider', function() + it("works correctly with a wrongly configured 'shell'", function() + clear() + command([[set shell=echo\ WRONG!!!]]) + command('let g:loaded_perl_provider = 0') + command('let g:loaded_python3_provider = 0') + command('checkhealth provider') + eq(nil, string.match(curbuf_contents(), 'WRONG!!!')) + end) +end) diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index 1af31c38f8..eab520948f 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -73,8 +73,11 @@ local function fake_lsp_server_setup(test_name, timeout_ms, options) on_init = function(client, result) TEST_RPC_CLIENT = client vim.rpcrequest(1, "init", result) - client.config.flags.allow_incremental_sync = options.allow_incremental_sync or false end; + flags = { + allow_incremental_sync = options.allow_incremental_sync or false; + debounce_text_changes = options.debounce_text_changes or 0; + }; on_exit = function(...) vim.rpcnotify(1, "exit", ...) end; @@ -926,7 +929,60 @@ describe('LSP', function() local client test_rpc_server { test_name = "basic_check_buffer_open_and_change_incremental"; - options = { allow_incremental_sync = true }; + options = { + allow_incremental_sync = true, + }; + on_setup = function() + exec_lua [[ + BUFFER = vim.api.nvim_create_buf(false, true) + vim.api.nvim_buf_set_lines(BUFFER, 0, -1, false, { + "testing"; + "123"; + }) + ]] + end; + on_init = function(_client) + client = _client + local sync_kind = exec_lua("return require'vim.lsp.protocol'.TextDocumentSyncKind.Incremental") + eq(sync_kind, client.resolved_capabilities().text_document_did_change) + eq(true, client.resolved_capabilities().text_document_open_close) + exec_lua [[ + assert(lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID)) + ]] + end; + on_exit = function(code, signal) + eq(0, code, "exit code", fake_lsp_logfile) + eq(0, signal, "exit signal", fake_lsp_logfile) + end; + on_handler = function(err, result, ctx) + if ctx.method == 'start' then + exec_lua [[ + vim.api.nvim_buf_set_lines(BUFFER, 1, 2, false, { + "123boop"; + }) + ]] + client.notify('finish') + end + eq(table.remove(expected_handlers), {err, result, ctx}, "expected handler") + if ctx.method == 'finish' then + client.stop() + end + end; + } + end) + it('should check the body and didChange incremental with debounce', function() + local expected_handlers = { + {NIL, {}, {method="shutdown", client_id=1}}; + {NIL, {}, {method="finish", client_id=1}}; + {NIL, {}, {method="start", client_id=1}}; + } + local client + test_rpc_server { + test_name = "basic_check_buffer_open_and_change_incremental"; + options = { + allow_incremental_sync = true, + debounce_text_changes = 5 + }; on_setup = function() exec_lua [[ BUFFER = vim.api.nvim_create_buf(false, true) @@ -1235,7 +1291,7 @@ describe('LSP', function() make_edit(2, 0, 2, 2, {"3"}); make_edit(3, 2, 3, 4, {""}); } - exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1) + exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1, "utf-16") eq({ '123First line of text'; '2econd line of text'; @@ -1255,7 +1311,7 @@ describe('LSP', function() make_edit(3, #'', 3, #"Fourth", {"another line of text", "before this"}); make_edit(3, #'Fourth', 3, #"Fourth line of text", {"!"}); } - exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1) + exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1, "utf-16") eq({ ''; '123'; @@ -1279,7 +1335,7 @@ describe('LSP', function() make_edit(3, #"Fourth", 3, #'', {"another line of text", "before this"}); make_edit(3, #"Fourth line of text", 3, #'Fourth', {"!"}); } - exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1) + exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1, "utf-16") eq({ ''; '123'; @@ -1296,7 +1352,7 @@ describe('LSP', function() local edits = { make_edit(4, 3, 4, 4, {"ä"}); } - exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1) + exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1, "utf-16") eq({ 'First line of text'; 'Second line of text'; @@ -1309,7 +1365,7 @@ describe('LSP', function() local edits = { make_edit(5, 0, 5, 0, "foobar"); } - exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1) + exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1, "utf-16") eq({ 'First line of text'; 'Second line of text'; @@ -1319,6 +1375,20 @@ describe('LSP', function() 'foobar'; }, buf_lines(1)) end) + it('applies multiple text edits at the end of the document', function() + local edits = { + make_edit(4, 0, 5, 0, ""); + make_edit(5, 0, 5, 0, "foobar"); + } + exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1, "utf-16") + eq({ + 'First line of text'; + 'Second line of text'; + 'Third line of text'; + 'Fourth line of text'; + 'foobar'; + }, buf_lines(1)) + end) describe('cursor position', function() it('don\'t fix the cursor if the range contains the cursor', function() @@ -1326,7 +1396,7 @@ describe('LSP', function() local edits = { make_edit(1, 0, 1, 19, 'Second line of text') } - exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1) + exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1, "utf-16") eq({ 'First line of text'; 'Second line of text'; @@ -1343,7 +1413,7 @@ describe('LSP', function() make_edit(1, 0, 1, 6, ''), make_edit(1, 6, 1, 19, '') } - exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1) + exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1, "utf-16") eq({ 'First line of text'; ''; @@ -1360,7 +1430,7 @@ describe('LSP', function() make_edit(1, 0, 1, 6, ''), make_edit(0, 18, 5, 0, '') } - exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1) + exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1, "utf-16") eq({ 'First line of text'; }, buf_lines(1)) @@ -1372,7 +1442,7 @@ describe('LSP', function() local edits = { make_edit(1, 0, 2, 0, '') } - exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1) + exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1, "utf-16") eq({ 'First line of text'; 'Third line of text'; @@ -1387,7 +1457,7 @@ describe('LSP', function() local edits = { make_edit(1, 7, 1, 11, '') } - exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1) + exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1, "utf-16") eq({ 'First line of text'; 'Second of text'; @@ -1403,7 +1473,7 @@ describe('LSP', function() local edits = { make_edit(0, 11, 1, 12, '') } - exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1) + exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1, "utf-16") eq({ 'First line of text'; 'Third line of text'; @@ -1419,21 +1489,21 @@ describe('LSP', function() local edits = { make_edit(0, 0, 5, 0, {"All replaced"}); } - exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1) + exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1, "utf-16") eq({'All replaced'}, buf_lines(1)) end) it('applies edits when the end line is 2 larger than vim\'s', function() local edits = { make_edit(0, 0, 6, 0, {"All replaced"}); } - exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1) + exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1, "utf-16") eq({'All replaced'}, buf_lines(1)) end) it('applies edits with a column offset', function() local edits = { make_edit(0, 0, 5, 2, {"All replaced"}); } - exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1) + exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1, "utf-16") eq({'All replaced'}, buf_lines(1)) end) end) @@ -1461,7 +1531,7 @@ describe('LSP', function() ]] end) it('correctly goes ahead with the edit if all is normal', function() - exec_lua('vim.lsp.util.apply_text_document_edit(...)', text_document_edit(5)) + exec_lua("vim.lsp.util.apply_text_document_edit(..., nil, 'utf-16')", text_document_edit(5)) eq({ 'First ↥ 🤦 🦄 line of text'; '2nd line of 语text'; @@ -1473,7 +1543,7 @@ describe('LSP', function() local bufnr = select(1, ...) local text_edit = select(2, ...) vim.lsp.util.buf_versions[bufnr] = 10 - vim.lsp.util.apply_text_document_edit(text_edit) + vim.lsp.util.apply_text_document_edit(text_edit, nil, 'utf-16') ]], target_bufnr, text_document_edit(0)) eq({ 'First ↥ 🤦 🦄 line of text'; @@ -1486,7 +1556,7 @@ describe('LSP', function() local args = {...} local versionedBuf = args[2] vim.lsp.util.buf_versions[versionedBuf.bufnr] = versionedBuf.currentVersion - vim.lsp.util.apply_text_document_edit(args[1]) + vim.lsp.util.apply_text_document_edit(args[1], nil, 'utf-16') ]], edit, versionedBuf) end @@ -1512,17 +1582,36 @@ describe('LSP', function() describe('workspace_apply_edit', function() it('workspace/applyEdit returns ApplyWorkspaceEditResponse', function() - local expected = { - applied = true; - failureReason = nil; + local expected_handlers = { + {NIL, {}, {method="test", client_id=1}}; + } + test_rpc_server { + test_name = "basic_init"; + on_init = function(client, _) + client.stop() + end; + -- If the program timed out, then code will be nil. + on_exit = function(code, signal) + eq(0, code, "exit code", fake_lsp_logfile) + eq(0, signal, "exit signal", fake_lsp_logfile) + end; + -- Note that NIL must be used here. + -- on_handler(err, method, result, client_id) + on_handler = function(...) + local expected = { + applied = true; + failureReason = nil; + } + eq(expected, exec_lua [[ + local apply_edit = { + label = nil; + edit = {}; + } + return vim.lsp.handlers['workspace/applyEdit'](nil, apply_edit, {client_id = TEST_RPC_CLIENT_ID}) + ]]) + eq(table.remove(expected_handlers), {...}) + end; } - eq(expected, exec_lua [[ - local apply_edit = { - label = nil; - edit = {}; - } - return vim.lsp.handlers['workspace/applyEdit'](nil, apply_edit) - ]]) end) end) @@ -1596,7 +1685,7 @@ describe('LSP', function() local workspace_edits = args[1] local target_bufnr = args[2] - vim.lsp.util.apply_workspace_edit(workspace_edits) + vim.lsp.util.apply_workspace_edit(workspace_edits, 'utf-16') return vim.api.nvim_buf_get_lines(target_bufnr, 0, -1, false) ]], make_workspace_edit(edits), target_bufnr)) @@ -1618,7 +1707,7 @@ describe('LSP', function() local workspace_edits = args[1] local target_bufnr = args[2] - vim.lsp.util.apply_workspace_edit(workspace_edits) + vim.lsp.util.apply_workspace_edit(workspace_edits, 'utf-16') return vim.api.nvim_buf_get_lines(target_bufnr, 0, -1, false) ]], make_workspace_edit(edits), target_bufnr)) @@ -1635,7 +1724,7 @@ describe('LSP', function() }, } } - exec_lua('vim.lsp.util.apply_workspace_edit(...)', edit) + exec_lua('vim.lsp.util.apply_workspace_edit(...)', edit, 'utf-16') eq(true, exec_lua('return vim.loop.fs_stat(...) ~= nil', tmpfile)) end) it('createFile does not touch file if it exists and ignoreIfExists is set', function() @@ -1653,7 +1742,7 @@ describe('LSP', function() }, } } - exec_lua('vim.lsp.util.apply_workspace_edit(...)', edit) + exec_lua('vim.lsp.util.apply_workspace_edit(...)', edit, 'utf-16') eq(true, exec_lua('return vim.loop.fs_stat(...) ~= nil', tmpfile)) eq('Dummy content', read_file(tmpfile)) end) @@ -1673,7 +1762,7 @@ describe('LSP', function() }, } } - exec_lua('vim.lsp.util.apply_workspace_edit(...)', edit) + exec_lua('vim.lsp.util.apply_workspace_edit(...)', edit, 'utf-16') eq(true, exec_lua('return vim.loop.fs_stat(...) ~= nil', tmpfile)) eq('', read_file(tmpfile)) end) @@ -1694,7 +1783,7 @@ describe('LSP', function() } } } - eq(true, pcall(exec_lua, 'vim.lsp.util.apply_workspace_edit(...)', edit)) + eq(true, pcall(exec_lua, 'vim.lsp.util.apply_workspace_edit(...)', edit, 'utf-16')) eq(false, exec_lua('return vim.loop.fs_stat(...) ~= nil', tmpfile)) eq(false, exec_lua('return vim.api.nvim_buf_is_loaded(vim.fn.bufadd(...))', tmpfile)) end) @@ -1855,7 +1944,7 @@ describe('LSP', function() } }, } - return vim.lsp.util.locations_to_items(locations) + return vim.lsp.util.locations_to_items(locations, 'utf-16') ]] eq(expected, actual) end) @@ -1885,7 +1974,7 @@ describe('LSP', function() } }, } - return vim.lsp.util.locations_to_items(locations) + return vim.lsp.util.locations_to_items(locations, 'utf-16') ]] eq(expected, actual) end) @@ -2189,7 +2278,7 @@ describe('LSP', function() end local jump = function(msg) - eq(true, exec_lua('return vim.lsp.util.jump_to_location(...)', msg)) + eq(true, exec_lua('return vim.lsp.util.jump_to_location(...)', msg, "utf-16")) eq(target_bufnr, exec_lua[[return vim.fn.bufnr('%')]]) return { line = exec_lua[[return vim.fn.line('.')]], @@ -2263,6 +2352,27 @@ describe('LSP', function() end) end) + describe('lsp.util.convert_signature_help_to_markdown_lines', function() + it('can handle negative activeSignature', function() + local result = exec_lua[[ + local signature_help = { + activeParameter = 0, + activeSignature = -1, + signatures = { + { + documentation = "", + label = "TestEntity.TestEntity()", + parameters = {} + }, + } + } + return vim.lsp.util.convert_signature_help_to_markdown_lines(signature_help, 'cs', {','}) + ]] + local expected = {'```cs', 'TestEntity.TestEntity()', '```', ''} + eq(expected, result) + end) + end) + describe('lsp.util.get_effective_tabstop', function() local function test_tabstop(tabsize, softtabstop) exec_lua(string.format([[ diff --git a/test/functional/plugin/shada_spec.lua b/test/functional/plugin/shada_spec.lua index a4d78682ad..6f22f865e6 100644 --- a/test/functional/plugin/shada_spec.lua +++ b/test/functional/plugin/shada_spec.lua @@ -1,7 +1,7 @@ local helpers = require('test.functional.helpers')(after_each) local clear = helpers.clear -local eq, nvim_eval, nvim_command, nvim, exc_exec, funcs, nvim_feed, curbuf = - helpers.eq, helpers.eval, helpers.command, helpers.nvim, helpers.exc_exec, +local eq, meths, nvim_eval, nvim_command, nvim, exc_exec, funcs, nvim_feed, curbuf = + helpers.eq, helpers.meths, helpers.eval, helpers.command, helpers.nvim, helpers.exc_exec, helpers.funcs, helpers.feed, helpers.curbuf local neq = helpers.neq local read_file = helpers.read_file @@ -2162,6 +2162,10 @@ describe('plugin/shada.vim', function() wshada('\004\000\009\147\000\196\002ab\196\001a') wshada_tmp('\004\000\009\147\000\196\002ab\196\001b') + + local bufread_commands = meths.get_autocmds({ group = "ShaDaCommands", event = "BufReadCmd" }) + eq(2, #bufread_commands--[[, vim.inspect(bufread_commands) ]]) + -- Need to set nohidden so that the buffer containing 'fname' is not unloaded -- after loading 'fname_tmp', otherwise the '++opt not supported' test below -- won't work since the BufReadCmd autocmd won't be triggered. diff --git a/test/functional/provider/provider_spec.lua b/test/functional/provider/provider_spec.lua index 78bc4a4edb..3895b8613f 100644 --- a/test/functional/provider/provider_spec.lua +++ b/test/functional/provider/provider_spec.lua @@ -14,8 +14,8 @@ describe('providers', function() command('set loadplugins') -- Using test-fixture with broken impl: -- test/functional/fixtures/autoload/provider/python.vim - eq('Vim:provider: python: missing required variable g:loaded_python_provider', - pcall_err(eval, "has('python')")) + eq('Vim:provider: python3: missing required variable g:loaded_python3_provider', + pcall_err(eval, "has('python3')")) end) it('with g:loaded_xx_provider, missing #Call()', function() diff --git a/test/functional/provider/python3_spec.lua b/test/functional/provider/python3_spec.lua index d100db8de2..603f4d91b9 100644 --- a/test/functional/provider/python3_spec.lua +++ b/test/functional/provider/python3_spec.lua @@ -8,6 +8,7 @@ local source = helpers.source local missing_provider = helpers.missing_provider local matches = helpers.matches local pcall_err = helpers.pcall_err +local funcs = helpers.funcs do clear() @@ -93,16 +94,40 @@ describe('python3 provider', function() ghi]]) end) - it('py3eval', function() - eq({1, 2, {['key'] = 'val'}}, eval([[py3eval('[1, 2, {"key": "val"}]')]])) + describe('py3eval()', function() + it('works', function() + eq({1, 2, {['key'] = 'val'}}, funcs.py3eval('[1, 2, {"key": "val"}]')) + end) + + it('errors out when given non-string', function() + eq('Vim:E474: Invalid argument', pcall_err(eval, 'py3eval(10)')) + eq('Vim:E474: Invalid argument', pcall_err(eval, 'py3eval(v:_null_dict)')) + eq('Vim:E474: Invalid argument', pcall_err(eval, 'py3eval(v:_null_list)')) + eq('Vim:E474: Invalid argument', pcall_err(eval, 'py3eval(0.0)')) + eq('Vim:E474: Invalid argument', pcall_err(eval, 'py3eval(function("tr"))')) + eq('Vim:E474: Invalid argument', pcall_err(eval, 'py3eval(v:true)')) + eq('Vim:E474: Invalid argument', pcall_err(eval, 'py3eval(v:false)')) + eq('Vim:E474: Invalid argument', pcall_err(eval, 'py3eval(v:null)')) + end) + + it('accepts NULL string', function() + matches('.*SyntaxError.*', pcall_err(eval, 'py3eval($XXX_NONEXISTENT_VAR_XXX)')) + end) end) it('pyxeval #10758', function() - eq(0, eval([[&pyxversion]])) + eq(3, eval([[&pyxversion]])) eq(3, eval([[pyxeval('sys.version_info[:3][0]')]])) eq(3, eval([[&pyxversion]])) end) + it("setting 'pyxversion'", function() + command 'set pyxversion=3' -- no error + eq('Vim(set):E474: Invalid argument: pyxversion=2', pcall_err(command, 'set pyxversion=2')) + command 'set pyxversion=0' -- allowed, but equivalent to pyxversion=3 + eq(3, eval'&pyxversion') + end) + it('RPC call to expand("<afile>") during BufDelete #5245 #5617', function() helpers.add_builddir_to_rtp() source([=[ @@ -120,3 +145,15 @@ describe('python3 provider', function() assert_alive() end) end) + +describe('python2 feature test', function() + -- python2 is not supported, so correct behaviour is to return 0 + it('works', function() + eq(0, funcs.has('python2')) + eq(0, funcs.has('python')) + eq(0, funcs.has('python_compiled')) + eq(0, funcs.has('python_dynamic')) + eq(0, funcs.has('python_dynamic_')) + eq(0, funcs.has('python_')) + end) +end) diff --git a/test/functional/provider/python_spec.lua b/test/functional/provider/python_spec.lua deleted file mode 100644 index d60d8d1001..0000000000 --- a/test/functional/provider/python_spec.lua +++ /dev/null @@ -1,123 +0,0 @@ -local helpers = require('test.functional.helpers')(after_each) - -local eq = helpers.eq -local neq = helpers.neq -local feed = helpers.feed -local clear = helpers.clear -local funcs = helpers.funcs -local meths = helpers.meths -local insert = helpers.insert -local expect = helpers.expect -local command = helpers.command -local exc_exec = helpers.exc_exec -local write_file = helpers.write_file -local curbufmeths = helpers.curbufmeths -local missing_provider = helpers.missing_provider -local matches = helpers.matches -local pcall_err = helpers.pcall_err - -do - clear() - local reason = missing_provider('python') - if reason then - it(':python reports E319 if provider is missing', function() - local expected = [[Vim%(py.*%):E319: No "python" provider found.*]] - matches(expected, pcall_err(command, 'py print("foo")')) - matches(expected, pcall_err(command, 'pyfile foo')) - end) - pending(string.format('Python 2 (or the pynvim module) is broken/missing (%s)', reason), function() end) - return - end -end - -before_each(function() - clear() - command('python import vim') -end) - -describe('python feature test', function() - it('works', function() - eq(1, funcs.has('python')) - eq(1, funcs.has('python_compiled')) - eq(1, funcs.has('python_dynamic')) - eq(0, funcs.has('python_dynamic_')) - eq(0, funcs.has('python_')) - end) -end) - -describe(':python command', function() - it('works with a line', function() - command('python vim.vars["set_by_python"] = [100, 0]') - eq({100, 0}, meths.get_var('set_by_python')) - end) - - -- TODO(ZyX-I): works with << EOF - -- TODO(ZyX-I): works with execute 'python' line1."\n".line2."\n"… - - it('supports nesting', function() - command([[python vim.command('python vim.command("python vim.command(\'let set_by_nested_python = 555\')")')]]) - eq(555, meths.get_var('set_by_nested_python')) - end) - - it('supports range', function() - insert([[ - line1 - line2 - line3 - line4]]) - feed('ggjvj:python vim.vars["range"] = vim.current.range[:]<CR>') - eq({'line2', 'line3'}, meths.get_var('range')) - end) -end) - -describe(':pyfile command', function() - it('works', function() - local fname = 'pyfile.py' - write_file(fname, 'vim.command("let set_by_pyfile = 123")') - command('pyfile pyfile.py') - eq(123, meths.get_var('set_by_pyfile')) - os.remove(fname) - end) -end) - -describe(':pydo command', function() - it('works', function() - -- :pydo 42 returns None for all lines, - -- the buffer should not be changed - command('normal :pydo 42') - eq(false, curbufmeths.get_option('modified')) - -- insert some text - insert('abc\ndef\nghi') - expect([[ - abc - def - ghi]]) - -- go to top and select and replace the first two lines - feed('ggvj:pydo return str(linenr)<CR>') - expect([[ - 1 - 2 - ghi]]) - end) -end) - -describe('pyeval()', function() - it('works', function() - eq({1, 2, {['key'] = 'val'}}, funcs.pyeval('[1, 2, {"key": "val"}]')) - end) - - it('errors out when given non-string', function() - eq('Vim(call):E474: Invalid argument', exc_exec('call pyeval(10)')) - eq('Vim(call):E474: Invalid argument', exc_exec('call pyeval(v:_null_dict)')) - eq('Vim(call):E474: Invalid argument', exc_exec('call pyeval(v:_null_list)')) - eq('Vim(call):E474: Invalid argument', exc_exec('call pyeval(0.0)')) - eq('Vim(call):E474: Invalid argument', exc_exec('call pyeval(function("tr"))')) - eq('Vim(call):E474: Invalid argument', exc_exec('call pyeval(v:true)')) - eq('Vim(call):E474: Invalid argument', exc_exec('call pyeval(v:false)')) - eq('Vim(call):E474: Invalid argument', exc_exec('call pyeval(v:null)')) - end) - - it('accepts NULL string', function() - neq(0, exc_exec('call pyeval($XXX_NONEXISTENT_VAR_XXX)')) - end) -end) diff --git a/test/functional/terminal/buffer_spec.lua b/test/functional/terminal/buffer_spec.lua index f25cfa2039..1cef771f0d 100644 --- a/test/functional/terminal/buffer_spec.lua +++ b/test/functional/terminal/buffer_spec.lua @@ -258,6 +258,7 @@ describe(':terminal buffer', function() end) it('it works with set rightleft #11438', function() + if helpers.pending_win32(pending) then return end local columns = eval('&columns') feed(string.rep('a', columns)) command('set rightleft') @@ -292,10 +293,9 @@ describe(':terminal buffer', function() command('quit') end) - it('does not segfault when pasting empty buffer #13955', function() - feed_command('terminal') + it('does not segfault when pasting empty register #13955', function() feed('<c-\\><c-n>') - feed_command('put a') -- buffer a is empty + feed_command('put a') -- register a is empty helpers.assert_alive() end) end) @@ -350,7 +350,7 @@ describe('on_lines does not emit out-of-bounds line indexes when', function() end) it('creating a terminal buffer #16394', function() - feed_command([[autocmd TermOpen * ++once call v:lua.register_callback(expand("<abuf>"))]]) + feed_command('autocmd TermOpen * ++once call v:lua.register_callback(str2nr(expand("<abuf>")))') feed_command('terminal') sleep(500) eq('', exec_lua([[return _G.cb_error]])) diff --git a/test/functional/terminal/cursor_spec.lua b/test/functional/terminal/cursor_spec.lua index e9495f45a2..3b905f1f56 100644 --- a/test/functional/terminal/cursor_spec.lua +++ b/test/functional/terminal/cursor_spec.lua @@ -87,6 +87,7 @@ describe(':terminal cursor', function() describe('when invisible', function() it('is not highlighted and is detached from screen cursor', function() + if helpers.pending_win32(pending) then return end hide_cursor() screen:expect([[ tty ready | @@ -176,6 +177,7 @@ describe('cursor with customized highlighting', function() end) describe('buffer cursor position is correct in terminal without number column', function() + if helpers.pending_win32(pending) then return end local screen local function setup_ex_register(str) @@ -526,6 +528,7 @@ describe('buffer cursor position is correct in terminal without number column', end) describe('buffer cursor position is correct in terminal with number column', function() + if helpers.pending_win32(pending) then return end local screen local function setup_ex_register(str) diff --git a/test/functional/terminal/edit_spec.lua b/test/functional/terminal/edit_spec.lua index fabc5524ed..e7025d6739 100644 --- a/test/functional/terminal/edit_spec.lua +++ b/test/functional/terminal/edit_spec.lua @@ -36,6 +36,7 @@ describe(':edit term://*', function() end) it("runs TermOpen early enough to set buffer-local 'scrollback'", function() + if helpers.pending_win32(pending) then return end local columns, lines = 20, 4 local scr = get_screen(columns, lines) local rep = 97 diff --git a/test/functional/terminal/ex_terminal_spec.lua b/test/functional/terminal/ex_terminal_spec.lua index 065dd72485..b4f29a586a 100644 --- a/test/functional/terminal/ex_terminal_spec.lua +++ b/test/functional/terminal/ex_terminal_spec.lua @@ -142,6 +142,7 @@ describe(':terminal (with fake shell)', function() end it('with no argument, acts like termopen()', function() + if helpers.pending_win32(pending) then return end terminal_with_fake_shell() retry(nil, 4 * screen.timeout, function() screen:expect([[ @@ -165,6 +166,7 @@ describe(':terminal (with fake shell)', function() end) it("with no argument, but 'shell' has arguments, acts like termopen()", function() + if helpers.pending_win32(pending) then return end nvim('set_option', 'shell', nvim_dir..'/shell-test -t jeff') terminal_with_fake_shell() screen:expect([[ @@ -176,6 +178,7 @@ describe(':terminal (with fake shell)', function() end) it('executes a given command through the shell', function() + if helpers.pending_win32(pending) then return end command('set shellxquote=') -- win: avoid extra quotes terminal_with_fake_shell('echo hi') screen:expect([[ @@ -187,6 +190,7 @@ describe(':terminal (with fake shell)', function() end) it("executes a given command through the shell, when 'shell' has arguments", function() + if helpers.pending_win32(pending) then return end nvim('set_option', 'shell', nvim_dir..'/shell-test -t jeff') command('set shellxquote=') -- win: avoid extra quotes terminal_with_fake_shell('echo hi') @@ -199,6 +203,7 @@ describe(':terminal (with fake shell)', function() end) it('allows quotes and slashes', function() + if helpers.pending_win32(pending) then return end command('set shellxquote=') -- win: avoid extra quotes terminal_with_fake_shell([[echo 'hello' \ "world"]]) screen:expect([[ @@ -235,6 +240,7 @@ describe(':terminal (with fake shell)', function() end) it('works with :find', function() + if helpers.pending_win32(pending) then return end terminal_with_fake_shell() screen:expect([[ ^ready $ | @@ -253,6 +259,7 @@ describe(':terminal (with fake shell)', function() end) it('works with gf', function() + if helpers.pending_win32(pending) then return end command('set shellxquote=') -- win: avoid extra quotes terminal_with_fake_shell([[echo "scripts/shadacat.py"]]) retry(nil, 4 * screen.timeout, function() diff --git a/test/functional/terminal/highlight_spec.lua b/test/functional/terminal/highlight_spec.lua index 8d3f0218af..ebbae1df14 100644 --- a/test/functional/terminal/highlight_spec.lua +++ b/test/functional/terminal/highlight_spec.lua @@ -117,6 +117,7 @@ describe(':terminal highlight', function() end) it(':terminal highlight has lower precedence than editor #9964', function() + if helpers.pending_win32(pending) then return end clear() local screen = Screen.new(30, 4) screen:set_default_attr_ids({ diff --git a/test/functional/terminal/mouse_spec.lua b/test/functional/terminal/mouse_spec.lua index 833ded9479..6e2c851df7 100644 --- a/test/functional/terminal/mouse_spec.lua +++ b/test/functional/terminal/mouse_spec.lua @@ -1,7 +1,7 @@ local helpers = require('test.functional.helpers')(after_each) local thelpers = require('test.functional.terminal.helpers') local clear, eq, eval = helpers.clear, helpers.eq, helpers.eval -local feed, nvim = helpers.feed, helpers.nvim +local feed, nvim, command = helpers.feed, helpers.nvim, helpers.command local feed_data = thelpers.feed_data describe(':terminal mouse', function() @@ -10,9 +10,9 @@ describe(':terminal mouse', function() before_each(function() clear() nvim('set_option', 'statusline', '==========') - nvim('command', 'highlight StatusLine cterm=NONE') - nvim('command', 'highlight StatusLineNC cterm=NONE') - nvim('command', 'highlight VertSplit cterm=NONE') + command('highlight StatusLine cterm=NONE') + command('highlight StatusLineNC cterm=NONE') + command('highlight VertSplit cterm=NONE') screen = thelpers.screen_setup() local lines = {} for i = 1, 30 do @@ -38,6 +38,26 @@ describe(':terminal mouse', function() eq('nt', eval('mode(1)')) end) + it('will exit focus and trigger Normal mode mapping on mouse click', function() + command('let g:got_leftmouse = 0') + command('nnoremap <LeftMouse> <Cmd>let g:got_leftmouse = 1<CR>') + eq('t', eval('mode(1)')) + eq(0, eval('g:got_leftmouse')) + feed('<LeftMouse>') + eq('nt', eval('mode(1)')) + eq(1, eval('g:got_leftmouse')) + end) + + it('will exit focus and trigger Normal mode mapping on mouse click with modifier', function() + command('let g:got_ctrl_leftmouse = 0') + command('nnoremap <C-LeftMouse> <Cmd>let g:got_ctrl_leftmouse = 1<CR>') + eq('t', eval('mode(1)')) + eq(0, eval('g:got_ctrl_leftmouse')) + feed('<C-LeftMouse>') + eq('nt', eval('mode(1)')) + eq(1, eval('g:got_ctrl_leftmouse')) + end) + it('will exit focus on <C-\\> + mouse-scroll', function() eq('t', eval('mode(1)')) feed('<C-\\>') @@ -66,7 +86,7 @@ describe(':terminal mouse', function() ]]) end) - it('will forward mouse clicks to the program', function() + it('will forward mouse press, drag and release to the program', function() if helpers.pending_win32(pending) then return end feed('<LeftMouse><1,2>') screen:expect([[ @@ -78,6 +98,36 @@ describe(':terminal mouse', function() "#{1: } | {3:-- TERMINAL --} | ]]) + feed('<LeftDrag><2,2>') + screen:expect([[ + line27 | + line28 | + line29 | + line30 | + mouse enabled | + @##{1: } | + {3:-- TERMINAL --} | + ]]) + feed('<LeftDrag><3,2>') + screen:expect([[ + line27 | + line28 | + line29 | + line30 | + mouse enabled | + @$#{1: } | + {3:-- TERMINAL --} | + ]]) + feed('<LeftRelease><3,2>') + screen:expect([[ + line27 | + line28 | + line29 | + line30 | + mouse enabled | + #$#{1: } | + {3:-- TERMINAL --} | + ]]) end) it('will forward mouse scroll to the program', function() @@ -94,9 +144,63 @@ describe(':terminal mouse', function() ]]) end) + it('dragging and scrolling do not interfere with each other', function() + if helpers.pending_win32(pending) then return end + feed('<LeftMouse><1,2>') + screen:expect([[ + line27 | + line28 | + line29 | + line30 | + mouse enabled | + "#{1: } | + {3:-- TERMINAL --} | + ]]) + feed('<ScrollWheelUp><1,2>') + screen:expect([[ + line27 | + line28 | + line29 | + line30 | + mouse enabled | + `"#{1: } | + {3:-- TERMINAL --} | + ]]) + feed('<LeftDrag><2,2>') + screen:expect([[ + line27 | + line28 | + line29 | + line30 | + mouse enabled | + @##{1: } | + {3:-- TERMINAL --} | + ]]) + feed('<ScrollWheelUp><2,2>') + screen:expect([[ + line27 | + line28 | + line29 | + line30 | + mouse enabled | + `##{1: } | + {3:-- TERMINAL --} | + ]]) + feed('<LeftRelease><2,2>') + screen:expect([[ + line27 | + line28 | + line29 | + line30 | + mouse enabled | + ###{1: } | + {3:-- TERMINAL --} | + ]]) + end) + it('will forward mouse clicks to the program with the correct even if set nu', function() if helpers.pending_win32(pending) then return end - nvim('command', 'set number') + command('set number') -- When the display area such as a number is clicked, it returns to the -- normal mode. feed('<LeftMouse><3,0>') diff --git a/test/functional/terminal/scrollback_spec.lua b/test/functional/terminal/scrollback_spec.lua index 11bdc73a47..d1cfc7e91b 100644 --- a/test/functional/terminal/scrollback_spec.lua +++ b/test/functional/terminal/scrollback_spec.lua @@ -345,6 +345,7 @@ end) describe(':terminal prints more lines than the screen height and exits', function() it('will push extra lines to scrollback', function() + if helpers.pending_win32(pending) then return end clear() local screen = Screen.new(30, 7) screen:attach({rgb=false}) @@ -576,6 +577,7 @@ describe("pending scrollback line handling", function() end) it("does not crash after nvim_buf_call #14891", function() + if helpers.pending_win32(pending) then return end exec_lua [[ local a = vim.api local bufnr = a.nvim_create_buf(false, true) diff --git a/test/functional/terminal/tui_spec.lua b/test/functional/terminal/tui_spec.lua index bf57b135cb..faf44fa01d 100644 --- a/test/functional/terminal/tui_spec.lua +++ b/test/functional/terminal/tui_spec.lua @@ -20,7 +20,6 @@ local nvim_prog = helpers.nvim_prog local nvim_set = helpers.nvim_set local ok = helpers.ok local read_file = helpers.read_file -local exec_lua = helpers.exec_lua if helpers.pending_win32(pending) then return end @@ -581,34 +580,21 @@ describe('TUI', function() end) it("paste: 'nomodifiable' buffer", function() - local has_luajit = exec_lua('return jit ~= nil') child_session:request('nvim_command', 'set nomodifiable') child_session:request('nvim_exec_lua', [[ - -- Stack traces for this test are non-deterministic, so disable them - _G.debug.traceback = function(msg) return msg end + -- Truncate the error message to hide the line number + _G.debug.traceback = function(msg) return msg:sub(-49) end ]], {}) feed_data('\027[200~fail 1\nfail 2\n\027[201~') - if has_luajit then - screen:expect{grid=[[ - | - {4:~ }| - {5: }| - {8:paste: Error executing lua: vim.lua:0: Vim:E21: Ca}| - {8:nnot make changes, 'modifiable' is off} | - {10:Press ENTER or type command to continue}{1: } | - {3:-- TERMINAL --} | - ]]} - else - screen:expect{grid=[[ - | - {4:~ }| - {5: }| - {8:paste: Error executing lua: Vim:E21: Cannot make c}| - {8:hanges, 'modifiable' is off} | - {10:Press ENTER or type command to continue}{1: } | - {3:-- TERMINAL --} | - ]]} - end + screen:expect{grid=[[ + | + {4:~ }| + {5: }| + {8:paste: Error executing lua: Vim:E21: Cannot make c}| + {8:hanges, 'modifiable' is off} | + {10:Press ENTER or type command to continue}{1: } | + {3:-- TERMINAL --} | + ]]} feed_data('\n') -- <Enter> child_session:request('nvim_command', 'set modifiable') feed_data('\027[200~success 1\nsuccess 2\n\027[201~') diff --git a/test/functional/terminal/window_spec.lua b/test/functional/terminal/window_spec.lua index 9f278fd157..0d3295cf32 100644 --- a/test/functional/terminal/window_spec.lua +++ b/test/functional/terminal/window_spec.lua @@ -18,6 +18,7 @@ describe(':terminal window', function() end) it('sets topline correctly #8556', function() + if helpers.pending_win32(pending) then return end -- Test has hardcoded assumptions of dimensions. eq(7, eval('&lines')) feed_data('\n\n\n') -- Add blank lines. 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 911fa017ab..599c74102c 100644 --- a/test/functional/treesitter/parser_spec.lua +++ b/test/functional/treesitter/parser_spec.lua @@ -167,6 +167,27 @@ void ui_refresh(void) eq('variable', ret) end) + it("supports caching queries", function() + local long_query = query:rep(100) + local first_run = exec_lua ([[ + local before = vim.loop.hrtime() + cquery = vim.treesitter.parse_query("c", ...) + local after = vim.loop.hrtime() + return after - before + ]], long_query) + + local subsequent_runs = exec_lua ([[ + local before = vim.loop.hrtime() + for i=1,100,1 do + cquery = vim.treesitter.parse_query("c", ...) + end + local after = vim.loop.hrtime() + return after - before + ]], long_query) + + assert.True(1000 * subsequent_runs < first_run) + end) + it('support query and iter by capture', function() insert(test_text) @@ -264,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;') @@ -645,6 +685,21 @@ int x = INT_MAX; -- READ_STRING_OK(x, y) (char_u *)read_string((x), (size_t)(y)) }, get_ranges()) end) + + it("should not inject bad languages", function() + if helpers.pending_win32(pending) then return end + exec_lua([=[ + vim.treesitter.add_directive("inject-bad!", function(match, _, _, pred, metadata) + metadata.language = "{" + metadata.combined = true + metadata.content = pred[2] + end) + + parser = vim.treesitter.get_parser(0, "c", { + injections = { + c = "(preproc_function_def value: ((preproc_arg) @_a (#inject-bad! @_a)))"}}) + ]=]) + end) end) describe("when using the offset directive", function() diff --git a/test/functional/ui/bufhl_spec.lua b/test/functional/ui/bufhl_spec.lua index 16ed3b9486..9f84b71ece 100644 --- a/test/functional/ui/bufhl_spec.lua +++ b/test/functional/ui/bufhl_spec.lua @@ -755,12 +755,26 @@ describe('Buffer highlighting', function() -- TODO: only a virtual text from the same ns curretly overrides -- an existing virtual text. We might add a prioritation system. set_virtual_text(id1, 0, s1, {}) - eq({{1, 0, 0, { priority = 0, virt_text = s1}}}, get_extmarks(id1, {0,0}, {0, -1}, {details=true})) + eq({{1, 0, 0, { + priority = 0, + virt_text = s1, + -- other details + right_gravity = true, + virt_text_pos = 'eol', + virt_text_hide = false, + }}}, get_extmarks(id1, {0,0}, {0, -1}, {details=true})) -- TODO: is this really valid? shouldn't the max be line_count()-1? local lastline = line_count() set_virtual_text(id1, line_count(), s2, {}) - eq({{3, lastline, 0, { priority = 0, virt_text = s2}}}, get_extmarks(id1, {lastline,0}, {lastline, -1}, {details=true})) + eq({{3, lastline, 0, { + priority = 0, + virt_text = s2, + -- other details + right_gravity = true, + virt_text_pos = 'eol', + virt_text_hide = false, + }}}, get_extmarks(id1, {lastline,0}, {lastline, -1}, {details=true})) eq({}, get_extmarks(id1, {lastline+9000,0}, {lastline+9000, -1}, {})) end) diff --git a/test/functional/ui/cmdline_highlight_spec.lua b/test/functional/ui/cmdline_highlight_spec.lua index 9c746b99bd..384761ab17 100644 --- a/test/functional/ui/cmdline_highlight_spec.lua +++ b/test/functional/ui/cmdline_highlight_spec.lua @@ -33,7 +33,7 @@ before_each(function() let g:NUM_LVLS = 4 function Redraw() mode - return '' + return "\<Ignore>" endfunction let g:id = '' cnoremap <expr> {REDRAW} Redraw() @@ -42,7 +42,7 @@ before_each(function() let Cb = g:Nvim_color_input{g:id} let out = input({'prompt': ':', 'highlight': Cb}) let g:out{id} = out - return (a:do_return ? out : '') + return (a:do_return ? out : "\<Ignore>") endfunction nnoremap <expr> {PROMPT} DoPrompt(0) cnoremap <expr> {PROMPT} DoPrompt(1) @@ -410,7 +410,7 @@ describe('Command-line coloring', function() end) it('stops executing callback after a number of errors', function() set_color_cb('SplittedMultibyteStart') - start_prompt('let x = "«»«»«»«»«»"\n') + start_prompt('let x = "«»«»«»«»«»"') screen:expect([[ {EOB:~ }| {EOB:~ }| @@ -419,7 +419,7 @@ describe('Command-line coloring', function() :let x = " | {ERR:E5405: Chunk 0 start 10 splits multibyte}| {ERR: character} | - ^:let x = "«»«»«»«»«»" | + :let x = "«»«»«»«»«»"^ | ]]) feed('\n') screen:expect([[ @@ -432,6 +432,7 @@ describe('Command-line coloring', function() {EOB:~ }| | ]]) + feed('\n') eq('let x = "«»«»«»«»«»"', meths.get_var('out')) local msg = '\nE5405: Chunk 0 start 10 splits multibyte character' eq(msg:rep(1), funcs.execute('messages')) @@ -474,14 +475,14 @@ describe('Command-line coloring', function() ]]) feed('\n') screen:expect([[ - | + ^ | {EOB:~ }| {EOB:~ }| {EOB:~ }| {EOB:~ }| {EOB:~ }| {EOB:~ }| - ^:echo 42 | + :echo 42 | ]]) feed('\n') eq('echo 42', meths.get_var('out')) 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/hlstate_spec.lua b/test/functional/ui/hlstate_spec.lua index 2a567b28ee..295b70f265 100644 --- a/test/functional/ui/hlstate_spec.lua +++ b/test/functional/ui/hlstate_spec.lua @@ -179,6 +179,8 @@ describe('ext_hlstate detailed highlights', function() end) it("work with :terminal", function() + if helpers.pending_win32(pending) then return end + screen:set_default_attr_ids({ [1] = {{}, {{hi_name = "TermCursorNC", ui_name = "TermCursorNC", kind = "ui"}}}, [2] = {{foreground = tonumber('0x00ccff'), fg_indexed=true}, {{kind = "term"}}}, diff --git a/test/functional/ui/inccommand_spec.lua b/test/functional/ui/inccommand_spec.lua index b6e2f2311f..10700d9508 100644 --- a/test/functional/ui/inccommand_spec.lua +++ b/test/functional/ui/inccommand_spec.lua @@ -293,6 +293,70 @@ describe(":substitute, 'inccommand' preserves", function() end) end + for _, case in ipairs({'', 'split', 'nosplit'}) do + it('previous substitute string ~ (inccommand='..case..') #12109', function() + local screen = Screen.new(30,10) + common_setup(screen, case, default_text) + + feed(':%s/Inc/SUB<CR>') + expect([[ + SUB substitution on + two lines + ]]) + + feed(':%s/line/') + poke_eventloop() + feed('~') + poke_eventloop() + feed('<CR>') + expect([[ + SUB substitution on + two SUBs + ]]) + + feed(':%s/sti/') + poke_eventloop() + feed('~') + poke_eventloop() + feed('B') + poke_eventloop() + feed('<CR>') + expect([[ + SUB subSUBBtution on + two SUBs + ]]) + + feed(':%s/ion/NEW<CR>') + expect([[ + SUB subSUBBtutNEW on + two SUBs + ]]) + + feed(':%s/two/') + poke_eventloop() + feed('N') + poke_eventloop() + feed('~') + poke_eventloop() + feed('<CR>') + expect([[ + SUB subSUBBtutNEW on + NNEW SUBs + ]]) + + feed(':%s/bS/') + poke_eventloop() + feed('~') + poke_eventloop() + feed('W') + poke_eventloop() + feed('<CR>') + expect([[ + SUB suNNEWWUBBtutNEW on + NNEW SUBs + ]]) + end) + end end) describe(":substitute, 'inccommand' preserves undo", function() diff --git a/test/functional/ui/input_spec.lua b/test/functional/ui/input_spec.lua index ea8968a653..7000505909 100644 --- a/test/functional/ui/input_spec.lua +++ b/test/functional/ui/input_spec.lua @@ -114,11 +114,49 @@ describe('mappings', function() end) end) -describe('input utf sequences that contain CSI/K_SPECIAL', function() +describe('input utf sequences that contain K_SPECIAL (0x80)', function() it('ok', function() feed('i…<esc>') expect('…') end) + + it('can be mapped', function() + command('inoremap … E280A6') + feed('i…<esc>') + expect('E280A6') + end) +end) + +describe('input utf sequences that contain CSI (0x9B)', function() + it('ok', function() + feed('iě<esc>') + expect('ě') + end) + + it('can be mapped', function() + command('inoremap ě C49B') + feed('iě<esc>') + expect('C49B') + end) +end) + +describe('input split utf sequences', function() + it('ok', function() + local str = '►' + feed('i' .. str:sub(1, 1)) + helpers.sleep(10) + feed(str:sub(2, 3)) + expect('►') + end) + + it('can be mapped', function() + command('inoremap ► E296BA') + local str = '►' + feed('i' .. str:sub(1, 1)) + helpers.sleep(10) + feed(str:sub(2, 3)) + expect('E296BA') + end) end) describe('input non-printable chars', function() @@ -146,7 +184,7 @@ describe('input non-printable chars', function() {1:~ }| {1:~ }| {1:~ }| - "Xtest-overwrite" [noeol] 1L, 6C | + "Xtest-overwrite" [noeol] 1L, 6B | ]]) -- The timestamp is in second resolution, wait two seconds to be sure. diff --git a/test/functional/ui/messages_spec.lua b/test/functional/ui/messages_spec.lua index f038348253..949591870a 100644 --- a/test/functional/ui/messages_spec.lua +++ b/test/functional/ui/messages_spec.lua @@ -1206,6 +1206,7 @@ end) describe('ui/msg_puts_printf', function() it('output multibyte characters correctly', function() + if helpers.pending_win32(pending) then return end local screen local cmd = '' local locale_dir = test_build_dir..'/share/locale/ja/LC_MESSAGES' diff --git a/test/functional/ui/output_spec.lua b/test/functional/ui/output_spec.lua index 50e5dfac84..7305baa761 100644 --- a/test/functional/ui/output_spec.lua +++ b/test/functional/ui/output_spec.lua @@ -14,6 +14,7 @@ local has_powershell = helpers.has_powershell local set_shell_powershell = helpers.set_shell_powershell describe("shell command :!", function() + if helpers.pending_win32(pending) then return end local screen before_each(function() clear() diff --git a/test/functional/ui/popupmenu_spec.lua b/test/functional/ui/popupmenu_spec.lua index 4fc5c389e5..c44e59cfd3 100644 --- a/test/functional/ui/popupmenu_spec.lua +++ b/test/functional/ui/popupmenu_spec.lua @@ -10,6 +10,7 @@ local funcs = helpers.funcs local get_pathsep = helpers.get_pathsep local eq = helpers.eq local pcall_err = helpers.pcall_err +local exec_lua = helpers.exec_lua describe('ui/ext_popupmenu', function() local screen @@ -25,6 +26,7 @@ describe('ui/ext_popupmenu', function() [5] = {bold = true, foreground = Screen.colors.SeaGreen}, [6] = {background = Screen.colors.WebGray}, [7] = {background = Screen.colors.LightMagenta}, + [8] = {foreground = Screen.colors.Red}, }) source([[ function! TestComplete() abort @@ -369,6 +371,111 @@ describe('ui/ext_popupmenu', function() {1:~ }| {2:-- INSERT --} | ]]) + + command('iunmap <f1>') + command('iunmap <f2>') + command('iunmap <f3>') + exec_lua([[ + vim.keymap.set('i', '<f1>', function() vim.api.nvim_select_popupmenu_item(2, true, false, {}) end) + vim.keymap.set('i', '<f2>', function() vim.api.nvim_select_popupmenu_item(-1, false, false, {}) end) + vim.keymap.set('i', '<f3>', function() vim.api.nvim_select_popupmenu_item(1, false, true, {}) end) + ]]) + feed('<C-r>=TestComplete()<CR>') + screen:expect([[ + | + foo^ | + {6:fo x the foo }{1: }| + {7:bar }{1: }| + {7:spam }{1: }| + {1:~ }| + {1:~ }| + {2:-- INSERT --} | + ]]) + + feed('<f1>') + screen:expect([[ + | + spam^ | + {7:fo x the foo }{1: }| + {7:bar }{1: }| + {6:spam }{1: }| + {1:~ }| + {1:~ }| + {2:-- INSERT --} | + ]]) + + feed('<f2>') + screen:expect([[ + | + spam^ | + {7:fo x the foo }{1: }| + {7:bar }{1: }| + {7:spam }{1: }| + {1:~ }| + {1:~ }| + {2:-- INSERT --} | + ]]) + + feed('<f3>') + screen:expect([[ + | + bar^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {2:-- INSERT --} | + ]]) + + feed('<esc>ddiaa bb cc<cr>') + feed('<c-x><c-n>') + screen:expect([[ + aa bb cc | + aa^ | + {6:aa }{1: }| + {7:bb }{1: }| + {7:cc }{1: }| + {1:~ }| + {1:~ }| + {2:-- Keyword Local completion (^N^P) }{5:match 1 of 3} | + ]]) + + feed('<f1>') + screen:expect([[ + aa bb cc | + cc^ | + {7:aa }{1: }| + {7:bb }{1: }| + {6:cc }{1: }| + {1:~ }| + {1:~ }| + {2:-- Keyword Local completion (^N^P) }{5:match 3 of 3} | + ]]) + + feed('<f2>') + screen:expect([[ + aa bb cc | + cc^ | + {7:aa }{1: }| + {7:bb }{1: }| + {7:cc }{1: }| + {1:~ }| + {1:~ }| + {2:-- Keyword Local completion (^N^P) }{8:Back at original} | + ]]) + + feed('<f3>') + screen:expect([[ + aa bb cc | + bb^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {2:-- INSERT --} | + ]]) end) local function source_complete_month() @@ -2214,6 +2321,47 @@ describe('builtin popupmenu', function() assert_alive() end) + it('is closed by :stopinsert from timer #12976', function() + screen:try_resize(32,14) + command([[call setline(1, ['hello', 'hullo', 'heeee', ''])]]) + feed('Gah<C-N>') + screen:expect([[ + hello | + hullo | + heeee | + hello^ | + {s:hello }{1: }| + {n:hullo }{1: }| + {n:heeee }{1: }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {2:-- }{5:match 1 of 3} | + ]]) + command([[call timer_start(100, { -> execute('stopinsert') })]]) + helpers.sleep(200) + feed('k') -- cursor should move up in Normal mode + screen:expect([[ + hello | + hullo | + heee^e | + hello | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]) + end) + it('truncates double-width character correctly when there is no scrollbar', function() screen:try_resize(32,8) command('set completeopt+=menuone,noselect') diff --git a/test/functional/ui/screen_basic_spec.lua b/test/functional/ui/screen_basic_spec.lua index 958e137f65..09bcbc2bbd 100644 --- a/test/functional/ui/screen_basic_spec.lua +++ b/test/functional/ui/screen_basic_spec.lua @@ -6,6 +6,7 @@ local insert = helpers.insert local eq = helpers.eq local eval = helpers.eval local iswin = helpers.iswin +local funcs, meths, exec_lua = helpers.funcs, helpers.meths, helpers.exec_lua describe('screen', function() local screen @@ -127,14 +128,67 @@ local function screen_tests(linegrid) end) it('has correct default title with named file', function() - local expected = (iswin() and 'myfile (C:\\mydir) - NVIM' - or 'myfile (/mydir) - NVIM') + local expected = (iswin() and 'myfile (C:\\mydir) - NVIM' or 'myfile (/mydir) - NVIM') command('set title') command(iswin() and 'file C:\\mydir\\myfile' or 'file /mydir/myfile') screen:expect(function() eq(expected, screen.title) end) end) + + describe('is not changed by', function() + local file1 = iswin() and 'C:\\mydir\\myfile1' or '/mydir/myfile1' + local file2 = iswin() and 'C:\\mydir\\myfile2' or '/mydir/myfile2' + local expected = (iswin() and 'myfile1 (C:\\mydir) - NVIM' or 'myfile1 (/mydir) - NVIM') + local buf2 + + before_each(function() + command('edit '..file1) + buf2 = funcs.bufadd(file2) + command('set title') + end) + + it('calling setbufvar() to set an option in a hidden buffer from i_CTRL-R', function() + command([[inoremap <F2> <C-R>=setbufvar(]]..buf2..[[, '&autoindent', 1) ? '' : ''<CR>]]) + feed('i<F2><Esc>') + command('redraw!') + screen:expect(function() + eq(expected, screen.title) + end) + end) + + it('an RPC call to nvim_buf_set_option in a hidden buffer', function() + meths.buf_set_option(buf2, 'autoindent', true) + command('redraw!') + screen:expect(function() + eq(expected, screen.title) + end) + end) + + it('a Lua callback calling nvim_buf_set_option in a hidden buffer', function() + exec_lua(string.format([[ + vim.schedule(function() + vim.api.nvim_buf_set_option(%d, 'autoindent', true) + end) + ]], buf2)) + command('redraw!') + screen:expect(function() + eq(expected, screen.title) + end) + end) + + it('a Lua callback calling nvim_buf_call in a hidden buffer', function() + exec_lua(string.format([[ + vim.schedule(function() + vim.api.nvim_buf_call(%d, function() end) + end) + ]], buf2)) + command('redraw!') + screen:expect(function() + eq(expected, screen.title) + end) + end) + end) end) describe(':set icon', function() diff --git a/test/functional/ui/sign_spec.lua b/test/functional/ui/sign_spec.lua index dcd31cfdb7..f718786557 100644 --- a/test/functional/ui/sign_spec.lua +++ b/test/functional/ui/sign_spec.lua @@ -446,7 +446,7 @@ describe('Signs', function() {1:>>>>>>>>}{6: 1 }a | {2: }{6: 2 }b | {2: }{6: 3 }c | - {2: }{6:^ 4 } | + {2: }{6: 4 }^ | {0:~ }| {0:~ }| {0:~ }| @@ -468,7 +468,7 @@ describe('Signs', function() {1:>>>>>>>>>>}{6: 1 }a | {2: }{6: 2 }b | {2: }{6: 3 }c | - {2: ^ }{6: 4 } | + {2: }{6: 4 }^ | {0:~ }| {0:~ }| {0:~ }| @@ -512,7 +512,33 @@ describe('Signs', function() {1:>>}{6: 1 }a | {2: }{6: 2 }b | {2: }{6: 3 }c | - {2: }{6: ^4 } | + {2: }{6: 4 }^ | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + | + ]]) + 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:~ }| diff --git a/test/functional/vimscript/eval_spec.lua b/test/functional/vimscript/eval_spec.lua index e1459ab5b8..0c2ca8de78 100644 --- a/test/functional/vimscript/eval_spec.lua +++ b/test/functional/vimscript/eval_spec.lua @@ -5,7 +5,7 @@ -- null_spec.lua -- operators_spec.lua -- --- Tests for the Vimscript |functions| library should live in: +-- Tests for the Vimscript |builtin-functions| library should live in: -- test/functional/vimscript/<funcname>_spec.lua -- test/functional/vimscript/functions_spec.lua diff --git a/test/functional/vimscript/functions_spec.lua b/test/functional/vimscript/functions_spec.lua index 0ad7fd8010..20c1400030 100644 --- a/test/functional/vimscript/functions_spec.lua +++ b/test/functional/vimscript/functions_spec.lua @@ -1,4 +1,4 @@ --- Tests for misc Vimscript |functions|. +-- Tests for misc Vimscript |builtin-functions|. -- -- If a function is non-trivial, consider moving its spec to: -- test/functional/vimscript/<funcname>_spec.lua diff --git a/test/functional/vimscript/timer_spec.lua b/test/functional/vimscript/timer_spec.lua index e45b64422f..25e46062b7 100644 --- a/test/functional/vimscript/timer_spec.lua +++ b/test/functional/vimscript/timer_spec.lua @@ -272,4 +272,12 @@ describe('timers', function() ]] eq("Vim(call):E48: Not allowed in sandbox", exc_exec("sandbox call timer_start(0, 'Scary')")) end) + + it('can be triggered after an empty string <expr> mapping', function() + local screen = Screen.new(40, 6) + screen:attach() + command([=[imap <expr> <F2> [timer_start(0, { _ -> execute("throw 'x'", "") }), ''][-1]]=]) + feed('i<F2>') + screen:expect({any='E605: Exception not caught: x'}) + end) end) diff --git a/test/helpers.lua b/test/helpers.lua index 87431e4342..7d2f8f760a 100644 --- a/test/helpers.lua +++ b/test/helpers.lua @@ -58,9 +58,9 @@ local check_logs_useless_lines = { --- Invokes `fn` and includes the tail of `logfile` in the error message if it --- fails. --- ----@param logfile Log file, defaults to $NVIM_LOG_FILE or '.nvimlog' ----@param fn Function to invoke ----@param ... Function arguments +---@param logfile string Log file, defaults to $NVIM_LOG_FILE or '.nvimlog' +---@param fn string Function to invoke +---@param ... string Function arguments local function dumplog(logfile, fn, ...) -- module.validate({ -- logfile={logfile,'s',true}, @@ -102,8 +102,8 @@ end --- Asserts that `pat` matches one or more lines in the tail of $NVIM_LOG_FILE. --- ----@param pat (string) Lua pattern to search for in the log file. ----@param logfile (string, default=$NVIM_LOG_FILE) full path to log file. +---@param pat string Lua pattern to search for in the log file +---@param logfile string Full path to log file (default=$NVIM_LOG_FILE) function module.assert_log(pat, logfile) logfile = logfile or os.getenv('NVIM_LOG_FILE') or '.nvimlog' local nrlines = 10 @@ -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 @@ -815,7 +813,7 @@ end function module.read_nvim_log(logfile, ci_rename) logfile = logfile or os.getenv('NVIM_LOG_FILE') or '.nvimlog' local is_ci = module.isCI() - local keep = is_ci and 999 or 10 + local keep = is_ci and 100 or 10 local lines = module.read_file_list(logfile, -keep) or {} local log = (('-'):rep(78)..'\n' ..string.format('$NVIM_LOG_FILE: %s\n', logfile) diff --git a/test/unit/buffer_spec.lua b/test/unit/buffer_spec.lua index 3692e19379..34c88248e6 100644 --- a/test/unit/buffer_spec.lua +++ b/test/unit/buffer_spec.lua @@ -249,7 +249,7 @@ describe('buffer functions', function() -- -- @param arg Options can be placed in an optional dictionary as the last parameter -- .expected_cell_count The expected number of cells build_stl_str_hl will return - -- .expected_byte_length The expected byte length of the string + -- .expected_byte_length The expected byte length of the string (defaults to byte length of expected_stl) -- .file_name The name of the file to be tested (useful in %f type tests) -- .fillchar The character that will be used to fill any 'extra' space in the stl local function statusline_test (description, @@ -264,7 +264,7 @@ describe('buffer functions', function() local fillchar = option.fillchar or (' '):byte() local expected_cell_count = option.expected_cell_count or statusline_cell_count - local expected_byte_length = option.expected_byte_length or expected_cell_count + local expected_byte_length = option.expected_byte_length or #expected_stl itp(description, function() if option.file_name then @@ -312,12 +312,12 @@ describe('buffer functions', function() statusline_test('should put fillchar `~` in between text', 10, 'abc%=def', 'abc~~~~def', {fillchar=('~'):byte()}) + statusline_test('should put fillchar `━` in between text', 10, + 'abc%=def', 'abc━━━━def', + {fillchar=0x2501}) statusline_test('should handle zero-fillchar as a space', 10, 'abcde%=', 'abcde ', {fillchar=0}) - statusline_test('should handle multibyte-fillchar as a dash', 10, - 'abcde%=', 'abcde-----', - {fillchar=0x80}) statusline_test('should print the tail file name', 80, '%t', 'buffer_spec.lua', {file_name='test/unit/buffer_spec.lua', expected_cell_count=15}) @@ -366,70 +366,86 @@ describe('buffer functions', function() statusline_test('should ignore trailing %', 3, 'abc%', 'abc') - -- alignment testing - statusline_test('should right align when using =', 20, - 'neo%=vim', 'neo vim') - statusline_test('should, when possible, center text when using %=text%=', 20, - 'abc%=neovim%=def', 'abc neovim def') - statusline_test('should handle uneven spacing in the buffer when using %=text%=', 20, - 'abc%=neo_vim%=def', 'abc neo_vim def') - statusline_test('should have equal spaces even with non-equal sides when using =', 20, - 'foobar%=test%=baz', 'foobar test baz') - statusline_test('should have equal spaces even with longer right side when using =', 20, - 'a%=test%=longtext', 'a test longtext') - statusline_test('should handle an empty left side when using ==', 20, - '%=test%=baz', ' test baz') - statusline_test('should handle an empty right side when using ==', 20, - 'foobar%=test%=', 'foobar test ') - statusline_test('should handle consecutive empty ==', 20, - '%=%=test%=', ' test ') - statusline_test('should handle an = alone', 20, - '%=', ' ') - statusline_test('should right align text when it is alone with =', 20, - '%=foo', ' foo') - statusline_test('should left align text when it is alone with =', 20, - 'foo%=', 'foo ') - - statusline_test('should approximately center text when using %=text%=', 21, - 'abc%=neovim%=def', 'abc neovim def') - statusline_test('should completely fill the buffer when using %=text%=', 21, - 'abc%=neo_vim%=def', 'abc neo_vim def') - statusline_test('should have equal spaces even with non-equal sides when using =', 21, - 'foobar%=test%=baz', 'foobar test baz') - statusline_test('should have equal spaces even with longer right side when using =', 21, - 'a%=test%=longtext', 'a test longtext') - statusline_test('should handle an empty left side when using ==', 21, - '%=test%=baz', ' test baz') - statusline_test('should handle an empty right side when using ==', 21, - 'foobar%=test%=', 'foobar test ') - - statusline_test('should quadrant the text when using 3 %=', 40, - 'abcd%=n%=eovim%=ef', 'abcd n eovim ef') - statusline_test('should work well with %t', 40, - '%t%=right_aligned', 'buffer_spec.lua right_aligned', + -- alignment testing with fillchar + local function statusline_test_align (description, + statusline_cell_count, + input_stl, + expected_stl, + arg) + arg = arg or {} + statusline_test(description .. ' without fillchar', + statusline_cell_count, input_stl, expected_stl:gsub('%~', ' '), arg) + arg.fillchar = ('!'):byte() + statusline_test(description .. ' with fillchar `!`', + statusline_cell_count, input_stl, expected_stl:gsub('%~', '!'), arg) + arg.fillchar = 0x2501 + statusline_test(description .. ' with fillchar `━`', + statusline_cell_count, input_stl, expected_stl:gsub('%~', '━'), arg) + end + + statusline_test_align('should right align when using =', 20, + 'neo%=vim', 'neo~~~~~~~~~~~~~~vim') + statusline_test_align('should, when possible, center text when using %=text%=', 20, + 'abc%=neovim%=def', 'abc~~~~neovim~~~~def') + statusline_test_align('should handle uneven spacing in the buffer when using %=text%=', 20, + 'abc%=neo_vim%=def', 'abc~~~neo_vim~~~~def') + statusline_test_align('should have equal spaces even with non-equal sides when using =', 20, + 'foobar%=test%=baz', 'foobar~~~test~~~~baz') + statusline_test_align('should have equal spaces even with longer right side when using =', 20, + 'a%=test%=longtext', 'a~~~test~~~~longtext') + statusline_test_align('should handle an empty left side when using ==', 20, + '%=test%=baz', '~~~~~~test~~~~~~~baz') + statusline_test_align('should handle an empty right side when using ==', 20, + 'foobar%=test%=', 'foobar~~~~~test~~~~~') + statusline_test_align('should handle consecutive empty ==', 20, + '%=%=test%=', '~~~~~~~~~~test~~~~~~') + statusline_test_align('should handle an = alone', 20, + '%=', '~~~~~~~~~~~~~~~~~~~~') + statusline_test_align('should right align text when it is alone with =', 20, + '%=foo', '~~~~~~~~~~~~~~~~~foo') + statusline_test_align('should left align text when it is alone with =', 20, + 'foo%=', 'foo~~~~~~~~~~~~~~~~~') + + statusline_test_align('should approximately center text when using %=text%=', 21, + 'abc%=neovim%=def', 'abc~~~~neovim~~~~~def') + statusline_test_align('should completely fill the buffer when using %=text%=', 21, + 'abc%=neo_vim%=def', 'abc~~~~neo_vim~~~~def') + statusline_test_align('should have equal spacing even with non-equal sides when using =', 21, + 'foobar%=test%=baz', 'foobar~~~~test~~~~baz') + statusline_test_align('should have equal spacing even with longer right side when using =', 21, + 'a%=test%=longtext', 'a~~~~test~~~~longtext') + statusline_test_align('should handle an empty left side when using ==', 21, + '%=test%=baz', '~~~~~~~test~~~~~~~baz') + statusline_test_align('should handle an empty right side when using ==', 21, + 'foobar%=test%=', 'foobar~~~~~test~~~~~~') + + statusline_test_align('should quadrant the text when using 3 %=', 40, + 'abcd%=n%=eovim%=ef', 'abcd~~~~~~~~~n~~~~~~~~~eovim~~~~~~~~~~ef') + statusline_test_align('should work well with %t', 40, + '%t%=right_aligned', 'buffer_spec.lua~~~~~~~~~~~~right_aligned', {file_name='test/unit/buffer_spec.lua'}) - statusline_test('should work well with %t and regular text', 40, - 'l%=m_l %t m_r%=r', 'l m_l buffer_spec.lua m_r r', + statusline_test_align('should work well with %t and regular text', 40, + 'l%=m_l %t m_r%=r', 'l~~~~~~~m_l buffer_spec.lua m_r~~~~~~~~r', {file_name='test/unit/buffer_spec.lua'}) - statusline_test('should work well with %=, %t, %L, and %l', 40, - '%t %= %L %= %l', 'buffer_spec.lua 1 0', + statusline_test_align('should work well with %=, %t, %L, and %l', 40, + '%t %= %L %= %l', 'buffer_spec.lua ~~~~~~~~~ 1 ~~~~~~~~~~ 0', {file_name='test/unit/buffer_spec.lua'}) - statusline_test('should quadrant the text when using 3 %=', 41, - 'abcd%=n%=eovim%=ef', 'abcd n eovim ef') - statusline_test('should work well with %t', 41, - '%t%=right_aligned', 'buffer_spec.lua right_aligned', + statusline_test_align('should quadrant the text when using 3 %=', 41, + 'abcd%=n%=eovim%=ef', 'abcd~~~~~~~~~n~~~~~~~~~eovim~~~~~~~~~~~ef') + statusline_test_align('should work well with %t', 41, + '%t%=right_aligned', 'buffer_spec.lua~~~~~~~~~~~~~right_aligned', {file_name='test/unit/buffer_spec.lua'}) - statusline_test('should work well with %t and regular text', 41, - 'l%=m_l %t m_r%=r', 'l m_l buffer_spec.lua m_r r', + statusline_test_align('should work well with %t and regular text', 41, + 'l%=m_l %t m_r%=r', 'l~~~~~~~~m_l buffer_spec.lua m_r~~~~~~~~r', {file_name='test/unit/buffer_spec.lua'}) - statusline_test('should work well with %=, %t, %L, and %l', 41, - '%t %= %L %= %l', 'buffer_spec.lua 1 0', + statusline_test_align('should work well with %=, %t, %L, and %l', 41, + '%t %= %L %= %l', 'buffer_spec.lua ~~~~~~~~~~ 1 ~~~~~~~~~~ 0', {file_name='test/unit/buffer_spec.lua'}) - statusline_test('should work with 10 %=', 50, + statusline_test_align('should work with 10 %=', 50, 'aaaa%=b%=c%=d%=e%=fg%=hi%=jk%=lmnop%=qrstuv%=wxyz', - 'aaaa b c d e fg hi jk lmnop qrstuv wxyz') + 'aaaa~~b~~c~~d~~e~~fg~~hi~~jk~~lmnop~~qrstuv~~~wxyz') -- stl item testing local tabline = '' @@ -452,11 +468,10 @@ describe('buffer functions', function() -- multi-byte testing statusline_test('should handle multibyte characters', 10, - 'Ĉ%=x', 'Ĉ x', - {expected_byte_length=11}) + 'Ĉ%=x', 'Ĉ x') statusline_test('should handle multibyte characters and different fillchars', 10, 'Ą%=mid%=end', 'Ą@mid@@end', - {fillchar=('@'):byte(), expected_byte_length=11}) + {fillchar=('@'):byte()}) -- escaping % testing statusline_test('should handle escape of %', 4, 'abc%%', 'abc%') diff --git a/test/unit/charset/vim_str2nr_spec.lua b/test/unit/charset/vim_str2nr_spec.lua index 5fc3b83a13..caf330c378 100644 --- a/test/unit/charset/vim_str2nr_spec.lua +++ b/test/unit/charset/vim_str2nr_spec.lua @@ -463,7 +463,7 @@ describe('vim_str2nr()', function() test_vim_str2nr("1'2'3'4", flags, {len = 7, num = 1234, unum = 1234, pre = 0}, 0) -- counter-intuitive, but like Vim, strict=true should partially accept - -- these: (' and - are not alpha-numeric) + -- these: (' and - are not alphanumeric) test_vim_str2nr("7''331", flags, {len = 1, num = 7, unum = 7, pre = 0}, 0) test_vim_str2nr("123'x4", flags, {len = 3, num = 123, unum = 123, pre = 0}, 0) test_vim_str2nr("1337'", flags, {len = 4, num = 1337, unum = 1337, pre = 0}, 0) diff --git a/test/unit/garray_spec.lua b/test/unit/garray_spec.lua index 28df8a6e3f..5d41dd39ec 100644 --- a/test/unit/garray_spec.lua +++ b/test/unit/garray_spec.lua @@ -18,7 +18,7 @@ local growsize = 95 -- constructing a class wrapper around garray. It could for example associate -- ga_clear_strings to the underlying garray cdata if the garray is a string -- array. But for now I estimate that that kind of magic might make testing --- less "transparant" (i.e.: the interface would become quite different as to +-- less "transparent" (i.e.: the interface would become quite different as to -- how one would use it from C. -- accessors diff --git a/test/unit/marktree_spec.lua b/test/unit/marktree_spec.lua index 10d02d2eb4..3c96bc5f58 100644 --- a/test/unit/marktree_spec.lua +++ b/test/unit/marktree_spec.lua @@ -32,11 +32,11 @@ local function shadoworder(tree, shadow, iter, giveorder) local mark = lib.marktree_itr_current(iter) local id = tonumber(mark.id) local spos = shadow[id] - if (mark.row ~= spos[1] or mark.col ~= spos[2]) then - error("invalid pos for "..id..":("..mark.row..", "..mark.col..") instead of ("..spos[1]..", "..spos[2]..")") + if (mark.pos.row ~= spos[1] or mark.pos.col ~= spos[2]) then + error("invalid pos for "..id..":("..mark.pos.row..", "..mark.pos.col..") instead of ("..spos[1]..", "..spos[2]..")") end - if mark.right_gravity ~= spos[3] then - error("invalid gravity for "..id..":("..mark.row..", "..mark.col..")") + if lib.mt_right_test(mark) ~= spos[3] then + error("invalid gravity for "..id..":("..mark.pos.row..", "..mark.pos.col..")") end if count > 0 then if not pos_leq(last, spos) then @@ -87,7 +87,21 @@ local function dosplice(tree, shadow, start, old_extent, new_extent) shadowsplice(shadow, start, old_extent, new_extent) end +local last_id = nil + +local function put(tree, row, col, gravitate) + last_id = last_id + 1 + local my_id = last_id + + lib.marktree_put_test(tree, my_id, row, col, gravitate); + return my_id +end + describe('marktree', function() + before_each(function() + last_id = 0 + end) + itp('works', function() local tree = ffi.new("MarkTree[1]") -- zero initialized by luajit local shadow = {} @@ -97,7 +111,7 @@ describe('marktree', function() for i = 1,100 do for j = 1,100 do local gravitate = (i%2) > 0 - local id = tonumber(lib.marktree_put(tree, j, i, gravitate, 0)) + local id = put(tree, j, i, gravitate) ok(id > 0) eq(nil, shadow[id]) shadow[id] = {j,i,gravitate} @@ -115,12 +129,12 @@ describe('marktree', function() eq({}, id2pos) for i,ipos in pairs(shadow) do - local pos = lib.marktree_lookup(tree, i, iter) - eq(ipos[1], pos.row) - eq(ipos[2], pos.col) + local p = lib.marktree_lookup_ns(tree, -1, i, false, iter) + eq(ipos[1], p.pos.row) + eq(ipos[2], p.pos.col) local k = lib.marktree_itr_current(iter) - eq(ipos[1], k.row) - eq(ipos[2], k.col, ipos[1]) + eq(ipos[1], k.pos.row) + eq(ipos[2], k.pos.col, ipos[1]) lib.marktree_itr_next(tree, iter) -- TODO(bfredl): use id2pos to check neighbour? -- local k2 = lib.marktree_itr_current(iter) @@ -130,8 +144,8 @@ describe('marktree', function() lib.marktree_itr_get(tree, ipos[1], ipos[2], iter) local k = lib.marktree_itr_current(iter) eq(i, tonumber(k.id)) - eq(ipos[1], k.row) - eq(ipos[2], k.col) + eq(ipos[1], k.pos.row) + eq(ipos[2], k.pos.col) end ok(lib.marktree_itr_first(tree, iter)) @@ -146,8 +160,8 @@ describe('marktree', function() lib.marktree_itr_get(tree, i, 50+ci, iter) local k = lib.marktree_itr_current(iter) local id = tonumber(k.id) - eq(shadow[id][1], k.row) - eq(shadow[id][2], k.col) + eq(shadow[id][1], k.pos.row) + eq(shadow[id][2], k.pos.col) lib.marktree_del_itr(tree, iter, false) shadow[id] = nil end @@ -191,7 +205,7 @@ describe('marktree', function() -- https://github.com/neovim/neovim/pull/14719 lib.marktree_clear(tree) for i = 1,20 do - lib.marktree_put(tree, i, i, false, 0) + put(tree, i, i, false) end lib.marktree_itr_get(tree, 10, 10, iter) diff --git a/test/unit/os/env_spec.lua b/test/unit/os/env_spec.lua index e7cb5e5d5e..a0e02b6624 100644 --- a/test/unit/os/env_spec.lua +++ b/test/unit/os/env_spec.lua @@ -191,7 +191,7 @@ describe('env.c', function() if ffi.abi('64bit') then -- couldn't use a bigger number because it gets converted to - -- double somewere, should be big enough anyway + -- double somewhere, should be big enough anyway -- maxuint64 = ffi.new 'size_t', 18446744073709551615 local maxuint64 = ffi.new('size_t', 18446744073709000000) eq(NULL, cimp.os_getenvname_at_index(maxuint64)) diff --git a/test/unit/os/fs_spec.lua b/test/unit/os/fs_spec.lua index 7fd71cb1ae..0bb33772cd 100644 --- a/test/unit/os/fs_spec.lua +++ b/test/unit/os/fs_spec.lua @@ -1029,7 +1029,7 @@ describe('fs.c', function() itp('returns the correct blocksize of a file', function() local path = 'unit-test-directory/test.file' -- there is a bug in luafilesystem where - -- `lfs.attributes path, 'blksize'` returns the worng value: + -- `lfs.attributes path, 'blksize'` returns the wrong value: -- https://github.com/keplerproject/luafilesystem/pull/44 -- using this workaround for now: local blksize = lfs.attributes(path).blksize @@ -1038,7 +1038,7 @@ describe('fs.c', function() if blksize then eq(blksize, fs.os_fileinfo_blocksize(info)) else - -- luafs dosn't support blksize on windows + -- luafs doesn't support blksize on windows -- libuv on windows returns a constant value as blocksize -- checking for this constant value should be enough eq(2048, fs.os_fileinfo_blocksize(info)) diff --git a/test/unit/path_spec.lua b/test/unit/path_spec.lua index 15ce59747e..fb476397e6 100644 --- a/test/unit/path_spec.lua +++ b/test/unit/path_spec.lua @@ -626,4 +626,20 @@ describe('path.c', function() eq(false, path_with_extension('/some/path/file', 'lua')) end) end) + + describe('path_with_url', function() + itp('scheme is alpha and inner hyphen only', function() + local function path_with_url(fname) + return cimp.path_with_url(to_cstr(fname)) + end + eq(1, path_with_url([[test://xyz/foo/b0]])) + eq(2, path_with_url([[test:\\xyz\foo\b0]])) + eq(0, path_with_url([[test+abc://xyz/foo/b1]])) + eq(0, path_with_url([[test_abc://xyz/foo/b2]])) + eq(1, path_with_url([[test-abc://xyz/foo/b3]])) + eq(2, path_with_url([[test-abc:\\xyz\foo\b3]])) + eq(0, path_with_url([[-test://xyz/foo/b4]])) + eq(0, path_with_url([[test-://xyz/foo/b5]])) + end) + end) end) diff --git a/third-party/CMakeLists.txt b/third-party/CMakeLists.txt index b0b91df791..2001171378 100644 --- a/third-party/CMakeLists.txt +++ b/third-party/CMakeLists.txt @@ -144,8 +144,8 @@ endif() include(ExternalProject) -set(LIBUV_URL https://github.com/libuv/libuv/archive/v1.42.0.tar.gz) -set(LIBUV_SHA256 371e5419708f6aaeb8656671f89400b92a9bba6443369af1bb70bcd6e4b3c764) +set(LIBUV_URL https://github.com/libuv/libuv/archive/7ae0c9543d0080968766288c73874aee3798ae30.tar.gz) +set(LIBUV_SHA256 02ade646f52221e56f2515f8d0bfb8099204d21f6973b2a139bc726807ea803c) set(MSGPACK_URL https://github.com/msgpack/msgpack-c/releases/download/cpp-3.0.0/msgpack-3.0.0.tar.gz) set(MSGPACK_SHA256 bfbb71b7c02f806393bc3cbc491b40523b89e64f83860c58e3e54af47de176e4) @@ -163,15 +163,15 @@ 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) +set(LUV_VERSION 1.43.0-0) set(LUV_URL https://github.com/luvit/luv/archive/${LUV_VERSION}.tar.gz) -set(LUV_SHA256 a55563e6da9294ea26f77a2111598aa49188c5806b8bd1e1f4bdf402f340713e) +set(LUV_SHA256 a36865f34db029e2caa01245a41341a067038c09e94459b50db1346d9fdf82f0) set(LUA_COMPAT53_URL https://github.com/keplerproject/lua-compat-5.3/archive/v0.9.tar.gz) set(LUA_COMPAT53_SHA256 ad05540d2d96a48725bb79a1def35cf6652a4e2ec26376e2617c8ce2baa6f416) @@ -203,8 +203,8 @@ set(LIBICONV_SHA256 ccf536620a45458d26ba83887a983b96827001e92a13847b45e4925cc891 set(TREESITTER_C_URL https://github.com/tree-sitter/tree-sitter-c/archive/v0.20.1.tar.gz) set(TREESITTER_C_SHA256 ffcc2ef0eded59ad1acec9aec4f9b0c7dd209fc1a85d85f8b0e81298e3dddcc2) -set(TREESITTER_URL https://github.com/tree-sitter/tree-sitter/archive/25f64e1eb66bb1ab3eccd4f0b7da543005f3ba79.tar.gz) -set(TREESITTER_SHA256 4f43bad474df494d00a779157f1c437638987d0e7896f9c73492cfeef35366b5) +set(TREESITTER_URL https://github.com/tree-sitter/tree-sitter/archive/v0.20.3.tar.gz) +set(TREESITTER_SHA256 ab52fe93e0c658cff656b9d10d67cdd29084247052964eba13ed6f0e9fa3bd36) if(USE_BUNDLED_UNIBILIUM) include(BuildUnibilium) diff --git a/third-party/cmake/BuildGettext.cmake b/third-party/cmake/BuildGettext.cmake index 9357456343..6128ecfa69 100644 --- a/third-party/cmake/BuildGettext.cmake +++ b/third-party/cmake/BuildGettext.cmake @@ -21,6 +21,7 @@ if(MSVC) -DCMAKE_TOOLCHAIN_FILE=${TOOLCHAIN} -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DCMAKE_GENERATOR=${CMAKE_GENERATOR} + -DCMAKE_GENERATOR_PLATFORM=${CMAKE_GENERATOR_PLATFORM} -DLIBICONV_INCLUDE_DIRS=${DEPS_INSTALL_DIR}/include -DLIBICONV_LIBRARIES=${DEPS_LIB_DIR}/${CMAKE_STATIC_LIBRARY_PREFIX}libcharset${CMAKE_STATIC_LIBRARY_SUFFIX}$<SEMICOLON>${DEPS_LIB_DIR}/${CMAKE_STATIC_LIBRARY_PREFIX}libiconv${CMAKE_STATIC_LIBRARY_SUFFIX} BUILD_COMMAND ${CMAKE_COMMAND} --build . --config ${CMAKE_BUILD_TYPE} diff --git a/third-party/cmake/BuildGperf.cmake b/third-party/cmake/BuildGperf.cmake index 71c3cc1eef..5401191150 100644 --- a/third-party/cmake/BuildGperf.cmake +++ b/third-party/cmake/BuildGperf.cmake @@ -57,6 +57,7 @@ elseif(MSVC OR MINGW) -DCMAKE_C_COMPILER=${CMAKE_C_COMPILER} -DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER} -DCMAKE_GENERATOR=${CMAKE_GENERATOR} + -DCMAKE_GENERATOR_PLATFORM=${CMAKE_GENERATOR_PLATFORM} -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DCMAKE_INSTALL_PREFIX=${DEPS_INSTALL_DIR} BUILD_COMMAND ${CMAKE_COMMAND} --build . --config ${CMAKE_BUILD_TYPE} diff --git a/third-party/cmake/BuildLibiconv.cmake b/third-party/cmake/BuildLibiconv.cmake index dc3d8fe4c3..5ff33e0cd3 100644 --- a/third-party/cmake/BuildLibiconv.cmake +++ b/third-party/cmake/BuildLibiconv.cmake @@ -21,6 +21,7 @@ if(MSVC) -DCMAKE_TOOLCHAIN_FILE=${TOOLCHAIN} -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DCMAKE_GENERATOR=${CMAKE_GENERATOR} + -DCMAKE_GENERATOR_PLATFORM=${CMAKE_GENERATOR_PLATFORM} BUILD_COMMAND ${CMAKE_COMMAND} --build . --config ${CMAKE_BUILD_TYPE} INSTALL_COMMAND ${CMAKE_COMMAND} --build . --target install --config ${CMAKE_BUILD_TYPE}) diff --git a/third-party/cmake/BuildLibtermkey.cmake b/third-party/cmake/BuildLibtermkey.cmake index 10e98fbab3..d44e09d734 100644 --- a/third-party/cmake/BuildLibtermkey.cmake +++ b/third-party/cmake/BuildLibtermkey.cmake @@ -22,6 +22,7 @@ ExternalProject_Add(libtermkey # Hack to avoid -rdynamic in Mingw -DCMAKE_SHARED_LIBRARY_LINK_C_FLAGS="" -DCMAKE_GENERATOR=${CMAKE_GENERATOR} + -DCMAKE_GENERATOR_PLATFORM=${CMAKE_GENERATOR_PLATFORM} -DUNIBILIUM_INCLUDE_DIRS=${DEPS_INSTALL_DIR}/include -DUNIBILIUM_LIBRARIES=${DEPS_LIB_DIR}/${CMAKE_STATIC_LIBRARY_PREFIX}unibilium${CMAKE_STATIC_LIBRARY_SUFFIX} BUILD_COMMAND ${CMAKE_COMMAND} --build . --config ${CMAKE_BUILD_TYPE} diff --git a/third-party/cmake/BuildLibuv.cmake b/third-party/cmake/BuildLibuv.cmake index dce64c777b..42650308a8 100644 --- a/third-party/cmake/BuildLibuv.cmake +++ b/third-party/cmake/BuildLibuv.cmake @@ -63,10 +63,6 @@ elseif(WIN32) set(BUILD_SHARED ON) elseif(MINGW) set(BUILD_SHARED OFF) - set(LIBUV_PATCH_COMMAND - ${GIT_EXECUTABLE} -C ${DEPS_BUILD_DIR}/src/libuv init - COMMAND ${GIT_EXECUTABLE} -C ${DEPS_BUILD_DIR}/src/libuv apply --ignore-whitespace - ${CMAKE_CURRENT_SOURCE_DIR}/patches/libuv-disable-typedef-MinGW.patch) else() message(FATAL_ERROR "Trying to build libuv in an unsupported system ${CMAKE_SYSTEM_NAME}/${CMAKE_C_COMPILER_ID}") endif() @@ -77,6 +73,7 @@ elseif(WIN32) COMMAND ${CMAKE_COMMAND} ${DEPS_BUILD_DIR}/src/libuv/CMakeLists.txt -DCMAKE_C_COMPILER=${CMAKE_C_COMPILER} -DCMAKE_GENERATOR=${CMAKE_GENERATOR} + -DCMAKE_GENERATOR_PLATFORM=${CMAKE_GENERATOR_PLATFORM} -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DBUILD_SHARED_LIBS=${BUILD_SHARED} -DCMAKE_INSTALL_PREFIX=${DEPS_INSTALL_DIR} diff --git a/third-party/cmake/BuildLibvterm.cmake b/third-party/cmake/BuildLibvterm.cmake index 09f2ba7f2c..2c300dda7c 100644 --- a/third-party/cmake/BuildLibvterm.cmake +++ b/third-party/cmake/BuildLibvterm.cmake @@ -47,6 +47,7 @@ if(WIN32) COMMAND ${CMAKE_COMMAND} ${DEPS_BUILD_DIR}/src/libvterm -DCMAKE_INSTALL_PREFIX=${DEPS_INSTALL_DIR} -DCMAKE_C_COMPILER=${CMAKE_C_COMPILER} + -DCMAKE_GENERATOR_PLATFORM=${CMAKE_GENERATOR_PLATFORM} "-DCMAKE_C_FLAGS:STRING=${CMAKE_C_COMPILER_ARG1} -fPIC" -DCMAKE_GENERATOR=${CMAKE_GENERATOR}) set(LIBVTERM_BUILD_COMMAND ${CMAKE_COMMAND} --build . --config ${CMAKE_BUILD_TYPE}) diff --git a/third-party/cmake/BuildLuv.cmake b/third-party/cmake/BuildLuv.cmake index 69f3b60ecf..99822249c2 100644 --- a/third-party/cmake/BuildLuv.cmake +++ b/third-party/cmake/BuildLuv.cmake @@ -104,6 +104,7 @@ elseif(MSVC) set(LUV_CONFIGURE_COMMAND ${LUV_CONFIGURE_COMMAND_COMMON} -DCMAKE_C_COMPILER=${CMAKE_C_COMPILER} + -DCMAKE_GENERATOR_PLATFORM=${CMAKE_GENERATOR_PLATFORM} # Same as Unix without fPIC "-DCMAKE_C_FLAGS:STRING=${CMAKE_C_COMPILER_ARG1} ${LUV_INCLUDE_FLAGS}" # Make sure we use the same generator, otherwise we may diff --git a/third-party/cmake/BuildMsgpack.cmake b/third-party/cmake/BuildMsgpack.cmake index 39a8a64d23..ee4f0eb080 100644 --- a/third-party/cmake/BuildMsgpack.cmake +++ b/third-party/cmake/BuildMsgpack.cmake @@ -63,6 +63,7 @@ elseif(MSVC) -DMSGPACK_BUILD_EXAMPLES=OFF -DCMAKE_INSTALL_PREFIX=${DEPS_INSTALL_DIR} -DCMAKE_C_COMPILER=${CMAKE_C_COMPILER} + -DCMAKE_GENERATOR_PLATFORM=${CMAKE_GENERATOR_PLATFORM} "-DCMAKE_C_FLAGS:STRING=${CMAKE_C_COMPILER_ARG1}" -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} # Make sure we use the same generator, otherwise we may diff --git a/third-party/cmake/BuildTreesitter.cmake b/third-party/cmake/BuildTreesitter.cmake index 0aa2706d7d..01fdb837e2 100644 --- a/third-party/cmake/BuildTreesitter.cmake +++ b/third-party/cmake/BuildTreesitter.cmake @@ -42,6 +42,7 @@ if(MSVC) COMMAND ${CMAKE_COMMAND} ${DEPS_BUILD_DIR}/src/tree-sitter/CMakeLists.txt -DCMAKE_C_COMPILER=${CMAKE_C_COMPILER} -DCMAKE_GENERATOR=${CMAKE_GENERATOR} + -DCMAKE_GENERATOR_PLATFORM=${CMAKE_GENERATOR_PLATFORM} -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DCMAKE_INSTALL_PREFIX=${DEPS_INSTALL_DIR} BUILD_COMMAND ${CMAKE_COMMAND} --build . --config ${CMAKE_BUILD_TYPE} diff --git a/third-party/cmake/BuildTreesitterParsers.cmake b/third-party/cmake/BuildTreesitterParsers.cmake index 5284a7fd62..f966d640e6 100644 --- a/third-party/cmake/BuildTreesitterParsers.cmake +++ b/third-party/cmake/BuildTreesitterParsers.cmake @@ -17,6 +17,7 @@ CMAKE_ARGS -DCMAKE_C_COMPILER=${CMAKE_C_COMPILER} -DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER} -DCMAKE_GENERATOR=${CMAKE_GENERATOR} + -DCMAKE_GENERATOR_PLATFORM=${CMAKE_GENERATOR_PLATFORM} -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DCMAKE_INSTALL_PREFIX=${DEPS_INSTALL_DIR} # Pass toolchain diff --git a/third-party/cmake/BuildUnibilium.cmake b/third-party/cmake/BuildUnibilium.cmake index 74c1cbddb0..2f940bdfd3 100644 --- a/third-party/cmake/BuildUnibilium.cmake +++ b/third-party/cmake/BuildUnibilium.cmake @@ -18,6 +18,7 @@ if(WIN32) -DCMAKE_INSTALL_PREFIX=${DEPS_INSTALL_DIR} # Pass toolchain -DCMAKE_TOOLCHAIN_FILE=${TOOLCHAIN} + -DCMAKE_GENERATOR_PLATFORM=${CMAKE_GENERATOR_PLATFORM} -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DCMAKE_GENERATOR=${CMAKE_GENERATOR} BUILD_COMMAND ${CMAKE_COMMAND} --build . --config ${CMAKE_BUILD_TYPE} diff --git a/third-party/patches/libuv-disable-typedef-MinGW.patch b/third-party/patches/libuv-disable-typedef-MinGW.patch deleted file mode 100644 index a47893cede..0000000000 --- a/third-party/patches/libuv-disable-typedef-MinGW.patch +++ /dev/null @@ -1,19 +0,0 @@ -diff --git a/include/uv/win.h b/include/uv/win.h -index f5f1d3a3..64a0dfd9 100644 ---- a/include/uv/win.h -+++ b/include/uv/win.h -@@ -45,7 +45,14 @@ typedef struct pollfd { - #endif - - #include <mswsock.h> -+// Disable the typedef in mstcpip.h of MinGW. -+#define _TCP_INITIAL_RTO_PARAMETERS _TCP_INITIAL_RTO_PARAMETERS__ -+#define TCP_INITIAL_RTO_PARAMETERS TCP_INITIAL_RTO_PARAMETERS__ -+#define PTCP_INITIAL_RTO_PARAMETERS PTCP_INITIAL_RTO_PARAMETERS__ - #include <ws2tcpip.h> -+#undef _TCP_INITIAL_RTO_PARAMETERS -+#undef TCP_INITIAL_RTO_PARAMETERS -+#undef PTCP_INITIAL_RTO_PARAMETERS - #include <windows.h> - - #include <process.h> |