diff options
817 files changed, 78510 insertions, 47129 deletions
diff --git a/.builds/freebsd.yml b/.builds/freebsd.yml index d5809c42cf..2d06b1e685 100644 --- a/.builds/freebsd.yml +++ b/.builds/freebsd.yml @@ -1,11 +1,10 @@ -image: freebsd/12.x +image: freebsd/latest packages: - cmake - gmake - ninja - libtool -- sha - automake - pkgconf - unzip @@ -13,6 +12,7 @@ packages: - gettext - python - libffi +- gdb sources: - https://github.com/neovim/neovim @@ -35,10 +35,6 @@ tasks: - unittest: | cd neovim gmake unittest - -# Unfortunately, oldtest is tanking hard on sourcehut's FreeBSD instance -# and not producing any logs as a result. So don't do this task for now. -# Ref: https://github.com/neovim/neovim/pull/11477#discussion_r352095005. -# - test-oldtest: | -# cd neovim -# gmake oldtest +- oldtest: | + cd neovim + gmake oldtest diff --git a/.builds/openbsd.yml b/.builds/openbsd.yml index 422fa366b6..0aaa003820 100644 --- a/.builds/openbsd.yml +++ b/.builds/openbsd.yml @@ -1,6 +1,6 @@ # sourcehut CI: https://builds.sr.ht/~jmk/neovim -image: openbsd/6.9 +image: openbsd/latest packages: - autoconf-2.71 @@ -12,6 +12,7 @@ packages: - libtool - ninja-1.10.2p0 - unzip-6.0p14 +- gdb sources: - https://github.com/neovim/neovim diff --git a/.clang-format b/.clang-format index c86f5a3ddf..d5e328f461 100644 --- a/.clang-format +++ b/.clang-format @@ -2,10 +2,10 @@ BasedOnStyle: Google Language: Cpp ColumnLimit: 100 IndentWidth: 2 -TabWidth: 2 +TabWidth: 8 UseTab: Never -IndentCaseLabels: true -BreakBeforeBraces: Linux +IndentCaseLabels: false +BreakBeforeBraces: Custom AlignEscapedNewlinesLeft: false AllowShortFunctionsOnASingleLine: false AlignTrailingComments: true @@ -17,4 +17,24 @@ AllowShortLoopsOnASingleLine: false BinPackParameters: false BreakBeforeBinaryOperators: true BreakBeforeTernaryOperators: true -ContinuationIndentWidth: 4 +ContinuationIndentWidth: 2 +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: No +AlwaysBreakTemplateDeclarations: No +AlignEscapedNewlines: DontAlign +BinPackArguments: false +BraceWrapping: + AfterClass: false + AfterControlStatement: false + AfterEnum: false + AfterFunction: true + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + BeforeCatch: false + BeforeElse: false + IndentBraces: false +PointerAlignment: Right +SortIncludes: false +Cpp11BracedListStyle: false diff --git a/.editorconfig b/.editorconfig index bb6a1423ef..22fee54b22 100644 --- a/.editorconfig +++ b/.editorconfig @@ -8,6 +8,9 @@ end_of_line = lf insert_final_newline = true charset = utf-8 +[*.{c,h,in,lua}] +max_line_length = 100 + [{Makefile,**/Makefile,runtime/doc/*.txt}] indent_style = tab indent_size = 8 diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs index bbca6b3339..fce35b54ee 100644 --- a/.git-blame-ignore-revs +++ b/.git-blame-ignore-revs @@ -36,3 +36,18 @@ d90fb1c0bfc1e64c783c385a79e7de87013dadba 9c268263b1792d00b3ffdfd7495af2575862656e 8c74c895b300bcee5fa937a2329d1d4756567b42 40be47e0faef7aa015eb4ba44ceb1ee1a03e97cf +4472c56d54f447040f6e8610b261b7efa0d04eb6 +a68faed02dc8e37b8f10da14dc02e33e6ed93947 +725cbe7d414f609e769081276f2a034e32a4337b +7e3bdc75e44b9139d8afaea4381b53ae78b15746 +4ba12b3dda34472c193c9fa8ffd7d3bd5b6c04d6 +849f104c2789c884428fd45501912c6591a78e12 +38dd53c525054daf83dba27d7d46e90e8b41fa50 +6059784770c4c88fb6fe528b9f7634192fa1164e +ee031eb5256bb83e0d6add2bae6fd943a4186ffe +69e11b58b4db0952f11a5ff85aa7150b5f5b8db8 +271bb32855853b011fceaf0ad2f829bce66b2a19 + +# typos +d238b8f6003d34cae7f65ff7585b48a2cd9449fb +4547137aaff32b20172870a549d3a28a3c7adf1c 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..2e3aa8f875 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -49,9 +49,10 @@ "build": - CMakeLists.txt - "**/CMakeLists.txt" + - "**/Makefile" - "**/*.cmake" -"tests": +"test": - all: ["test/**/*"] "ci": @@ -59,3 +60,6 @@ - .github/workflows/**/* - .builds/* - ci/**/* + +"filetype": + - runtime/lua/vim/filetype.lua diff --git a/.github/scripts/remove-reviewers.js b/.github/scripts/remove-reviewers.js new file mode 100644 index 0000000000..631f08e57d --- /dev/null +++ b/.github/scripts/remove-reviewers.js @@ -0,0 +1,16 @@ +module.exports = async ({github, context}) => { + const requestedReviewers = await github.rest.pulls.listRequestedReviewers({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: context.issue.number + }); + + const reviewers = requestedReviewers.data.users.map(e => e.login) + + github.rest.pulls.removeRequestedReviewers({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: context.issue.number, + reviewers: reviewers + }); +} diff --git a/.github/scripts/reviews.js b/.github/scripts/reviews.js new file mode 100644 index 0000000000..e227b62c8c --- /dev/null +++ b/.github/scripts/reviews.js @@ -0,0 +1,95 @@ +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('build')) { + reviewers.add("jamessan") + } + + 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('dependencies')) { + reviewers.add("jamessan") + } + + 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("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-check.yml b/.github/workflows/api-docs-check.yml new file mode 100644 index 0000000000..f76c035de4 --- /dev/null +++ b/.github/workflows/api-docs-check.yml @@ -0,0 +1,20 @@ +name: Missing API docs +on: + pull_request: + types: [opened, synchronize, reopened, ready_for_review] + branches-ignore: + - 'marvim/api-doc-update**' + paths: + - 'src/nvim/api/*.[ch]' + - 'runtime/lua/**.lua' + - 'runtime/doc/**' + +jobs: + call-regen-api-docs: + if: github.event.pull_request.draft == false + permissions: + contents: write + pull-requests: write + uses: ./.github/workflows/api-docs.yml + with: + check_only: true diff --git a/.github/workflows/api-docs.yml b/.github/workflows/api-docs.yml index 413c2e90c6..ce8ed7996a 100644 --- a/.github/workflows/api-docs.yml +++ b/.github/workflows/api-docs.yml @@ -1,14 +1,23 @@ +# Autogenerate the API docs on new commit to important branches +# Also work as a check for PR's to not forget committing their doc changes +# called from api-docs-check.yml name: Autogenerate API docs on: push: paths: - 'src/nvim/api/*.[ch]' - - 'src/nvim/**.lua' - 'runtime/lua/**.lua' + - 'runtime/doc/**' branches: - 'master' - 'release-[0-9]+.[0-9]+' workflow_dispatch: + workflow_call: + inputs: + check_only: + type: boolean + default: false + required: false jobs: regen-api-docs: @@ -26,7 +35,9 @@ jobs: - name: Install dependencies run: | sudo apt-get update - sudo env DEBIAN_FRONTEND=noninteractive apt-get install -y doxygen python3 python3-msgpack luajit + sudo env DEBIAN_FRONTEND=noninteractive apt-get install -y python3 luajit + conda install -c conda-forge doxygen=1.9.2 msgpack-python + echo "$CONDA/bin" >> $GITHUB_PATH - name: Setup git config run: | @@ -42,10 +53,18 @@ jobs: python3 scripts/gen_vimdoc.py printf '::set-output name=UPDATED_DOCS::%s\n' $([ -z "$(git diff)" ]; echo $?) + - name: FAIL, PR has not committed doc changes + if: ${{ steps.docs.outputs.UPDATED_DOCS != 0 && inputs.check_only }} + run: | + echo "Job failed, run ./scripts/gen_vimdoc.py and commit your doc changes" + echo "The doc generation produces the following changes:" + git --no-pager diff + exit 1 + - name: Automatic PR - if: ${{ steps.docs.outputs.UPDATED_DOCS != 0 }} + if: ${{ steps.docs.outputs.UPDATED_DOCS != 0 && !inputs.check_only }} 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/backport.yml b/.github/workflows/backport.yml index 25f8414008..d281bb1404 100644 --- a/.github/workflows/backport.yml +++ b/.github/workflows/backport.yml @@ -12,7 +12,7 @@ jobs: name: Backport Pull Request if: > github.repository_owner == 'neovim' && ( - github.event_name == 'pull_request' && + github.event_name == 'pull_request_target' && github.event.pull_request.merged ) || ( github.event_name == 'issue_comment' && diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d07b9fdac7..ea3185d2a1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,24 +9,114 @@ 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: + group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.head_ref || github.sha }} + cancel-in-progress: true jobs: - unixish: + lint: + # This job tests two things: it lints the code but also builds neovim using + # system dependencies instead of bundled dependencies. This is to make sure + # we are able to build neovim without pigeonholing ourselves into specifics + # of the bundled dependencies. + + 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_nvim + + - if: "!cancelled()" + name: clint-full + run: ./ci/run_lint.sh clint-full + + - 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: check-single-includes + run: ./ci/run_lint.sh check-single-includes + + - name: Cache dependencies + run: ./ci/before_cache.sh + + posix: name: ${{ matrix.runner }} ${{ matrix.flavor }} (cc=${{ matrix.cc }}) strategy: fail-fast: false 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 @@ -35,6 +125,17 @@ jobs: - cc: clang runner: macos-11.0 os: osx + + # The functionaltest-lua test two things simultaneously: + # 1. Check that the tests pass with PUC Lua instead of LuaJIT. + # 2. Use as oldest/minimum versions of dependencies/build tools we + # still explicitly support so we don't accidentally rely on + # features that is only available on later versions. + - 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 @@ -53,27 +154,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 upgrade - brew install automake ccache perl cpanminus ninja + brew update --quiet + brew install automake ccache 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 @@ -86,15 +200,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_nvim + + - 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) }} @@ -117,69 +247,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 559d8eae83..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.3.1 + - 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..7845ae92a3 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,24 @@ 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 + - name: "Extract if the PR is a breaking change and add it as label" + run: gh pr edit "$PR_NUMBER" --add-label "$(echo "$PR_TITLE" | sed -E 's|[[:alpha:]]+(\(.*\))?!:.*|breaking-change|')" || 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..3bd6c739a5 100644 --- a/.github/workflows/notes.md +++ b/.github/workflows/notes.md @@ -6,8 +6,17 @@ ${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. Search and run `nvim-qt.exe` or run `nvim.exe` on your CLI of choice. ### macOS @@ -17,6 +26,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 +54,11 @@ ${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} ``` diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f1ed05e6cb..be93cd0245 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/nvim-linux64.tar.gz + retention-days: 1 - uses: actions/upload-artifact@v2 with: name: nvim-linux64 - path: build/release/nvim-linux64.tar.gz + path: build/nvim-linux64.deb retention-days: 1 appimage: @@ -80,9 +85,7 @@ jobs: fetch-depth: 0 - name: Install brew packages run: | - rm -f /usr/local/bin/2to3 - brew update >/dev/null - brew upgrade + brew update --quiet brew install automake ninja - if: github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && github.event.inputs.tag_name != 'nightly') run: printf 'NVIM_BUILD_TYPE=Release\n' >> $GITHUB_ENV @@ -116,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) }} @@ -133,12 +136,16 @@ 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 publish: needs: [linux, appimage, macOS, windows] @@ -189,7 +196,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 @@ -209,11 +218,14 @@ 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 - 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/remove-reviewers-on-draft.yml b/.github/workflows/remove-reviewers-on-draft.yml new file mode 100644 index 0000000000..64474618b8 --- /dev/null +++ b/.github/workflows/remove-reviewers-on-draft.yml @@ -0,0 +1,17 @@ +name: "Remove reviewers" +on: + pull_request_target: + types: [converted_to_draft, closed] +jobs: + remove-reviewers: + runs-on: ubuntu-latest + permissions: + pull-requests: write + steps: + - uses: actions/checkout@v2 + - name: 'Remove reviewers' + uses: actions/github-script@v6 + with: + script: | + const script = require('./.github/scripts/remove-reviewers.js') + await script({github, context}) diff --git a/.github/workflows/reviews.yml b/.github/workflows/reviews.yml new file mode 100644 index 0000000000..964f57b871 --- /dev/null +++ b/.github/workflows/reviews.yml @@ -0,0 +1,18 @@ +name: "Request reviews" +on: + pull_request_target: + types: [labeled, ready_for_review] +jobs: + request-reviewer: + if: github.event.pull_request.state == 'open' && github.event.pull_request.draft == false + runs-on: ubuntu-latest + permissions: + pull-requests: write + steps: + - uses: actions/checkout@v2 + - name: 'Request reviewers' + uses: actions/github-script@v6 + with: + script: | + const script = require('./.github/scripts/reviews.js') + await script({github, context}) diff --git a/.github/workflows/squash-typos.yml b/.github/workflows/squash-typos.yml deleted file mode 100644 index 6779589dc6..0000000000 --- a/.github/workflows/squash-typos.yml +++ /dev/null @@ -1,33 +0,0 @@ -name: Squash Typo Pull Requests - -on: - pull_request_target: - types: labeled -concurrency: - group: ${{ github.workflow }} -jobs: - build: - if: github.event.label.name == 'typo' - runs-on: ubuntu-latest - - permissions: - contents: write - pull-requests: write - - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - steps: - - uses: actions/checkout@v2 - with: - fetch-depth: 0 - - uses: actions/setup-python@v2 - - - name: Setup git config - run: | - git config --global user.name 'marvim' - git config --global user.email 'marvim@users.noreply.github.com' - - - run: python scripts/squash_typos.py - env: - PR_NUMBER: ${{ github.event.number }} 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 e07ce4906e..11479346c7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ # Tools /venv/ compile_commands.json +/.luarc.json # IDEs /.vs/ @@ -14,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/.mailmap b/.mailmap new file mode 100644 index 0000000000..1aa9aa36d8 --- /dev/null +++ b/.mailmap @@ -0,0 +1,134 @@ +Abdelhakeem <abdelhakeem.osama@hotmail.com> Abdelhakeem Osama +Alvaro Muñoz <alvaro@pwntester.com> Alvaro Muñoz +Andreas Johansson <andreas@ndrs.xyz> <ndreas@users.noreply.github.com> +Andrew Pyatkov <mrbiggfoot@gmail.com> <mrbiggfoot@users.noreply.github.com> +Anmol Sethi <hi@nhooyr.io> <anmol@aubble.com> +Anmol Sethi <hi@nhooyr.io> <me@anmol.io> +Anmol Sethi <hi@nhooyr.io> <nhooyr@users.noreply.github.com> +BK1603 <chouhan.shreyansh2702@gmail.com> Shreyansh Chouhan +Billy Su <g4691821@gmail.com> Billy SU +Billy Vong <billyvg@gmail.com> <billyvg@users.noreply.github.com> +Björn Linse <bjorn.linse@gmail.com> bfredl +Carlos Hernandez <carlos@techbyte.ca> <hurricanehrndz@users.noreply.github.com> +Chris Kipp <ckipp@pm.me> ckipp01 +Christian Clason <c.clason@uni-graz.at> <christian.clason@uni-due.de> +Cédric Barreteau <> <cbarrete@users.noreply.github.com> +Dan Aloni <alonid@gmail.com> <dan@kernelim.com> +Daniel Hahler <git@thequod.de> <github@thequod.de> +Eisuke Kawashima <e-kwsm@users.noreply.github.com> E Kawashima +ElPiloto <luis.r.piloto@gmail.com> Luis Piloto +Eliseo Martínez <eliseomarmol@gmail.com> Eliseo Martínez +Fabian Viöl <f.vioel@googlemail.com> Fabian +Florian Walch <florian@fwalch.com> <fwalch@users.noreply.github.com> +Gabriel Cruz <gabs.oficial98@gmail.com> <LTKills@users.noreply.github.com> +Gaelan Steele <gbs@canishe.com> Gaelan +Gavin D. Howard <gavin@schedmd.com> <yzena.tech@gmail.com> +George Zhao <zhaozg@gmail.com> <zhaozg@aliyun.com> +George Zhao <zhaozg@gmail.com> George Zhao +Gregory Anders <greg@gpanders.com> <8965202+gpanders@users.noreply.github.com> +Gregory Anders <greg@gpanders.com> Greg Anders +Grzegorz Milka <grzegorzmilka@gmail.com> Grzegorz +Harm te Hennepe <dhtehennepe@gmail.com> <d.h.tehennepe@student.utwente.nl> +Harm te Hennepe <dhtehennepe@gmail.com> <harm@tehennepe.org> +Hirokazu Hata <h.hata.ai.t@gmail.com> <h-michael@users.noreply.github.com> +Ihor Antonov <ngortheone@gmail.com> <ngortheone@users.noreply.github.com> +J Phani Mahesh <phanimahesh@gmail.com> <github@phanimahesh.me> +Jack Bracewell <FriedSock@users.noreply.github.com> <jack.bracewell@unboxedconsulting.com> +Jack Bracewell <FriedSock@users.noreply.github.com> <jbtwentythree@gmail.com> +Jacques Germishuys <jacquesg@striata.com> <jacquesg@users.noreply.github.com> +Jakub Łuczyński <doubleloop@o2.pl> <doubleloop@users.noreply.github.com> +James McCoy <jamessan@jamessan.com> <vega.james@gmail.com> +Jan Edmund Lazo <jan.lazo@mail.utoronto.ca> <janedmundlazo@hotmail.com> +Jan Viljanen <jan.a.viljanen@gmail.com> <jan.viljanen@greenpeace.org> +Javier Lopez <graulopezjavier@gmail.com> Javier López +Jit Yao Yap <jityao@gmail.com> <jityao+github@gmail.com> +Jit Yao Yap <jityao@gmail.com> Jit +John Gehrig <jdg.gehrig@gmail.com> <jgehrig@users.noreply.github.com> +John Schmidt <john.schmidt.h@gmail.com> John +John Szakmeister <john@szakmeister.net> <jszakmeister@users.noreply.github.com> +Jonathan de Boyne Pollard <J.deBoynePollard-newsgroups@NTLWorld.com> <jdebp@users.noreply.github.com> +Jonathan de Boyne Pollard <J.deBoynePollard-newsgroups@NTLWorld.com> <postmaster@localhost> +Jurica Bradaric <jbradaric@gmail.com> <jbradaric@users.noreply.github.com> +Jurica Bradaric <jbradaric@gmail.com> <jurica.bradaric@avl.com> +KillTheMule <KillTheMule@users.noreply.github.com> <github@pipsfrank.de> +Kwon-Young Choi <kwon-young.choi@hotmail.fr> Kwon-Young +Lucas Hoffmann <l-m-h@web.de> <lucc@posteo.de> +Lucas Hoffmann <l-m-h@web.de> <lucc@users.noreply.github.com> +Marco Hinz <mh.codebro@gmail.com> <mh.codebro+github@gmail.com> +Marvim the Paranoid Android <marvim@users.noreply.github.com> marvim +Mateusz Czapliński <czapkofan@gmail.com> Mateusz Czaplinski +Mathias Fussenegger <f.mathias@zignar.net> <mfussenegger@users.noreply.github.com> +Mathias Fussenegger <f.mathias@zignar.net> Mathias Fußenegger +Matt Wozniski <godlygeek@gmail.com> <godlygeek+git@gmail.com> +Matthieu Coudron <mattator@gmail.com> <coudron@iij.ad.jp> +Matthieu Coudron <mattator@gmail.com> <matthieu.coudron@upmc.fr> +Matthieu Coudron <mattator@gmail.com> <mcoudron@hotmail.com> +Matthieu Coudron <mattator@gmail.com> <teto@users.noreply.github.com> +MichaHoffmann <michoffmann.potsdam@gmail.com> Michael Hoffmann +MichaHoffmann <michoffmann.potsdam@gmail.com> micha +Michael Ennen <mike.ennen@gmail.com> <brcolow@users.noreply.github.com> +Michael Ennen <mike.ennen@gmail.com> brcolow +Michael Reed <m.reed@mykolab.com> <Pyrohh@users.noreply.github.com> +Michael Schupikov <michael@schupikov.de> <DarkDeepBlue@users.noreply.github.com> +Nicolas Hillegeer <nicolas@hillegeer.com> <nicolashillegeer@gmail.com> +Panashe M. Fundira <fundirap@gmail.com> Panashe Fundira +Patrice Peterson <patrice.peterson@mailbox.org> runiq +Pavel Platto <hinidu@gmail.com> Hinidu +Petter Wahlman <petter@wahlman.no> <pwahlman@cisco.com> +Poh Zi How <poh.zihow@gmail.com> pohzipohzi +Rich Wareham <rjw57@cam.ac.uk> <rjw57@cantab.net> +Rui Abreu Ferreira <equalsraf@users.noreply.github.com> @equalsraf +Rui Abreu Ferreira <raf-ep@gmx.com> <equalsraf@users.noreply.github.com> +Rui Abreu Ferreira <raf-ep@gmx.com> <rap-ep@gmx.com> +Sam Wilson <tecywiz121@hotmail.com> <sawilson@akamai.com> +Sander Bosma <sanderbosma@gmail.com> sander2 +Santos Gallegos <stsewd@protonmail.com> <santos_g@outlook.com> +Sebastian Parborg <darkdefende@gmail.com> DarkDefender +Shirasaka <tk.shirasaka@gmail.com> tk-shirasaka +Shota <shotat@users.noreply.github.com> shotat +Shougo Matsushita <Shougo.Matsu@gmail.com> Shougo +Stephan Seitz <stephan.seitz@fau.de> <stephan.lauf@yahoo.de> +Steven Sojka <Steven.Sojka@tdameritrade.com> <steelsojka@gmail.com> +Steven Sojka <steelsojka@gmail.com> <steelsojka@users.noreply.github.com> +TJ DeVries <devries.timothyj@gmail.com> <timothydvrs1234@gmail.com> +Thomas Fehér <thomas.feher@yahoo.de> <thomasfeher@web.de> +Thomas Vigouroux <tomvig38@gmail.com> <39092278+vigoux@users.noreply.github.com> +Utkarsh Maheshwari <UtkarshME96@gmail.com> UTkarsh Maheshwari +Utkarsh Maheshwari <utkarshme96@gmail.com> <UtkarshME96@gmail.com> +VVKot <volodymyr.kot.ua@gmail.com> Volodymyr Kot +Victor Adam <victor.adam@cofelyineo-gdfsuez.com> <Victor.Adam@derpymail.org> +Wang Shidong <wsdjeg@outlook.com> <wsdjeg@users.noreply.github.com> +Wei Huang <daviseago@gmail.com> davix +Xu Cheng <xucheng@me.com> <xu-cheng@users.noreply.github.com> +Yamakaky <yamakaky@gmail.com> <yamakaky@yamaworld.fr> +Yegappan Lakshmanan <yegappan@yahoo.com> <4298407+yegappan@users.noreply.github.com> +Yichao Zhou <broken.zhoug@gmail.com> Yichao Zhou <broken.zhou@gmail.com> +Yichao Zhou <broken.zhoug@gmail.com> zhou13 <broken.zhou@gmail.com> +Yorick Peterse <git@yorickpeterse.com> <yorick@yorickpeterse.com> +ZyX <kp-pav@yandex.ru> <kp-pav@ya.ru> +ZyX <kp-pav@yandex.ru> Nikolai Aleksandrovich Pavlov +aph <a.hewson@gmail.com> Ashley Hewson +butwerenotthereyet <58348703+butwerenotthereyet@users.noreply.github.com> We're Yet +chemzqm <chemzqm@gmail.com> Qiming zhao +chentau <tchen1998@gmail.com> Tony Chen +dedmass <carlo.abelli@gmail.com> Carlo Abelli +equal-l2 <eng.equall2@gmail.com> <equal-l2@users.noreply.github.com> +francisco souza <fsouza@users.noreply.github.com> <108725+fsouza@users.noreply.github.com> +glacambre <code@lacamb.re> <me@r4> +glacambre <code@lacamb.re> Ghjuvan Lacambre +ii14 <ii14@users.noreply.github.com> <59243201+ii14@users.noreply.github.com> +jdrouhard <john@jmdtech.org> <github@jmdtech.org> +kuuote <znmxodq1@gmail.com> <36663503+kuuote@users.noreply.github.com> +matveyt <matthewtarasov@gmail.com> <35012635+matveyt@users.noreply.github.com> +nate <nateozemon@gmail.com> nateozem +ray-x <rayx.cn@gmail.com> rayx +relnod <mail@paul-schiffers.de> <relnod@users.noreply.github.com> +rockerBOO <rockerboo@gmail.com> Dave Lage +rpigott <rpigott@berkeley.edu> Ronan Pigott +sach1t <sach0010t@gmail.com> <sach1t@users.noreply.github.com> +shade-of-noon <73705427+shade-of-noon@users.noreply.github.com> Edwin Pujols +shadmansaleh <shadmansaleh3@gmail.com> <13149513+shadmansaleh@users.noreply.github.com> +shadmansaleh <shadmansaleh3@gmail.com> Shadman +sohnryang <loop.infinitely@gmail.com> 손량 +timeyyy <timeyyy_da_man@hotmail.com> Timothy C Eichler +timeyyy <timeyyy_da_man@hotmail.com> timothy eichler diff --git a/CMakeLists.txt b/CMakeLists.txt index f44937b5ae..2aed7d8b48 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,6 +12,9 @@ endif() if(POLICY CMP0060) cmake_policy(SET CMP0060 NEW) endif() +if(POLICY CMP0075) + cmake_policy(SET CMP0075 NEW) +endif() # Point CMake at any custom modules we may ship list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake") @@ -136,12 +139,12 @@ set_property(CACHE CMAKE_BUILD_TYPE PROPERTY # If not in a git repo (e.g., a tarball) these tokens define the complete # version string, else they are combined with the result of `git describe`. set(NVIM_VERSION_MAJOR 0) -set(NVIM_VERSION_MINOR 6) +set(NVIM_VERSION_MINOR 7) set(NVIM_VERSION_PATCH 0) set(NVIM_VERSION_PRERELEASE "-dev") # for package maintainers # API level -set(NVIM_API_LEVEL 8) # Bump this after any API change. +set(NVIM_API_LEVEL 9) # Bump this after any API change. set(NVIM_API_LEVEL_COMPAT 0) # Adjust this after a _breaking_ API change. set(NVIM_API_PRERELEASE true) @@ -281,6 +284,9 @@ if(MSVC) else() add_compile_options(-Wall -Wextra -pedantic -Wno-unused-parameter -Wstrict-prototypes -std=gnu99 -Wshadow -Wconversion + -Wdouble-promotion + -Wmissing-noreturn + -Wmissing-format-attribute -Wmissing-prototypes) check_c_compiler_flag(-Wimplicit-fallthrough HAVE_WIMPLICIT_FALLTHROUGH_FLAG) @@ -288,6 +294,27 @@ else() add_compile_options(-Wimplicit-fallthrough) endif() + # Clang doesn't have -Wsuggest-attribute so check for each one. + check_c_compiler_flag(-Wsuggest-attribute=pure HAVE_WSUGGEST_ATTRIBUTE_PURE) + if(HAVE_WSUGGEST_ATTRIBUTE_PURE) + add_compile_options(-Wsuggest-attribute=pure) + endif() + + check_c_compiler_flag(-Wsuggest-attribute=const HAVE_WSUGGEST_ATTRIBUTE_CONST) + if(HAVE_WSUGGEST_ATTRIBUTE_CONST) + add_compile_options(-Wsuggest-attribute=const) + endif() + + check_c_compiler_flag(-Wsuggest-attribute=malloc HAVE_WSUGGEST_ATTRIBUTE_MALLOC) + if(HAVE_WSUGGEST_ATTRIBUTE_MALLOC) + add_compile_options(-Wsuggest-attribute=malloc) + endif() + + check_c_compiler_flag(-Wsuggest-attribute=cold HAVE_WSUGGEST_ATTRIBUTE_COLD) + if(HAVE_WSUGGEST_ATTRIBUTE_COLD) + add_compile_options(-Wsuggest-attribute=cold) + endif() + # On FreeBSD 64 math.h uses unguarded C11 extension, which taints clang # 3.4.1 used there. if(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD" AND CMAKE_C_COMPILER_ID MATCHES "Clang") @@ -387,7 +414,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 +435,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,6 +583,33 @@ endif() message(STATUS "Using Lua interpreter: ${LUA_PRG}") +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) + foreach(CURRENT_LUAC_PRG luac5.1 luac) + find_program(_CHECK_LUAC_PRG ${CURRENT_LUAC_PRG}) + if(_CHECK_LUAC_PRG) + set(LUAC_PRG "${_CHECK_LUAC_PRG} -s -o - %s" CACHE STRING "Format for compiling to Lua bytecode") + break() + endif() + endforeach() + elseif(LUA_PRG MATCHES "luajit") + check_lua_module(${LUA_PRG} "jit.bcsave" LUAJIT_HAS_JIT_BCSAVE) + if(LUAJIT_HAS_JIT_BCSAVE) + set(LUAC_PRG "${LUA_PRG} -b -s %s -" CACHE STRING "Format for compiling to Lua bytecode") + endif() + endif() +endif() + +if(LUAC_PRG) + message(STATUS "Using Lua compiler: ${LUAC_PRG}") +endif() + # Setup busted. find_program(BUSTED_PRG NAMES busted busted.bat) find_program(BUSTED_LUA_PRG busted-lua) @@ -696,17 +763,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) @@ -718,3 +774,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 e9c1173007..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 [good first issue](../../labels/good-first-issue) or [complexity:low] issue. +- 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 ------------------ @@ -244,6 +244,10 @@ You can lint a single file (but this will _not_ exclude legacy errors): ("Exuberant ctags", the typical `ctags` binary provided by your distro, is unmaintained and won't recognize many function signatures in Neovim source.) - Explore the source code [on the web](https://sourcegraph.com/github.com/neovim/neovim). +- If using [lua-language-server][], symlink `contrib/luarc.json` into the + project root: + + $ ln -s contrib/luarc.json .luarc.json Reviewing @@ -252,10 +256,10 @@ Reviewing To help review pull requests, start with [this checklist][review-checklist]. Reviewing can be done on GitHub, but you may find it easier to do locally. -Using [`hub`][hub], you can create a new branch with the contents of a pull +Using [GitHub CLI][gh], you can create a new branch with the contents of a pull request, e.g. [#1820][1820]: - hub checkout https://github.com/neovim/neovim/pull/1820 + gh pr checkout https://github.com/neovim/neovim/pull/1820 Use [`git log -p master..FETCH_HEAD`][git-history-filtering] to list all commits in the feature branch which aren't in the `master` branch; `-p` @@ -270,7 +274,7 @@ as context, use the `-W` argument as well. [git-rebasing]: http://git-scm.com/book/en/v2/Git-Branching-Rebasing [github-issues]: https://github.com/neovim/neovim/issues [1820]: https://github.com/neovim/neovim/pull/1820 -[hub]: https://hub.github.com/ +[gh]: https://cli.github.com/ [conventional_commits]: https://www.conventionalcommits.org [style-guide]: https://neovim.io/doc/user/dev_style.html#dev-style [ASan]: http://clang.llvm.org/docs/AddressSanitizer.html @@ -287,4 +291,5 @@ as context, use the `-W` argument as well. [wiki-contribute-help]: https://github.com/neovim/neovim/wiki/contribute-%3Ahelp [pr-draft]: https://docs.github.com/en/github/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request [pr-ready]: https://docs.github.com/en/github/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/changing-the-stage-of-a-pull-request -[uncrustify]: https://formulae.brew.sh/formula/uncrustify +[uncrustify]: http://uncrustify.sourceforge.net/ +[lua-language-server]: https://github.com/sumneko/lua-language-server/ diff --git a/MAINTAIN.md b/MAINTAIN.md index 7df91b7d57..2b390270a9 100644 --- a/MAINTAIN.md +++ b/MAINTAIN.md @@ -76,6 +76,12 @@ These "bundled" dependencies can be updated by bumping their versions in `third- - [lua-compat](https://github.com/keplerproject/lua-compat-5.3) - [tree-sitter](https://github.com/tree-sitter/tree-sitter) +`scripts/bump-dep.sh` is a script that can automate this process for `LuaJIT`, `Luv`, `libuv` & `tree-sitter`. See usage guide: + - Run `./scripts/bump-deps.sh --dep Luv --version 1.43.0-0` to update a dependency. + See `./scripts/bump-deps.sh -h` for more detailed usage + - Run `./scripts/bump-deps.sh --pr` to create a pr + To generate the default PR title and body, the script uses the most recent commit (not in `master`) with prefix `build(deps): ` + These dependencies are "vendored" (inlined), we need to update the sources manually: - [libmpack](https://github.com/libmpack/libmpack) - [xdiff](https://github.com/git/git/tree/master/xdiff) @@ -166,7 +166,7 @@ _opt_pylint: || echo "SKIP: pylint (flake8 not found)" commitlint: - $(NVIM_PRG) --clean -es +"lua require('scripts.lintcommit').main({trace=false})" + $(NVIM_PRG) -u NONE -es +"lua require('scripts.lintcommit').main({trace=false})" _opt_commitlint: @test -x build/bin/nvim && { $(MAKE) commitlint; exit $$?; } \ @@ -1,4 +1,6 @@ -[](https://neovim.io) +<h1 align="center"> + <img src="https://raw.githubusercontent.com/neovim/neovim.github.io/master/logos/neovim-logo-300x87.png" alt="Neovim"> +</h1> [Documentation](https://neovim.io/doc/general/) | [Chat](https://app.element.io/#/room/#neovim:matrix.org) | @@ -119,7 +121,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..c7c3b3d470 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 @@ -176,7 +183,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..0320ac15c3 100644 --- a/ci/common/suite.sh +++ b/ci/common/suite.sh @@ -1,9 +1,3 @@ -# HACK: get newline for use in strings given that "\n" and $'' do not work. -NL="$(printf '\nE')" -NL="${NL%E}" - -FAIL_SUMMARY="" - # Test success marker. If END_MARKER file exists, we know that all tests # finished. If FAIL_SUMMARY_FILE exists we know that some tests failed, this # file will contain information about failed tests. Build is considered @@ -11,190 +5,27 @@ 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 -} - fail() { local test_name="$1" - local fail_char="$2" - local message="$3" + local message="$2" - : ${fail_char:=F} : ${message:=Test $test_name failed} - local full_msg="$fail_char $NVIM_TEST_CURRENT_SUITE|$test_name :: $message" - FAIL_SUMMARY="${FAIL_SUMMARY}${NL}${full_msg}" + local full_msg="$test_name :: $message" echo "${full_msg}" >> "${FAIL_SUMMARY_FILE}" echo "Failed: $full_msg" 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 diff --git a/ci/common/test.sh b/ci/common/test.sh index 92c15c8ba1..7db39a0e5f 100644 --- a/ci/common/test.sh +++ b/ci/common/test.sh @@ -51,7 +51,7 @@ check_core_dumps() { fi done if test "$app" != quiet ; then - fail 'cores' E 'Core dumps found' + fail 'cores' 'Core dumps found' fi } @@ -72,7 +72,7 @@ check_logs() { rm "${log}" done if test -n "${err}" ; then - fail 'logs' E 'Runtime errors detected.' + fail 'logs' 'Runtime errors detected.' fi } @@ -86,46 +86,39 @@ check_sanitizer() { fi } -run_unittests() {( - enter_suite unittests +unittests() {( ulimit -c unlimited || true if ! build_make unittest ; then - fail 'unittests' F 'Unit tests failed' + fail 'unittests' 'Unit tests failed' fi submit_coverage unittest check_core_dumps "$(command -v luajit)" - exit_suite )} -run_functionaltests() {( - enter_suite functionaltests +functionaltests() {( ulimit -c unlimited || true if ! build_make ${FUNCTIONALTEST}; then - fail 'functionaltests' F 'Functional tests failed' + fail 'functionaltests' 'Functional tests failed' fi submit_coverage functionaltest check_sanitizer "${LOG_DIR}" valgrind_check "${LOG_DIR}" check_core_dumps - exit_suite )} -run_oldtests() {( - enter_suite oldtests +oldtests() {( ulimit -c unlimited || true if ! make oldtest; then reset - fail 'oldtests' F 'Legacy tests failed' + fail 'oldtests' 'Legacy tests failed' fi submit_coverage oldtest 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 @@ -136,27 +129,25 @@ check_runtime_files() {( # Prefer failing the build over using more robust construct because files # with IFS are not welcome. if ! test -e "$file" ; then - fail "$test_name" E \ - "It appears that $file is only a part of the file name" + fail "$test_name" "It appears that $file is only a part of the file name" fi if ! test "$tst" "$INSTALL_PREFIX/share/nvim/runtime/$file" ; then - fail "$test_name" F "$(printf "$message" "$file")" + fail "$test_name" "$(printf "$message" "$file")" fi done )} install_nvim() {( - enter_suite 'install_nvim' if ! build_make install ; then - fail 'install' E 'make install failed' - exit_suite + fail 'install' 'make install failed' + exit 1 fi "${INSTALL_PREFIX}/bin/nvim" --version if ! "${INSTALL_PREFIX}/bin/nvim" -u NONE -e -c ':help' -c ':qall' ; then echo "Running ':help' in the installed nvim failed." echo "Maybe the helptags have not been generated properly." - fail 'help' F 'Failed running :help' + fail 'help' 'Failed running :help' fi # Check that all runtime files were installed @@ -177,13 +168,6 @@ install_nvim() {( local genvimsynf=syntax/vim/generated.vim local gpat='syn keyword vimFuncName .*eval' if ! grep -q "$gpat" "${INSTALL_PREFIX}/share/nvim/runtime/$genvimsynf" ; then - fail 'funcnames' F "It appears that $genvimsynf does not contain $gpat." + fail 'funcnames' "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..3a524b4ed6 100755 --- a/ci/run_lint.sh +++ b/ci/run_lint.sh @@ -8,29 +8,17 @@ 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 +rm -f "$END_MARKER" + +# Run all tests if no input argument is given +if (($# == 0)); then + tests=('clint-full' 'lualint' 'pylint' 'shlint' 'check-single-includes') +else + tests=("$@") +fi + +for i in "${tests[@]}"; do + make "$i" || fail "$i" +done end_tests diff --git a/ci/run_tests.sh b/ci/run_tests.sh index d91ac5589e..23460b682e 100755 --- a/ci/run_tests.sh +++ b/ci/run_tests.sh @@ -8,33 +8,28 @@ 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 - -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 +rm -f "$END_MARKER" + +# Run all tests (with some caveats) if no input argument is given +if (($# == 0)); then + tests=('build_nvim') + + 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 + tests+=('unittests') + fi + tests+=('functionaltests') fi - run_test run_functionaltests -fi -run_test run_oldtests -run_test install_nvim + tests+=('oldtests' 'install_nvim') +else + tests=("$@") +fi -exit_suite --continue +for i in "${tests[@]}"; do + eval "$i" || fail "$i" +done 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/ci/snap/deploy.sh b/ci/snap/deploy.sh index 579c48e933..1794fc61d9 100755 --- a/ci/snap/deploy.sh +++ b/ci/snap/deploy.sh @@ -8,7 +8,7 @@ WEBHOOK_PAYLOAD="$(cat "${SNAP_DIR}/.snapcraft_payload")" PAYLOAD_SIG="${SECRET_SNAP_SIG}" -snap_realease_needed() { +snap_release_needed() { last_committed_tag="$(git tag -l --sort=refname|head -1)" last_snap_release="$(snap info nvim | awk '$1 == "latest/edge:" { print $2 }' | perl -lpe 's/v\d.\d.\d-//g')" git fetch -f --tags @@ -24,7 +24,7 @@ snap_realease_needed() { trigger_snapcraft_webhook() { [[ -n "${PAYLOAD_SIG}" ]] || exit - echo "Triggering new snap relase via webhook..." + echo "Triggering new snap release via webhook..." curl -X POST \ -H "Content-Type: application/json" \ -H "X-Hub-Signature: sha1=${PAYLOAD_SIG}" \ @@ -33,7 +33,7 @@ trigger_snapcraft_webhook() { } -if $(snap_realease_needed); then +if $(snap_release_needed); then echo "New snap release required" trigger_snapcraft_webhook fi diff --git a/cmake/RunTests.cmake b/cmake/RunTests.cmake index e78392562f..3adbcbbfc5 100644 --- a/cmake/RunTests.cmake +++ b/cmake/RunTests.cmake @@ -46,6 +46,10 @@ if(DEFINED ENV{TEST_FILTER} AND NOT "$ENV{TEST_FILTER}" STREQUAL "") list(APPEND BUSTED_ARGS --filter $ENV{TEST_FILTER}) endif() +if(DEFINED ENV{TEST_FILTER_OUT} AND NOT "$ENV{TEST_FILTER_OUT}" STREQUAL "") + list(APPEND BUSTED_ARGS --filter-out $ENV{TEST_FILTER_OUT}) +endif() + # TMPDIR: use relative test path (for parallel test runs / isolation). set(ENV{TMPDIR} "${BUILD_DIR}/Xtest_tmpdir/${TEST_PATH}") execute_process(COMMAND ${CMAKE_COMMAND} -E make_directory $ENV{TMPDIR}) diff --git a/config/CMakeLists.txt b/config/CMakeLists.txt index 581f25857b..8f93e1eb27 100644 --- a/config/CMakeLists.txt +++ b/config/CMakeLists.txt @@ -45,10 +45,24 @@ check_function_exists(readlink HAVE_READLINK) check_function_exists(setpgid HAVE_SETPGID) check_function_exists(setsid HAVE_SETSID) check_function_exists(sigaction HAVE_SIGACTION) +check_function_exists(strnlen HAVE_STRNLEN) check_function_exists(strcasecmp HAVE_STRCASECMP) check_function_exists(strncasecmp HAVE_STRNCASECMP) check_function_exists(strptime HAVE_STRPTIME) +if(CMAKE_SYSTEM_NAME STREQUAL "SunOS") + check_c_source_compiles(" +#include <termios.h> +int +main(void) +{ + return forkpty(0, NULL, NULL, NULL); +} +" HAVE_FORKPTY) +else() + set(HAVE_FORKPTY 1) +endif() + # Symbols check_symbol_exists(FD_CLOEXEC "fcntl.h" HAVE_FD_CLOEXEC) if(HAVE_LANGINFO_H) @@ -92,7 +106,7 @@ if (NOT "${HAVE_BE64TOH}") # any case and ORDER_BIG_ENDIAN will not be examined. # - CMAKE_CROSSCOMPILING *and* HAVE_BE64TOH are both false. In this case # be64toh function which uses cycle and arithmetic operations is used which - # will work regardless of endianess. Function is sub-optimal though. + # will work regardless of endianness. Function is sub-optimal though. check_c_source_runs(" ${SI} ${MS} diff --git a/config/config.h.in b/config/config.h.in index 27a28116af..b44b7238d2 100644 --- a/config/config.h.in +++ b/config/config.h.in @@ -30,6 +30,7 @@ #cmakedefine HAVE_SETPGID #cmakedefine HAVE_SETSID #cmakedefine HAVE_SIGACTION +#cmakedefine HAVE_STRNLEN #cmakedefine HAVE_STRCASECMP #cmakedefine HAVE_STRINGS_H #cmakedefine HAVE_STRNCASECMP @@ -49,6 +50,7 @@ # undef HAVE_SYS_UIO_H # endif #endif +#cmakedefine HAVE_FORKPTY #cmakedefine FEAT_TUI diff --git a/contrib/flake.lock b/contrib/flake.lock index c4d7f120ba..b72e1f8d5f 100644 --- a/contrib/flake.lock +++ b/contrib/flake.lock @@ -2,11 +2,11 @@ "nodes": { "flake-utils": { "locked": { - "lastModified": 1629481132, - "narHash": "sha256-JHgasjPR0/J1J3DRm4KxM4zTyAj4IOJY8vIl75v/kPI=", + "lastModified": 1644229661, + "narHash": "sha256-1YdnJAsNy69bpcjuoKdOYQX0YxZBiCYZo4Twxerqv7k=", "owner": "numtide", "repo": "flake-utils", - "rev": "997f7efcb746a9c140ce1f13c72263189225f482", + "rev": "3cecb5b042f7f209c56ffd8371b2711a290ec797", "type": "github" }, "original": { @@ -17,11 +17,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1630074300, - "narHash": "sha256-BFM7OiXRs0RvSUZd6NCGAKWVPn3VodgYQ4TUQXxbMBU=", + "lastModified": 1646254136, + "narHash": "sha256-8nQx02tTzgYO21BP/dy5BCRopE8OwE8Drsw98j+Qoaw=", "owner": "nixos", "repo": "nixpkgs", - "rev": "21c937f8cb1e6adcfeb36dfd6c90d9d9bfab1d28", + "rev": "3e072546ea98db00c2364b81491b893673267827", "type": "github" }, "original": { diff --git a/contrib/local.mk.example b/contrib/local.mk.example index 778e848d60..20396e86ae 100644 --- a/contrib/local.mk.example +++ b/contrib/local.mk.example @@ -27,7 +27,7 @@ # With non-Debug builds interprocedural optimization (IPO) (which includes # link-time optimization (LTO)) is enabled by default, which causes the link -# step to take a significant amout of time, which is relevant when building +# step to take a significant amount of time, which is relevant when building # often. You can disable it explicitly: # CMAKE_EXTRA_FLAGS += -DENABLE_LTO=OFF diff --git a/contrib/luarc.json b/contrib/luarc.json new file mode 100644 index 0000000000..770b023ac6 --- /dev/null +++ b/contrib/luarc.json @@ -0,0 +1,23 @@ +{ + "runtime.version": "LuaJIT", + "diagnostics": { + "enable": true, + "globals": [ + "vim", + "describe", + "it", + "before_each", + "after_each", + "setup", + "teardown" + ] + }, + "workspace": { + "library": { + "runtime/lua": true + }, + "maxPreload": 2000, + "preloadFileSize": 1000 + }, + "telemetry.enable": false +} diff --git a/man/nvim.1 b/man/nvim.1 index b206b62343..43dfc21dc7 100644 --- a/man/nvim.1 +++ b/man/nvim.1 @@ -177,8 +177,7 @@ If .Ar vimrc is .Cm NORC , -do not load any initialization files (except plugins), -and do not attempt to parse environment variables. +do not load any initialization files (except plugins). If .Ar vimrc is diff --git a/packaging/CMakeLists.txt b/packaging/CMakeLists.txt new file mode 100644 index 0000000000..8538075388 --- /dev/null +++ b/packaging/CMakeLists.txt @@ -0,0 +1,64 @@ +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) + + # 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}/neovim.ico) + + # We use a wix patch to add further options to the installer. At present, it's just to add neovim to the path + # on installation, however, it can be extended. + # See: https://cmake.org/cmake/help/v3.7/module/CPackWIX.html#variable:CPACK_WIX_PATCH_FILE + list(APPEND CPACK_WIX_EXTENSIONS WixUtilExtension) + list(APPEND CPACK_WIX_PATCH_FILE ${CMAKE_CURRENT_LIST_DIR}/WixPatch.xml) +elseif(APPLE) + set(CPACK_PACKAGE_FILE_NAME "nvim-macos") + set(CPACK_GENERATOR TGZ) + set(CPACK_PACKAGE_ICON ${CMAKE_CURRENT_LIST_DIR}/neovim.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/WixPatch.xml b/packaging/WixPatch.xml new file mode 100644 index 0000000000..728d43951e --- /dev/null +++ b/packaging/WixPatch.xml @@ -0,0 +1,16 @@ +<CPackWiXPatch> + <!-- Fragment ID is from: <your build dir>/_CPack_Packages/win64/WIX/files.wxs --> + <CPackWiXFragment Id="CM_CP_bin.nvim.exe"> + <!-- Note: if we were to specify Value='[INSTALL_ROOT]\bin' - with a backslash, the installer will still + use a forward slash in the path. --> + <Environment + Id='UpdatePath' + Name='PATH' + Action='set' + Permanent='no' + System='yes' + Part='last' + Value='[INSTALL_ROOT]/bin' + /> + </CPackWiXFragment> +</CPackWiXPatch> diff --git a/packaging/neovim.icns b/packaging/neovim.icns Binary files differnew file mode 100644 index 0000000000..df0e982369 --- /dev/null +++ b/packaging/neovim.icns diff --git a/packaging/neovim.ico b/packaging/neovim.ico Binary files differnew file mode 100644 index 0000000000..e0c151c966 --- /dev/null +++ b/packaging/neovim.ico diff --git a/packaging/neovim.png b/packaging/neovim.png Binary files differnew file mode 100644 index 0000000000..a3960b41bd --- /dev/null +++ b/packaging/neovim.png diff --git a/packaging/neovim.svg b/packaging/neovim.svg new file mode 100644 index 0000000000..d82ad667c1 --- /dev/null +++ b/packaging/neovim.svg @@ -0,0 +1,147 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:sketch="http://www.bohemiancoding.com/sketch/ns" + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="256" + height="256" + viewBox="0 0 256 256" + version="1.1" + id="svg4612" + sodipodi:docname="neovim.svg" + inkscape:version="0.92.4 5da689c313, 2019-01-14"> + <metadata + id="metadata4616"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title>neovim-mark@2x</dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="2560" + inkscape:window-height="1333" + id="namedview4614" + showgrid="false" + inkscape:zoom="2.1945358" + inkscape:cx="132.84232" + inkscape:cy="196.34741" + inkscape:window-x="0" + inkscape:window-y="34" + inkscape:window-maximized="1" + inkscape:current-layer="svg4612" /> + <title + id="title4587">neovim-mark@2x</title> + <description>Created with Sketch (http://www.bohemiancoding.com/sketch)</description> + <defs + id="defs4604"> + <linearGradient + x1="167.95833" + y1="-0.46142399" + x2="167.95833" + y2="335.45523" + id="linearGradient-1" + gradientTransform="scale(0.46142398,2.1672042)" + gradientUnits="userSpaceOnUse"> + <stop + stop-color="#16B0ED" + stop-opacity="0.800235524" + offset="0%" + id="stop4589" /> + <stop + stop-color="#0F59B2" + stop-opacity="0.83700023" + offset="100%" + id="stop4591" /> + </linearGradient> + <linearGradient + x1="1118.3427" + y1="-0.46586797" + x2="1118.3427" + y2="338.68604" + id="linearGradient-2" + gradientTransform="scale(0.46586797,2.1465309)" + gradientUnits="userSpaceOnUse"> + <stop + stop-color="#7DB643" + offset="0%" + id="stop4594" /> + <stop + stop-color="#367533" + offset="100%" + id="stop4596" /> + </linearGradient> + <linearGradient + x1="356.33795" + y1="0" + x2="356.33795" + y2="612.90131" + id="linearGradient-3" + gradientTransform="scale(0.84189739,1.1877932)" + gradientUnits="userSpaceOnUse"> + <stop + stop-color="#88C649" + stop-opacity="0.8" + offset="0%" + id="stop4599" /> + <stop + stop-color="#439240" + stop-opacity="0.84" + offset="100%" + id="stop4601" /> + </linearGradient> + </defs> + <g + id="Page-1" + sketch:type="MSPage" + style="fill:none;fill-rule:evenodd;stroke:none;stroke-width:1" + transform="matrix(0.34367476,0,0,0.34367476,25.312651,1.7737533)"> + <g + id="mark-copy" + sketch:type="MSLayerGroup" + transform="translate(2,3)"> + <path + d="M 0,155.5704 155,-1 V 727 L 0,572.23792 Z" + id="Left---green" + sketch:type="MSShapeGroup" + inkscape:connector-curvature="0" + style="fill:url(#linearGradient-1)" /> + <path + d="M 443.0604,156.9824 600,-1 596.81879,727 442,572.21994 Z" + id="Right---blue" + sketch:type="MSShapeGroup" + transform="matrix(-1,0,0,1,1042,0)" + inkscape:connector-curvature="0" + style="fill:url(#linearGradient-2)" /> + <path + d="M 154.98629,0 558,615.1897 445.2246,728 42,114.17202 Z" + id="Cross---blue" + sketch:type="MSShapeGroup" + inkscape:connector-curvature="0" + style="fill:url(#linearGradient-3)" /> + <path + d="M 155,283.83232 154.78675,308 31,124.71061 42.461949,113 Z" + id="Shadow" + sketch:type="MSShapeGroup" + inkscape:connector-curvature="0" + style="fill:#000000;fill-opacity:0.12999998" /> + </g> + </g> +</svg> diff --git a/runtime/CMakeLists.txt b/runtime/CMakeLists.txt index 37029874f2..f656f1cbc3 100644 --- a/runtime/CMakeLists.txt +++ b/runtime/CMakeLists.txt @@ -123,7 +123,7 @@ foreach(PROG ${RUNTIME_PROGRAMS}) endforeach() globrecurse_wrapper(RUNTIME_FILES ${CMAKE_CURRENT_SOURCE_DIR} - rgb.txt *.vim *.lua *.dict *.py *.rb *.ps *.spl *.tutor *.tutor.json) + *.vim *.lua *.dict *.py *.rb *.ps *.spl *.tutor *.tutor.json) foreach(F ${RUNTIME_FILES}) get_filename_component(BASEDIR ${F} PATH) diff --git a/runtime/autoload/clojurecomplete.vim b/runtime/autoload/clojurecomplete.vim index 9f2c39081a..02262a6f91 100644 --- a/runtime/autoload/clojurecomplete.vim +++ b/runtime/autoload/clojurecomplete.vim @@ -4,12 +4,12 @@ " Former Maintainers: Sung Pae <self@sungpae.com> " URL: https://github.com/clojure-vim/clojure.vim " License: Vim (see :h license) -" Last Change: 2021-10-26 +" Last Change: 2022-03-24 " -*- COMPLETION WORDS -*- -" Generated from https://github.com/clojure-vim/clojure.vim/blob/62b215f079ce0f3834fd295c7a7f6bd8cc54bcc3/clj/src/vim_clojure_static/generate.clj -" Clojure version 1.10.3 -let s:words = ["*","*'","*1","*2","*3","*agent*","*allow-unresolved-vars*","*assert*","*clojure-version*","*command-line-args*","*compile-files*","*compile-path*","*compiler-options*","*data-readers*","*default-data-reader-fn*","*e","*err*","*file*","*flush-on-newline*","*fn-loader*","*in*","*math-context*","*ns*","*out*","*print-dup*","*print-length*","*print-level*","*print-meta*","*print-namespace-maps*","*print-readably*","*read-eval*","*reader-resolver*","*source-path*","*suppress-read*","*unchecked-math*","*use-context-classloader*","*verbose-defrecords*","*warn-on-reflection*","+","+'","-","-'","->","->>","->ArrayChunk","->Eduction","->Vec","->VecNode","->VecSeq","-cache-protocol-fn","-reset-methods",".","..","/","<","<=","=","==",">",">=","EMPTY-NODE","Inst","PrintWriter-on","StackTraceElement->vec","Throwable->map","accessor","aclone","add-classpath","add-tap","add-watch","agent","agent-error","agent-errors","aget","alength","alias","all-ns","alter","alter-meta!","alter-var-root","amap","ancestors","and","any?","apply","areduce","array-map","as->","aset","aset-boolean","aset-byte","aset-char","aset-double","aset-float","aset-int","aset-long","aset-short","assert","assoc!","assoc","assoc-in","associative?","atom","await","await-for","await1","bases","bean","bigdec","bigint","biginteger","binding","bit-and","bit-and-not","bit-clear","bit-flip","bit-not","bit-or","bit-set","bit-shift-left","bit-shift-right","bit-test","bit-xor","boolean","boolean-array","boolean?","booleans","bound-fn","bound-fn*","bound?","bounded-count","butlast","byte","byte-array","bytes","bytes?","case","cast","cat","catch","char","char-array","char-escape-string","char-name-string","char?","chars","chunk","chunk-append","chunk-buffer","chunk-cons","chunk-first","chunk-next","chunk-rest","chunked-seq?","class","class?","clear-agent-errors","clojure-version","coll?","comment","commute","comp","comparator","compare","compare-and-set!","compile","complement","completing","concat","cond","cond->","cond->>","condp","conj!","conj","cons","constantly","construct-proxy","contains?","count","counted?","create-ns","create-struct","cycle","dec","dec'","decimal?","declare","dedupe","def","default-data-readers","definline","definterface","defmacro","defmethod","defmulti","defn","defn-","defonce","defprotocol","defrecord","defstruct","deftype","delay","delay?","deliver","denominator","deref","derive","descendants","destructure","disj!","disj","dissoc!","dissoc","distinct","distinct?","do","doall","dorun","doseq","dosync","dotimes","doto","double","double-array","double?","doubles","drop","drop-last","drop-while","eduction","empty","empty?","ensure","ensure-reduced","enumeration-seq","error-handler","error-mode","eval","even?","every-pred","every?","ex-cause","ex-data","ex-info","ex-message","extend","extend-protocol","extend-type","extenders","extends?","false?","ffirst","file-seq","filter","filterv","finally","find","find-keyword","find-ns","find-protocol-impl","find-protocol-method","find-var","first","flatten","float","float-array","float?","floats","flush","fn","fn","fn?","fnext","fnil","for","force","format","frequencies","future","future-call","future-cancel","future-cancelled?","future-done?","future?","gen-class","gen-interface","gensym","get","get-in","get-method","get-proxy-class","get-thread-bindings","get-validator","group-by","halt-when","hash","hash-combine","hash-map","hash-ordered-coll","hash-set","hash-unordered-coll","ident?","identical?","identity","if","if-let","if-not","if-some","ifn?","import","in-ns","inc","inc'","indexed?","init-proxy","inst-ms","inst-ms*","inst?","instance?","int","int-array","int?","integer?","interleave","intern","interpose","into","into-array","ints","io!","isa?","iterate","iterator-seq","juxt","keep","keep-indexed","key","keys","keyword","keyword?","last","lazy-cat","lazy-seq","let","let","letfn","line-seq","list","list*","list?","load","load-file","load-reader","load-string","loaded-libs","locking","long","long-array","longs","loop","loop","macroexpand","macroexpand-1","make-array","make-hierarchy","map","map-entry?","map-indexed","map?","mapcat","mapv","max","max-key","memfn","memoize","merge","merge-with","meta","method-sig","methods","min","min-key","mix-collection-hash","mod","monitor-enter","monitor-exit","munge","name","namespace","namespace-munge","nat-int?","neg-int?","neg?","new","newline","next","nfirst","nil?","nnext","not","not-any?","not-empty","not-every?","not=","ns","ns-aliases","ns-imports","ns-interns","ns-map","ns-name","ns-publics","ns-refers","ns-resolve","ns-unalias","ns-unmap","nth","nthnext","nthrest","num","number?","numerator","object-array","odd?","or","parents","partial","partition","partition-all","partition-by","pcalls","peek","persistent!","pmap","pop!","pop","pop-thread-bindings","pos-int?","pos?","pr","pr-str","prefer-method","prefers","primitives-classnames","print","print-ctor","print-dup","print-method","print-simple","print-str","printf","println","println-str","prn","prn-str","promise","proxy","proxy-call-with-super","proxy-mappings","proxy-name","proxy-super","push-thread-bindings","pvalues","qualified-ident?","qualified-keyword?","qualified-symbol?","quot","quote","rand","rand-int","rand-nth","random-sample","range","ratio?","rational?","rationalize","re-find","re-groups","re-matcher","re-matches","re-pattern","re-seq","read","read+string","read-line","read-string","reader-conditional","reader-conditional?","realized?","record?","recur","reduce","reduce-kv","reduced","reduced?","reductions","ref","ref-history-count","ref-max-history","ref-min-history","ref-set","refer","refer-clojure","reify","release-pending-sends","rem","remove","remove-all-methods","remove-method","remove-ns","remove-tap","remove-watch","repeat","repeatedly","replace","replicate","require","requiring-resolve","reset!","reset-meta!","reset-vals!","resolve","rest","restart-agent","resultset-seq","reverse","reversible?","rseq","rsubseq","run!","satisfies?","second","select-keys","send","send-off","send-via","seq","seq?","seqable?","seque","sequence","sequential?","set!","set","set-agent-send-executor!","set-agent-send-off-executor!","set-error-handler!","set-error-mode!","set-validator!","set?","short","short-array","shorts","shuffle","shutdown-agents","simple-ident?","simple-keyword?","simple-symbol?","slurp","some","some->","some->>","some-fn","some?","sort","sort-by","sorted-map","sorted-map-by","sorted-set","sorted-set-by","sorted?","special-symbol?","spit","split-at","split-with","str","string?","struct","struct-map","subs","subseq","subvec","supers","swap!","swap-vals!","symbol","symbol?","sync","tagged-literal","tagged-literal?","take","take-last","take-nth","take-while","tap>","test","the-ns","thread-bound?","throw","time","to-array","to-array-2d","trampoline","transduce","transient","tree-seq","true?","try","type","unchecked-add","unchecked-add-int","unchecked-byte","unchecked-char","unchecked-dec","unchecked-dec-int","unchecked-divide-int","unchecked-double","unchecked-float","unchecked-inc","unchecked-inc-int","unchecked-int","unchecked-long","unchecked-multiply","unchecked-multiply-int","unchecked-negate","unchecked-negate-int","unchecked-remainder-int","unchecked-short","unchecked-subtract","unchecked-subtract-int","underive","unquote","unquote-splicing","unreduced","unsigned-bit-shift-right","update","update-in","update-proxy","uri?","use","uuid?","val","vals","var","var-get","var-set","var?","vary-meta","vec","vector","vector-of","vector?","volatile!","volatile?","vreset!","vswap!","when","when-first","when-let","when-not","when-some","while","with-bindings","with-bindings*","with-in-str","with-loading-context","with-local-vars","with-meta","with-open","with-out-str","with-precision","with-redefs","with-redefs-fn","xml-seq","zero?","zipmap"] +" Generated from https://github.com/clojure-vim/clojure.vim/blob/fd280e33e84c88e97860930557dba3ff80b1a82d/clj/src/vim_clojure_static/generate.clj +" Clojure version 1.11.0 +let s:words = ["&","*","*'","*1","*2","*3","*agent*","*allow-unresolved-vars*","*assert*","*clojure-version*","*command-line-args*","*compile-files*","*compile-path*","*compiler-options*","*data-readers*","*default-data-reader-fn*","*e","*err*","*file*","*flush-on-newline*","*fn-loader*","*in*","*math-context*","*ns*","*out*","*print-dup*","*print-length*","*print-level*","*print-meta*","*print-namespace-maps*","*print-readably*","*read-eval*","*reader-resolver*","*source-path*","*suppress-read*","*unchecked-math*","*use-context-classloader*","*verbose-defrecords*","*warn-on-reflection*","+","+'","-","-'","->","->>","->ArrayChunk","->Eduction","->Vec","->VecNode","->VecSeq","-cache-protocol-fn","-reset-methods",".","..","/","<","<=","=","==",">",">=","EMPTY-NODE","Inst","NaN?","PrintWriter-on","StackTraceElement->vec","Throwable->map","abs","accessor","aclone","add-classpath","add-tap","add-watch","agent","agent-error","agent-errors","aget","alength","alias","all-ns","alter","alter-meta!","alter-var-root","amap","ancestors","and","any?","apply","areduce","array-map","as->","aset","aset-boolean","aset-byte","aset-char","aset-double","aset-float","aset-int","aset-long","aset-short","assert","assoc","assoc!","assoc-in","associative?","atom","await","await-for","await1","bases","bean","bigdec","bigint","biginteger","binding","bit-and","bit-and-not","bit-clear","bit-flip","bit-not","bit-or","bit-set","bit-shift-left","bit-shift-right","bit-test","bit-xor","boolean","boolean-array","boolean?","booleans","bound-fn","bound-fn*","bound?","bounded-count","butlast","byte","byte-array","bytes","bytes?","case","case*","cast","cat","catch","char","char-array","char-escape-string","char-name-string","char?","chars","chunk","chunk-append","chunk-buffer","chunk-cons","chunk-first","chunk-next","chunk-rest","chunked-seq?","class","class?","clear-agent-errors","clojure-version","coll?","comment","commute","comp","comparator","compare","compare-and-set!","compile","complement","completing","concat","cond","cond->","cond->>","condp","conj","conj!","cons","constantly","construct-proxy","contains?","count","counted?","create-ns","create-struct","cycle","dec","dec'","decimal?","declare","dedupe","def","default-data-readers","definline","definterface","defmacro","defmethod","defmulti","defn","defn-","defonce","defprotocol","defrecord","defstruct","deftype","deftype*","delay","delay?","deliver","denominator","deref","derive","descendants","destructure","disj","disj!","dissoc","dissoc!","distinct","distinct?","do","doall","dorun","doseq","dosync","dotimes","doto","double","double-array","double?","doubles","drop","drop-last","drop-while","eduction","empty","empty?","ensure","ensure-reduced","enumeration-seq","error-handler","error-mode","eval","even?","every-pred","every?","ex-cause","ex-data","ex-info","ex-message","extend","extend-protocol","extend-type","extenders","extends?","false","false?","ffirst","file-seq","filter","filterv","finally","find","find-keyword","find-ns","find-protocol-impl","find-protocol-method","find-var","first","flatten","float","float-array","float?","floats","flush","fn","fn*","fn?","fnext","fnil","for","force","format","frequencies","future","future-call","future-cancel","future-cancelled?","future-done?","future?","gen-class","gen-interface","gensym","get","get-in","get-method","get-proxy-class","get-thread-bindings","get-validator","group-by","halt-when","hash","hash-combine","hash-map","hash-ordered-coll","hash-set","hash-unordered-coll","ident?","identical?","identity","if","if-let","if-not","if-some","ifn?","import","in-ns","inc","inc'","indexed?","infinite?","init-proxy","inst-ms","inst-ms*","inst?","instance?","int","int-array","int?","integer?","interleave","intern","interpose","into","into-array","ints","io!","isa?","iterate","iteration","iterator-seq","juxt","keep","keep-indexed","key","keys","keyword","keyword?","last","lazy-cat","lazy-seq","let","let*","letfn","letfn*","line-seq","list","list*","list?","load","load-file","load-reader","load-string","loaded-libs","locking","long","long-array","longs","loop","loop*","macroexpand","macroexpand-1","make-array","make-hierarchy","map","map-entry?","map-indexed","map?","mapcat","mapv","max","max-key","memfn","memoize","merge","merge-with","meta","method-sig","methods","min","min-key","mix-collection-hash","mod","monitor-enter","monitor-exit","munge","name","namespace","namespace-munge","nat-int?","neg-int?","neg?","new","newline","next","nfirst","nil","nil?","nnext","not","not-any?","not-empty","not-every?","not=","ns","ns-aliases","ns-imports","ns-interns","ns-map","ns-name","ns-publics","ns-refers","ns-resolve","ns-unalias","ns-unmap","nth","nthnext","nthrest","num","number?","numerator","object-array","odd?","or","parents","parse-boolean","parse-double","parse-long","parse-uuid","partial","partition","partition-all","partition-by","pcalls","peek","persistent!","pmap","pop","pop!","pop-thread-bindings","pos-int?","pos?","pr","pr-str","prefer-method","prefers","primitives-classnames","print","print-ctor","print-dup","print-method","print-simple","print-str","printf","println","println-str","prn","prn-str","promise","proxy","proxy-call-with-super","proxy-mappings","proxy-name","proxy-super","push-thread-bindings","pvalues","qualified-ident?","qualified-keyword?","qualified-symbol?","quot","quote","rand","rand-int","rand-nth","random-sample","random-uuid","range","ratio?","rational?","rationalize","re-find","re-groups","re-matcher","re-matches","re-pattern","re-seq","read","read+string","read-line","read-string","reader-conditional","reader-conditional?","realized?","record?","recur","reduce","reduce-kv","reduced","reduced?","reductions","ref","ref-history-count","ref-max-history","ref-min-history","ref-set","refer","refer-clojure","reify","reify*","release-pending-sends","rem","remove","remove-all-methods","remove-method","remove-ns","remove-tap","remove-watch","repeat","repeatedly","replace","replicate","require","requiring-resolve","reset!","reset-meta!","reset-vals!","resolve","rest","restart-agent","resultset-seq","reverse","reversible?","rseq","rsubseq","run!","satisfies?","second","select-keys","send","send-off","send-via","seq","seq-to-map-for-destructuring","seq?","seqable?","seque","sequence","sequential?","set","set!","set-agent-send-executor!","set-agent-send-off-executor!","set-error-handler!","set-error-mode!","set-validator!","set?","short","short-array","shorts","shuffle","shutdown-agents","simple-ident?","simple-keyword?","simple-symbol?","slurp","some","some->","some->>","some-fn","some?","sort","sort-by","sorted-map","sorted-map-by","sorted-set","sorted-set-by","sorted?","special-symbol?","spit","split-at","split-with","str","string?","struct","struct-map","subs","subseq","subvec","supers","swap!","swap-vals!","symbol","symbol?","sync","tagged-literal","tagged-literal?","take","take-last","take-nth","take-while","tap>","test","the-ns","thread-bound?","throw","time","to-array","to-array-2d","trampoline","transduce","transient","tree-seq","true","true?","try","type","unchecked-add","unchecked-add-int","unchecked-byte","unchecked-char","unchecked-dec","unchecked-dec-int","unchecked-divide-int","unchecked-double","unchecked-float","unchecked-inc","unchecked-inc-int","unchecked-int","unchecked-long","unchecked-multiply","unchecked-multiply-int","unchecked-negate","unchecked-negate-int","unchecked-remainder-int","unchecked-short","unchecked-subtract","unchecked-subtract-int","underive","unquote","unquote-splicing","unreduced","unsigned-bit-shift-right","update","update-in","update-keys","update-proxy","update-vals","uri?","use","uuid?","val","vals","var","var-get","var-set","var?","vary-meta","vec","vector","vector-of","vector?","volatile!","volatile?","vreset!","vswap!","when","when-first","when-let","when-not","when-some","while","with-bindings","with-bindings*","with-in-str","with-loading-context","with-local-vars","with-meta","with-open","with-out-str","with-precision","with-redefs","with-redefs-fn","xml-seq","zero?","zipmap"] " Simple word completion for special forms and public vars in clojure.core function! clojurecomplete#Complete(findstart, base) diff --git a/runtime/autoload/dist/ft.vim b/runtime/autoload/dist/ft.vim index 7484149a26..866196a7df 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: 2020 Aug 17 +" Last Change: 2022 Apr 06 " 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 @@ -93,6 +112,25 @@ func dist#ft#BindzoneCheck(default) endif endfunc +" Returns true if file content looks like RAPID +func IsRapid(sChkExt = "") + if a:sChkExt == "cfg" + return getline(1) =~? '\v^%(EIO|MMC|MOC|PROC|SIO|SYS):CFG' + endif + " called from FTmod, FTprg or FTsys + return getline(nextnonblank(1)) =~? '\v^\s*%(\%{3}|module\s+\k+\s*%(\(|$))' +endfunc + +func dist#ft#FTcfg() + if exists("g:filetype_cfg") + exe "setf " .. g:filetype_cfg + elseif IsRapid("cfg") + setf rapid + else + setf cfg + endif +endfunc + func dist#ft#FTlpc() if exists("g:lpc_syntax_for_c") let lnum = 1 @@ -154,7 +192,7 @@ endfunc func dist#ft#FTent() " This function checks for valid cl syntax in the first five lines. - " Look for either an opening comment, '#', or a block start, '{". + " Look for either an opening comment, '#', or a block start, '{'. " If not found, assume SGML. let lnum = 1 while lnum < 6 @@ -192,6 +230,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 +261,38 @@ 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() + if exists("g:filetype_fs") + exe "setf " . g:filetype_fs + else + let line = getline(nextnonblank(1)) + " comments and colon definitions + if line =~ '^\s*\.\=( ' || line =~ '^\s*\\G\= ' || line =~ '^\\$' + \ || line =~ '^\s*: \S' + setf forth + else + setf fsharp + endif + endif +endfunc + " Distinguish between HTML, XHTML and Django func dist#ft#FThtml() let n = 1 @@ -272,6 +346,8 @@ func dist#ft#FTm() " excluding end(for|function|if|switch|while) common to Murphi let octave_block_terminators = '\<end\%(_try_catch\|classdef\|enumeration\|events\|methods\|parfor\|properties\)\>' + let objc_preprocessor = '^\s*#\s*\%(import\|include\|define\|if\|ifn\=def\|undef\|line\|error\|pragma\)\>' + let n = 1 let saw_comment = 0 " Whether we've seen a multiline comment leader. while n < 100 @@ -282,7 +358,7 @@ func dist#ft#FTm() " anything more definitive. let saw_comment = 1 endif - if line =~ '^\s*\(#\s*\(include\|import\)\>\|@import\>\|//\)' + if line =~ '^\s*//' || line =~ '^\s*@import\>' || line =~ objc_preprocessor setf objc return endif @@ -358,6 +434,36 @@ func dist#ft#FTmm() setf nroff endfunc +" Returns true if file content looks like LambdaProlog +func IsLProlog() + " skip apparent comments and blank lines, what looks like + " LambdaProlog comment may be RAPID header + let l = nextnonblank(1) + while l > 0 && l < line('$') && getline(l) =~ '^\s*%' " LambdaProlog comment + let l = nextnonblank(l + 1) + endwhile + " this pattern must not catch a go.mod file + return getline(l) =~ '\<module\s\+\w\+\s*\.\s*\(%\|$\)' +endfunc + +" Determine if *.mod is ABB RAPID, LambdaProlog, Modula-2, Modsim III or go.mod +func dist#ft#FTmod() + if exists("g:filetype_mod") + exe "setf " .. g:filetype_mod + elseif IsLProlog() + setf lprolog + elseif getline(nextnonblank(1)) =~ '\%(\<MODULE\s\+\w\+\s*;\|^\s*(\*\)' + setf modula2 + elseif IsRapid() + setf rapid + elseif expand("<afile>") =~ '\<go.mod$' + setf gomod + else + " Nothing recognized, assume modsim3 + setf modsim3 + endif +endfunc + func dist#ft#FTpl() if exists("g:filetype_pl") exe "setf " . g:filetype_pl @@ -474,6 +580,18 @@ func dist#ft#FTpp() endif endfunc +" Determine if *.prg is ABB RAPID. Can also be Clipper, FoxPro or eviews +func dist#ft#FTprg() + if exists("g:filetype_prg") + exe "setf " .. g:filetype_prg + elseif IsRapid() + setf rapid + else + " Nothing recognized, assume Clipper + setf clipper + endif +endfunc + func dist#ft#FTr() let max = line("$") > 50 ? 50 : line("$") @@ -655,6 +773,28 @@ func dist#ft#SQL() endif endfunc +" This function checks the first 25 lines of file extension "sc" to resolve +" detection between scala and SuperCollider +func dist#ft#FTsc() + for lnum in range(1, min([line("$"), 25])) + if getline(lnum) =~# '[A-Za-z0-9]*\s:\s[A-Za-z0-9]\|var\s<\|classvar\s<\|\^this.*\||\w*|\|+\s\w*\s{\|\*ar\s' + setf supercollider + return + endif + endfor + setf scala +endfunc + +" This function checks the first line of file extension "scd" to resolve +" detection between scdoc and SuperCollider +func dist#ft#FTscd() + if getline(1) =~# '\%^\S\+(\d[0-9A-Za-z]*)\%(\s\+\"[^"]*\"\%(\s\+\"[^"]*\"\)\=\)\=$' + setf scdoc + else + setf supercollider + endif +endfunc + " If the file has an extension of 't' and is in a directory 't' or 'xt' then " it is almost certainly a Perl test file. " If the first line starts with '#' and contains 'perl' it's probably a Perl @@ -673,7 +813,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 @@ -682,6 +822,14 @@ func dist#ft#FTperl() return 0 endfunc +func dist#ft#FTsys() + if IsRapid() + setf rapid + else + setf bat + endif +endfunc + " Choose context, plaintex, or tex (LaTeX) based on these rules: " 1. Check the first line of the file for "%&<format>". " 2. Check the first 1000 non-comment lines for LaTeX or ConTeXt keywords. @@ -705,7 +853,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 . '\)', @@ -792,6 +941,72 @@ func dist#ft#Redif() endwhile endfunc +" This function is called for all files under */debian/patches/*, make sure not +" to non-dep3patch files, such as README and other text files. +func dist#ft#Dep3patch() + if expand('%:t') ==# 'series' + return + endif + + for ln in getline(1, 100) + if ln =~# '^\%(Description\|Subject\|Origin\|Bug\|Forwarded\|Author\|From\|Reviewed-by\|Acked-by\|Last-Updated\|Applied-Upstream\):' + setf dep3patch + return + elseif ln =~# '^---' + " end of headers found. stop processing + return + endif + 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 + +" Determine if a *.src file is Kuka Robot Language +func dist#ft#FTsrc() + if exists("g:filetype_src") + exe "setf " .. g:filetype_src + elseif getline(nextnonblank(1)) =~? '^\s*\%(&\w\+\|\%(global\s\+\)\?def\>\)' + setf krl + endif +endfunc + +" Determine if a *.dat file is Kuka Robot Language +func dist#ft#FTdat() + if exists("g:filetype_dat") + exe "setf " .. g:filetype_dat + elseif getline(nextnonblank(1)) =~? '^\s*\%(&\w\+\|defdat\>\)' + setf krl + endif +endfunc " Restore 'cpoptions' let &cpo = 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 73c1459f86..1292e4344e 100644 --- a/runtime/autoload/health.vim +++ b/runtime/autoload/health.vim @@ -1,27 +1,3 @@ -function! s:enhance_syntax() abort - syntax case match - - syntax keyword healthError ERROR[:] - \ containedin=markdownCodeBlock,mkdListItemLine - highlight default link healthError Error - - syntax keyword healthWarning WARNING[:] - \ containedin=markdownCodeBlock,mkdListItemLine - highlight default link healthWarning WarningMsg - - syntax keyword healthSuccess OK[:] - \ containedin=markdownCodeBlock,mkdListItemLine - highlight default healthSuccess guibg=#5fff00 guifg=#080808 ctermbg=82 ctermfg=232 - - syntax match healthHelp "|.\{-}|" contains=healthBar - \ containedin=markdownCodeBlock,mkdListItemLine - syntax match healthBar "|" contained conceal - highlight default link healthHelp Identifier - - " We do not care about markdown syntax errors in :checkhealth output. - highlight! link markdownError Normal -endfunction - " Runs the specified healthchecks. " Runs all discovered healthchecks if a:plugin_names is empty. function! health#check(plugin_names) abort @@ -29,13 +5,9 @@ function! health#check(plugin_names) abort \ ? s:discover_healthchecks() \ : s:get_healthcheck(a:plugin_names) - tabnew - setlocal wrap breakindent linebreak - setlocal filetype=markdown - setlocal conceallevel=2 concealcursor=nc - setlocal keywordprg=:help - let &l:iskeyword='!-~,^*,^|,^",192-255' - call s:enhance_syntax() + " create scratch-buffer + execute 'tab sbuffer' nvim_create_buf(v:true, v:true) + setfiletype checkhealth if empty(healthchecks) call setline(1, 'ERROR: No healthchecks found.') @@ -49,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", @@ -70,8 +49,6 @@ function! health#check(plugin_names) abort " needed for plasticboy/vim-markdown, because it uses fdm=expr normal! zR - setlocal nomodified - setlocal bufhidden=hide redraw|echo '' endfunction @@ -157,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' @@ -211,7 +188,7 @@ function! s:get_healthcheck_list(plugin_names) abort \ + nvim_get_runtime_file('lua/**/'.p.'/health/init.lua', v:true) \ + nvim_get_runtime_file('lua/**/'.p.'/health.lua', v:true) if len(paths) == 0 - let healthchecks += [[p, '', '']] " healthchek not found + let healthchecks += [[p, '', '']] " healthcheck not found else let healthchecks += map(uniq(sort(paths)), \'<SID>filepath_to_healthcheck(v:val)') 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..a01cb9631c 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_warnings] = provider#pythonx#Detect(3) if empty(pyname) call health#report_warn('No Python executable found that can `import neovim`. ' @@ -311,8 +311,9 @@ function! s:check_python(version) abort endif " No Python executable could `import neovim`, or host_prog_var was used. - if !empty(pythonx_errors) - call health#report_error('Python provider error:', pythonx_errors) + if !empty(pythonx_warnings) + call health#report_warn(pythonx_warnings, ['See :help provider-python for more information.', + \ 'You may disable this provider (and warning) by adding `let g:loaded_python3_provider = 0` to your init.vim']) elseif !empty(pyname) && empty(python_exe) if !exists('g:'.host_prog_var) @@ -405,7 +406,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 +417,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 +524,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 +566,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) @@ -573,7 +574,8 @@ function! s:check_ruby() abort \ ['Run `gem install neovim` to ensure the neovim RubyGem is installed.', \ 'Run `gem environment` to ensure the gem bin directory is in $PATH.', \ 'If you are using rvm/rbenv/chruby, try "rehashing".', - \ 'See :help g:ruby_host_prog for non-standard gem installations.']) + \ 'See :help g:ruby_host_prog for non-standard gem installations.', + \ 'You may disable this provider (and warning) by adding `let g:loaded_ruby_provider = 0` to your init.vim']) return endif call health#report_info('Host: '. host) @@ -588,11 +590,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 +621,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) @@ -634,7 +636,8 @@ function! s:check_node() abort if empty(host) call health#report_warn('Missing "neovim" npm (or yarn) package.', \ ['Run in shell: npm install -g neovim', - \ 'Run in shell (if you use yarn): yarn global add neovim']) + \ 'Run in shell (if you use yarn): yarn global add neovim', + \ 'You may disable this provider (and warning) by adding `let g:loaded_node_provider = 0` to your init.vim']) return endif call health#report_info('Nvim node.js host: '. host) @@ -660,8 +663,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 @@ -683,14 +686,15 @@ function! s:check_perl() abort return endif - let [perl_exec, perl_errors] = provider#perl#Detect() + let [perl_exec, perl_warnings] = provider#perl#Detect() if empty(perl_exec) - if !empty(perl_errors) - call health#report_error('perl provider error:', perl_errors) - else + if !empty(perl_warnings) + call health#report_warn(perl_warnings, ['See :help provider-perl for more information.', + \ 'You may disable this provider (and warning) by adding `let g:loaded_perl_provider = 0` to your init.vim']) + else call health#report_warn('No usable perl executable found') endif - return + return endif call health#report_info('perl executable: '. perl_exec) @@ -734,8 +738,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 +755,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/msgpack.vim b/runtime/autoload/msgpack.vim index 7dd225e3d9..7f98a5b230 100644 --- a/runtime/autoload/msgpack.vim +++ b/runtime/autoload/msgpack.vim @@ -56,6 +56,7 @@ function s:msgpack_init_python() abort \. " time = datetime.datetime.fromtimestamp(timestamp)\n" \. " return time.strftime(fmt)\n" \. "def shada_dict_strptime():\n" + \. " import calendar\n" \. " import datetime\n" \. " import vim\n" \. " fmt = vim.eval('a:format')\n" @@ -64,7 +65,10 @@ function s:msgpack_init_python() abort \. " try:\n" \. " timestamp = int(timestamp.timestamp())\n" \. " except:\n" - \. " timestamp = int(timestamp.strftime('%s'))\n" + \. " try:\n" + \. " timestamp = int(timestamp.strftime('%s'))\n" + \. " except:\n" + \. " timestamp = calendar.timegm(timestamp.utctimetuple())\n" \. " if timestamp > 2 ** 31:\n" \. " tsabs = abs(timestamp)\n" \. " return ('{\"_TYPE\": v:msgpack_types.integer,'\n" 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..048f898e62 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 @@ -60,7 +56,7 @@ function! provider#pythonx#DetectByModule(module, major_version) abort endfor " No suitable Python executable found. - return ['', 'provider/pythonx: Could not load Python '.a:major_version.":\n".join(errors, "\n")] + return ['', 'Could not load Python '.a:major_version.":\n".join(errors, "\n")] endfunction " Returns array: [prog_exitcode, prog_version] @@ -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: @@ -103,7 +99,7 @@ function! provider#pythonx#CheckForModule(prog, module, major_version) abort endif if prog_exitcode == 2 - return [0, prog_path.' does not have the "' . a:module . '" module. :help provider-python'] + return [0, prog_path.' does not have the "' . a:module . '" module.'] elseif prog_exitcode == 127 " This can happen with pyenv's shims. return [0, prog_path . ' does not exist: ' . prog_version] diff --git a/runtime/autoload/python3complete.vim b/runtime/autoload/python3complete.vim index 192e9e6df8..b0781864c5 100644 --- a/runtime/autoload/python3complete.vim +++ b/runtime/autoload/python3complete.vim @@ -2,7 +2,7 @@ " Maintainer: <vacancy> " Previous Maintainer: Aaron Griffin <aaronmgriffin@gmail.com> " Version: 0.9 -" Last Updated: 2020 Oct 9 +" Last Updated: 2022 Mar 30 " " Roland Puntaier: this file contains adaptations for python3 and is parallel to pythoncomplete.vim " @@ -91,6 +91,9 @@ endfunction function! s:DefPython() py3 << PYTHONEOF +import warnings +warnings.simplefilter(action='ignore', category=FutureWarning) + import sys, tokenize, io, types from token import NAME, DEDENT, NEWLINE, STRING diff --git a/runtime/autoload/remote/define.vim b/runtime/autoload/remote/define.vim index 2aec96e365..82e5164d85 100644 --- a/runtime/autoload/remote/define.vim +++ b/runtime/autoload/remote/define.vim @@ -240,7 +240,11 @@ function! s:GetAutocmdPrefix(name, opts) endif if has_key(a:opts, 'nested') && a:opts.nested - call add(rv, 'nested') + call add(rv, '++nested') + endif + + if has_key(a:opts, 'once') && a:opts.once + call add(rv, '++once') endif return join(rv, ' ') diff --git a/runtime/compiler/jest.vim b/runtime/compiler/jest.vim index fee70b7c55..a4bb549de1 100644 --- a/runtime/compiler/jest.vim +++ b/runtime/compiler/jest.vim @@ -1,7 +1,7 @@ " Vim compiler file " Compiler: Jest " Maintainer: Doug Kearns <dougkearns@gmail.com> -" Last Change: 2018 May 15 +" Last Change: 2021 Nov 20 if exists("current_compiler") finish @@ -15,12 +15,14 @@ endif let s:cpo_save = &cpo set cpo&vim -" CompilerSet makeprg=npx\ jest\ --no-colors +" CompilerSet makeprg=npx\ --no-install\ jest\ --no-colors CompilerSet makeprg=jest\ --no-colors -CompilerSet errorformat=%E\ \ ●\ %m, +CompilerSet errorformat=%-A\ \ ●\ Console, + \%E\ \ ●\ %m, \%Z\ %\\{4}%.%#Error:\ %f:\ %m\ (%l:%c):%\\=, \%Z\ %\\{6}at\ %\\S%#\ (%f:%l:%c), + \%Z\ %\\{6}at\ %\\S%#\ %f:%l:%c, \%+C\ %\\{4}%\\w%.%#, \%+C\ %\\{4}%[-+]%.%#, \%-C%.%#, diff --git a/runtime/compiler/sml.vim b/runtime/compiler/sml.vim index c7e1b1bf16..a0b13b6c8a 100644 --- a/runtime/compiler/sml.vim +++ b/runtime/compiler/sml.vim @@ -1,7 +1,7 @@ " Vim compiler file " Compiler: SML/NJ Compiler " Maintainer: Doug Kearns <dougkearns@gmail.com> -" Last Change: 2020 Feb 10 +" Last Change: 2022 Feb 09 if exists("current_compiler") finish @@ -16,10 +16,10 @@ let s:cpo_save = &cpo set cpo&vim CompilerSet makeprg=sml -CompilerSet errorformat=%f:%l.%c-%\\d%\\+.%\\d%\\+\ %trror:\ %m, +CompilerSet errorformat=%f:%l.%c-%e.%k\ %trror:\ %m, \%f:%l.%c\ %trror:\ %m, - \%trror:\ %m - \%f:%l.%c-%\\d%\\+.%\\d%\\+\ %tarning:\ %m, + \%trror:\ %m, + \%f:%l.%c-%e.%k\ %tarning:\ %m, \%f:%l.%c\ %tarning:\ %m, \%tarning:\ %m, \%-G%.%# diff --git a/runtime/doc/api.txt b/runtime/doc/api.txt index 166fef028d..858258320d 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 @@ -452,7 +452,7 @@ Extended marks (extmarks) represent buffer annotations that track text changes in the buffer. They can represent cursors, folds, misspelled words, anything that needs to track a logical location in the buffer over time. |api-indexing| -Extmark position works like "bar" cursor: it exists between characters. Thus +Extmark position works like "bar" cursor: it exists between characters. Thus, the maximum extmark index on a line is 1 more than the character index: > f o o b a r line contents @@ -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 input a newline at that point, the extmark will accordingly migrate to the next line: > f o o z b a r| line (| = cursor) @@ -594,7 +594,8 @@ nvim__id_float({flt}) *nvim__id_float()* its argument. nvim__inspect_cell({grid}, {row}, {col}) *nvim__inspect_cell()* - TODO: Documentation + NB: if your UI doesn't use hlstate, this will not return + hlstate first time. nvim__runtime_inspect() *nvim__runtime_inspect()* TODO: Documentation @@ -634,7 +635,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. @@ -653,11 +654,11 @@ nvim_call_atomic({calls}) *nvim_call_atomic()* be returned. nvim_chan_send({chan}, {data}) *nvim_chan_send()* - Send data to channel `id` . For a job, it writes it to the + 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 @@ -683,6 +684,62 @@ nvim_create_buf({listed}, {scratch}) *nvim_create_buf()* See also: ~ buf_open_scratch + *nvim_create_user_command()* +nvim_create_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_create_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_del_current_line() *nvim_del_current_line()* Deletes the current line. @@ -698,7 +755,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 +771,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. @@ -763,6 +826,7 @@ nvim_eval_statusline({str}, {*opts}) *nvim_eval_statusline()* • maxwidth: (number) Maximum width of statusline. • fillchar: (string) Character to fill blank spaces in the statusline (see 'fillchars'). + Treated as single-width even if it isn't. • highlights: (boolean) Return highlight information. • use_tabline: (boolean) Evaluate tabline instead @@ -787,7 +851,7 @@ nvim_exec_lua({code}, {args}) *nvim_exec_lua()* inside the chunk. The chunk can return a value. Only statements are executed. To evaluate an expression, - prefix it with `return` : return my_function(...) + prefix it with `return`: return my_function(...) Parameters: ~ {code} Lua code to execute @@ -796,7 +860,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 +868,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. @@ -1018,7 +1084,7 @@ nvim_get_mode() *nvim_get_mode()* {fast} nvim_get_option({name}) *nvim_get_option()* - Gets an option value string. + Gets the global value of an option. Parameters: ~ {name} Option name @@ -1049,14 +1115,32 @@ nvim_get_option_info({name}) *nvim_get_option_info()* Return: ~ Option Information +nvim_get_option_value({name}, {*opts}) *nvim_get_option_value()* + Gets the value of an option. The behavior of this function + matches that of |:set|: the local value of an option is + returned if it exists; otherwise, the global value is + returned. Local values always correspond to the current buffer + or window. To get a buffer-local or window-local option for a + specific buffer or window, use |nvim_buf_get_option()| or + |nvim_win_get_option()|. + + Parameters: ~ + {name} Option name + {opts} Optional parameters + • scope: One of 'global' or 'local'. Analogous to + |:setglobal| and |:setlocal|, respectively. + + Return: ~ + Option value + nvim_get_proc({pid}) *nvim_get_proc()* - Gets info describing process `pid` . + Gets info describing process `pid`. Return: ~ Map of process properties, or NIL if process not found. nvim_get_proc_children({pid}) *nvim_get_proc_children()* - Gets the immediate children of process `pid` . + Gets the immediate children of process `pid`. Return: ~ Array of child process ids, empty if process not found. @@ -1165,8 +1249,8 @@ nvim_input_mouse({button}, {action}, {modifier}, {grid}, {row}, {col}) nvim_list_bufs() *nvim_list_bufs()* Gets the current list of buffer handles - Includes unlisted (unloaded/deleted) buffers, like `:ls!` . - Use |nvim_buf_is_loaded()| to check if a buffer is loaded. + Includes unlisted (unloaded/deleted) buffers, like `:ls!`. Use + |nvim_buf_is_loaded()| to check if a buffer is loaded. Return: ~ List of buffer handles @@ -1275,7 +1359,7 @@ nvim_paste({data}, {crlf}, {phase}) *nvim_paste()* Errors ('nomodifiable', `vim.paste()` failure, …) are reflected in `err` but do not affect the return value (which - is strictly decided by `vim.paste()` ). On error, subsequent + is strictly decided by `vim.paste()`). On error, subsequent calls are ignored ("drained") until the next paste is initiated (phase 1 or -1). @@ -1328,7 +1412,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: ~ @@ -1352,7 +1436,7 @@ nvim_select_popupmenu_item({item}, {insert}, {finish}, {opts}) {insert} Whether the selection should be inserted in the buffer. {finish} Finish the completion and dismiss the popupmenu. - Implies `insert` . + Implies `insert`. {opts} Optional parameters. Reserved for future use. *nvim_set_client_info()* @@ -1461,25 +1545,30 @@ nvim_set_current_win({window}) *nvim_set_current_win()* Parameters: ~ {window} Window handle -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 - {name} highlight group name, like ErrorMsg - {val} highlight definition map, like - |nvim_get_hl_by_name|. in addition the following - keys are also recognized: `default` : don't - override existing definition, like `hi default` - `ctermfg` : sets foreground of cterm color - `ctermbg` : sets background of cterm color - `cterm` : cterm attribute map. sets attributed - for cterm colors. similer to `hi cterm` Note: by - default cterm attributes are same as attributes - of gui color +nvim_set_hl({ns_id}, {name}, {*val}) *nvim_set_hl()* + Sets a highlight group. + + Note: Unlike the `:highlight` command which can update a + highlight group, this function completely replaces the + definition. For example: `nvim_set_hl(0, 'Visual', {})` will + clear the highlight group 'Visual'. + + Parameters: ~ + {ns_id} Namespace id for this highlight + |nvim_create_namespace()|. Use 0 to set a + highlight group globally |:highlight|. + {name} Highlight group name, e.g. "ErrorMsg" + {val} Highlight definition map, like |synIDattr()|. In + addition, the following keys are recognized: + • default: Don't override existing definition + |:hi-default| + • ctermfg: Sets foreground of cterm color + |highlight-ctermfg| + • ctermbg: Sets background of cterm color + |highlight-ctermbg| + • cterm: cterm attribute map, like + |highlight-args|. Note: Attributes default to + those set for `gui` if not set. nvim_set_keymap({mode}, {lhs}, {rhs}, {*opts}) *nvim_set_keymap()* Sets a global |mapping| for the given mode. @@ -1506,15 +1595,32 @@ 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 an option value. + Sets the global value of an option. + + Parameters: ~ + {name} Option name + {value} New option value + + *nvim_set_option_value()* +nvim_set_option_value({name}, {value}, {*opts}) + Sets the value of an option. The behavior of this function + matches that of |:set|: for global-local options, both the + global and local value are set unless otherwise specified with + {scope}. Parameters: ~ {name} Option name {value} New option value + {opts} Optional parameters + • scope: One of 'global' or 'local'. Analogous to + |:setglobal| and |:setlocal|, respectively. nvim_set_var({name}, {value}) *nvim_set_var()* Sets a global (g:) variable. @@ -1531,7 +1637,7 @@ nvim_set_vvar({name}, {value}) *nvim_set_vvar()* {value} Variable value nvim_strwidth({text}) *nvim_strwidth()* - Calculates the number of display cells occupied by `text` . + Calculates the number of display cells occupied by `text`. <Tab> counts as one cell. Parameters: ~ @@ -1773,9 +1879,10 @@ nvim_buf_attach({buffer}, {send_buffer}, {opts}) *nvim_buf_attach()* {buffer} Buffer handle, or 0 for current buffer {send_buffer} True if the initial notification should contain the whole buffer: first - notification will be `nvim_buf_lines_event` - . Else the first notification will be - `nvim_buf_changedtick_event` . Not for Lua + notification will be + `nvim_buf_lines_event`. Else the first + notification will be + `nvim_buf_changedtick_event`. Not for Lua callbacks. {opts} Optional parameters. • on_lines: Lua callback invoked on change. @@ -1826,12 +1933,12 @@ nvim_buf_attach({buffer}, {send_buffer}, {opts}) *nvim_buf_attach()* • on_reload: Lua callback invoked on reload. The entire buffer content should be considered changed. Args: - • the string "detach" + • the string "reload" • buffer handle • utf_sizes: include UTF-32 and UTF-16 size of the replaced region, as args to - `on_lines` . + `on_lines`. • preview: also attach to command preview (i.e. 'inccommand') events. @@ -1851,7 +1958,7 @@ nvim_buf_call({buffer}, {fun}) *nvim_buf_call()* switched If a window inside the current tabpage (including a float) already shows the buffer One of these windows will be set as current window temporarily. Otherwise a temporary - scratch window (calleed the "autocmd window" for historical + scratch window (called the "autocmd window" for historical reasons) will be used. This is useful e.g. to call vimL functions that only work with @@ -1866,6 +1973,16 @@ nvim_buf_call({buffer}, {fun}) *nvim_buf_call()* Return value of function. NB: will deepcopy lua values currently, use upvalues to send lua references in and out. + *nvim_buf_create_user_command()* +nvim_buf_create_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_create_user_command + nvim_buf_del_keymap({buffer}, {mode}, {lhs}) *nvim_buf_del_keymap()* Unmaps a buffer-local |mapping| for the given mode. @@ -1893,6 +2010,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_create_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 @@ -2035,6 +2164,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. @@ -2069,7 +2221,7 @@ nvim_buf_is_valid({buffer}) *nvim_buf_is_valid()* true if the buffer is valid, false otherwise. nvim_buf_line_count({buffer}) *nvim_buf_line_count()* - Gets the buffer line count + Returns the number of lines in the given buffer. Parameters: ~ {buffer} Buffer handle, or 0 for current buffer @@ -2175,7 +2327,7 @@ nvim_buf_set_text({buffer}, {start_row}, {start_col}, {end_row}, {end_col}, Parameters: ~ {buffer} Buffer handle, or 0 for current buffer {start_row} First line index - {start_column} Last column + {start_column} First column {end_row} Last line index {end_column} Last column {replacement} Array of lines to use as replacement @@ -2211,7 +2363,7 @@ nvim_buf_add_highlight({buffer}, {ns_id}, {hl_group}, {line}, {col_start}, namespace. All highlights in the same namespace can then be cleared with single call to |nvim_buf_clear_namespace()|. If the highlight never will be deleted by an API call, pass - `ns_id = -1` . + `ns_id = -1`. As a shorthand, `ns_id = 0` can be used to create a new namespace for the highlight, the allocated id is then @@ -2291,8 +2443,8 @@ nvim_buf_get_extmarks({buffer}, {ns_id}, {start}, {end}, {opts}) nvim_buf_get_extmarks(0, my_ns, [0,0], [-1,-1], {}) < - If `end` is less than `start` , traversal works backwards. - (Useful with `limit` , to get the first marks prior to a given + If `end` is less than `start`, traversal works backwards. + (Useful with `limit`, to get the first marks prior to a given position.) Example: @@ -2301,9 +2453,9 @@ nvim_buf_get_extmarks({buffer}, {ns_id}, {start}, {end}, {opts}) local pos = a.nvim_win_get_cursor(0) local ns = a.nvim_create_namespace('my-plugin') -- Create new extmark at line 1, column 1. - local m1 = a.nvim_buf_set_extmark(0, ns, 0, 0, 0, {}) + local m1 = a.nvim_buf_set_extmark(0, ns, 0, 0, {}) -- Create new extmark at line 3, column 1. - local m2 = a.nvim_buf_set_extmark(0, ns, 0, 2, 0, {}) + local m2 = a.nvim_buf_set_extmark(0, ns, 0, 2, {}) -- Get extmarks only from line 3. local ms = a.nvim_buf_get_extmarks(0, ns, {2,0}, {2,0}, {}) -- Get all marks in this buffer + namespace. @@ -2353,7 +2505,7 @@ nvim_buf_set_extmark({buffer}, {ns_id}, {line}, {col}, {*opts}) |api-indexing| {opts} Optional parameters. • id : id of the extmark to edit. - • end_line : ending line of the mark, 0-based + • end_row : ending line of the mark, 0-based inclusive. • end_col : ending col of the mark, 0-based exclusive. @@ -2432,6 +2584,39 @@ 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. + • sign_text: string of length 1-2 used to + display in the sign column. Note: ranges are + unsupported and decorations are only applied + to start_row + • sign_hl_group: name of the highlight group + used to highlight the sign column text. Note: + ranges are unsupported and decorations are + only applied to start_row + • number_hl_group: name of the highlight group + used to highlight the number column. Note: + ranges are unsupported and decorations are + only applied to start_row + • line_hl_group: name of the highlight group + used to highlight the whole line. Note: ranges + are unsupported and decorations are only + applied to start_row + • cursorline_hl_group: name of the highlight + group used to highlight the line when the + cursor is on the same line as the mark and + 'cursorline' is enabled. Note: ranges are + unsupported and decorations are only applied + to start_row + • conceal: string which should be either empty + or a single character. Enable concealing + similar to |:syn-conceal|. When a character is + supplied it is used as |:syn-cchar|. + "hl_group" is used as highlight for the cchar + if provided, otherwise it defaults to + |hl-Conceal|. Return: ~ Id of the created/updated extmark @@ -2500,7 +2685,7 @@ nvim_set_decoration_provider({ns_id}, {opts}) specific window. ["win", winid, bufnr, topline, botline_guess] • on_line: called for each buffer line being - redrawn. (The interation with fold lines is + redrawn. (The interaction with fold lines is subject to change) ["win", winid, bufnr, row] • on_end: called at the end of a redraw cycle ["end", tick] @@ -2635,7 +2820,7 @@ nvim_win_hide({window}) *nvim_win_hide()* |:hide| with a |window-ID|). Like |:hide| the buffer becomes hidden unless another window - is editing it, or 'bufhidden' is `unload` , `delete` or `wipe` + is editing it, or 'bufhidden' is `unload`, `delete` or `wipe` as opposed to |:close| or |nvim_win_close|, which will close the buffer. @@ -2655,7 +2840,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 @@ -2666,7 +2851,8 @@ nvim_win_set_buf({window}, {buffer}) *nvim_win_set_buf()* nvim_win_set_cursor({window}, {pos}) *nvim_win_set_cursor()* Sets the (1,0)-indexed cursor position in the window. - |api-indexing| + |api-indexing| This scrolls the window even if it is not the + current one. Parameters: ~ {window} Window handle, or 0 for current window @@ -2851,9 +3037,10 @@ nvim_open_win({buffer}, {enter}, {*config}) *nvim_open_win()* ">", "", "", "", "<" ] will only make vertical borders but not horizontal ones. By default, `FloatBorder` highlight is used, - which links to `VertSplit` when not defined. - It could also be specified by character: [ - {"+", "MyCorner"}, {"x", "MyBorder"} ]. + which links to `WinSeparator` when not + defined. It could also be specified by + character: [ {"+", "MyCorner"}, {"x", + "MyBorder"} ]. • noautocmd: If true then no buffer-related autocommand events such as |BufEnter|, @@ -2883,7 +3070,7 @@ nvim_win_set_config({window}, {*config}) *nvim_win_set_config()* layouts). When reconfiguring a floating window, absent option keys will - not be changed. `row` / `col` and `relative` must be + not be changed. `row`/`col` and `relative` must be reconfigured together. Parameters: ~ @@ -2962,6 +3149,256 @@ nvim_tabpage_set_var({tabpage}, {name}, {value}) ============================================================================== +Autocmd Functions *api-autocmd* + +nvim_clear_autocmds({*opts}) *nvim_clear_autocmds()* + Clear all autocommands that match the corresponding {opts}. To + delete a particular autocmd, see |nvim_del_autocmd|. + + Parameters: ~ + {opts} Parameters + • event: (string|table) Examples: + • event: "pat1" + • event: { "pat1" } + • event: { "pat1", "pat2", "pat3" } + + • pattern: (string|table) + • pattern or patterns to match exactly. + • For example, if you have `*.py` as that + pattern for the autocmd, you must pass + `*.py` exactly to clear it. `test.py` will + not match the pattern. + + • defaults to clearing all patterns. + • NOTE: Cannot be used with {buffer} + + • buffer: (bufnr) + • clear only |autocmd-buflocal| autocommands. + • NOTE: Cannot be used with {pattern} + + • group: (string|int) The augroup name or id. + • NOTE: If not passed, will only delete autocmds not in any group. + +nvim_create_augroup({name}, {*opts}) *nvim_create_augroup()* + Create or get an autocommand group |autocmd-groups|. + + To get an existing group id, do: > + local id = vim.api.nvim_create_augroup("MyGroup", { + clear = false + }) +< + + Parameters: ~ + {name} String: The name of the group + {opts} Dictionary Parameters + • clear (bool) optional: defaults to true. Clear + existing commands if the group already exists + |autocmd-groups|. + + Return: ~ + Integer id of the created group. + + See also: ~ + |autocmd-groups| + +nvim_create_autocmd({event}, {*opts}) *nvim_create_autocmd()* + Create an |autocommand| + + The API allows for two (mutually exclusive) types of actions + to be executed when the autocommand triggers: a callback + function (Lua or Vimscript), or a command (like regular + autocommands). + + Example using callback: > + -- Lua function + local myluafun = function() print("This buffer enters") end + + -- Vimscript function name (as a string) + local myvimfun = "g:MyVimFunction" + + vim.api.nvim_create_autocmd({"BufEnter", "BufWinEnter"}, { + pattern = {"*.c", "*.h"}, + callback = myluafun, -- Or myvimfun + }) +< + + Example using command: > + vim.api.nvim_create_autocmd({"BufEnter", "BufWinEnter"}, { + pattern = {"*.c", "*.h"}, + command = "echo 'Entering a C or C++ file'", + }) +< + + Example values for pattern: > + pattern = "*.py" + pattern = { "*.py", "*.pyi" } +< + + Examples values for event: > + "BufPreWrite" + {"CursorHold", "BufPreWrite", "BufPostWrite"} +< + + Parameters: ~ + {event} (string|array) The event or events to register + this autocommand + {opts} Dictionary of autocommand options: + • group (string|integer) optional: the + autocommand group name or id to match against. + • pattern (string|array) optional: pattern or + patterns to match against |autocmd-pattern|. + • buffer (integer) optional: buffer number for + buffer local autocommands |autocmd-buflocal|. + Cannot be used with {pattern}. + • desc (string) optional: description of the + autocommand. + • callback (function|string) optional: if a + string, the name of a Vimscript function to + call when this autocommand is triggered. + Otherwise, a Lua function which is called when + this autocommand is triggered. Cannot be used + with {command}. Lua callbacks can return true + to delete the autocommand; in addition, they + accept a single table argument with the + following keys: + • id: (number) the autocommand id + • event: (string) the name of the event that + triggered the autocommand |autocmd-events| + • group: (number|nil) the autocommand group id, + if it exists + • match: (string) the expanded value of + |<amatch>| + • buf: (number) the expanded value of |<abuf>| + • file: (string) the expanded value of + |<afile>| + + • command (string) optional: Vim command to + execute on event. Cannot be used with + {callback} + • once (boolean) optional: defaults to false. Run + the autocommand only once |autocmd-once|. + • nested (boolean) optional: defaults to false. + Run nested autocommands |autocmd-nested|. + + Return: ~ + Integer id of the created autocommand. + + See also: ~ + |autocommand| + |nvim_del_autocmd()| + +nvim_del_augroup_by_id({id}) *nvim_del_augroup_by_id()* + Delete an autocommand group by id. + + To get a group id one can use |nvim_get_autocmds()|. + + NOTE: behavior differs from |augroup-delete|. When deleting a + group, autocommands contained in this group will also be + deleted and cleared. This group will no longer exist. + + Parameters: ~ + {id} Integer The id of the group. + + See also: ~ + |nvim_del_augroup_by_name()| + |nvim_create_augroup()| + +nvim_del_augroup_by_name({name}) *nvim_del_augroup_by_name()* + Delete an autocommand group by name. + + NOTE: behavior differs from |augroup-delete|. When deleting a + group, autocommands contained in this group will also be + deleted and cleared. This group will no longer exist. + + Parameters: ~ + {name} String The name of the group. + + See also: ~ + |autocommand-groups| + +nvim_del_autocmd({id}) *nvim_del_autocmd()* + Delete an autocommand by id. + + NOTE: Only autocommands created via the API have an id. + + Parameters: ~ + {id} Integer The id returned by nvim_create_autocmd + + See also: ~ + |nvim_create_autocmd()| + +nvim_exec_autocmds({event}, {*opts}) *nvim_exec_autocmds()* + Execute all autocommands for {event} that match the + corresponding {opts} |autocmd-execute|. + + Parameters: ~ + {event} (String|Array) The event or events to execute + {opts} Dictionary of autocommand options: + • group (string|integer) optional: the + autocommand group name or id to match against. + |autocmd-groups|. + • pattern (string|array) optional: defaults to + "*" |autocmd-pattern|. Cannot be used with + {buffer}. + • buffer (integer) optional: buffer number + |autocmd-buflocal|. Cannot be used with + {pattern}. + • modeline (bool) optional: defaults to true. + Process the modeline after the autocommands + |<nomodeline>|. + + See also: ~ + |:doautocmd| + +nvim_get_autocmds({*opts}) *nvim_get_autocmds()* + Get all autocommands that match the corresponding {opts}. + + These examples will get autocommands matching ALL the given + criteria: > + -- Matches all criteria + autocommands = vim.api.nvim_get_autocmds({ + group = "MyGroup", + event = {"BufEnter", "BufWinEnter"}, + pattern = {"*.c", "*.h"} + }) + + -- All commands from one group + autocommands = vim.api.nvim_get_autocmds({ + group = "MyGroup", + }) +< + + NOTE: When multiple patterns or events are provided, it will + find all the autocommands that match any combination of them. + + Parameters: ~ + {opts} Dictionary with at least one of the following: + • group (string|integer): the autocommand group + name or id to match against. + • event (string|array): event or events to match + against |autocmd-events|. + • pattern (string|array): pattern or patterns to + match against |autocmd-pattern|. + + Return: ~ + Array of autocommands matching the criteria, with each + item containing the following fields: + • id (number): the autocommand id (only when defined with + the API). + • group (integer): the autocommand group id. + • desc (string): the autocommand description. + • event (string): the autocommand event. + • command (string): the autocommand command. + • once (boolean): whether the autocommand is only run + once. + • pattern (string): the autocommand pattern. If the + autocommand is buffer local |autocmd-buffer-local|: + • buflocal (boolean): true if the autocommand is buffer + local. + • buffer (number): the buffer number. + + +============================================================================== UI Functions *api-ui* nvim_ui_attach({width}, {height}, {options}) *nvim_ui_attach()* diff --git a/runtime/doc/arabic.txt b/runtime/doc/arabic.txt index 5d3bf7a761..0df861111c 100644 --- a/runtime/doc/arabic.txt +++ b/runtime/doc/arabic.txt @@ -175,7 +175,7 @@ o Enable Arabic settings [short-cut] vertical separator like "l" or "𝖨" may be used. It may also be hidden by changing its color to the foreground color: > :set fillchars=vert:l - :hi VertSplit ctermbg=White + :hi WinSeparator ctermbg=White < Note that this is a workaround, not a proper solution. If, on the other hand, you'd like to be verbose and explicit and diff --git a/runtime/doc/autocmd.txt b/runtime/doc/autocmd.txt index 2737ac9b9f..07158982f2 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 @@ -57,7 +57,7 @@ The special pattern <buffer> or <buffer=N> defines a buffer-local autocommand. See |autocmd-buflocal|. Note: The ":autocmd" command can only be followed by another command when the -'|' appears before {cmd}. This works: > +'|' appears where the pattern is expected. This works: > :augroup mine | au! BufRead | augroup END But this sees "augroup" as part of the defined command: > :augroup mine | au! BufRead * | augroup END @@ -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} @@ -499,10 +499,10 @@ CursorMoved After the cursor was moved in Normal or Visual mode or to another window. Also when the text of the cursor line has been changed, e.g. with "x", "rx" or "p". - Not triggered when there is typeahead, while - executing a script file, when an operator is - pending, or when moving to another window while - remaining at the same cursor position. + Not always triggered when there is typeahead, + while executing commands in a script file, or + when an operator is pending. Always triggered + when moving to another window. For an example see |match-parens|. Note: Cannot be skipped with |:noautocmd|. Careful: This is triggered very often, don't @@ -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. @@ -663,15 +674,19 @@ FuncUndefined When a user function is used but it isn't alternative is to use an autoloaded function. See |autoload-functions|. *UIEnter* -UIEnter After a UI connects via |nvim_ui_attach()|, - after VimEnter. Can be used for GUI-specific - configuration. +UIEnter After a UI connects via |nvim_ui_attach()|, or + after builtin TUI is started, after |VimEnter|. Sets these |v:event| keys: - chan + chan: 0 for builtin TUI + 1 for |--embed| + |channel-id| of the UI otherwise *UILeave* -UILeave After a UI disconnects from Nvim. +UILeave After a UI disconnects from Nvim, or after + builtin TUI is stopped, after |VimLeave|. Sets these |v:event| keys: - chan + chan: 0 for builtin TUI + 1 for |--embed| + |channel-id| of the UI otherwise *InsertChange* InsertChange When typing <Insert> while in Insert or Replace mode. The |v:insertmode| variable @@ -718,7 +733,27 @@ MenuPopup Just before showing the popup menu (under the o Operator-pending i Insert c Command line - *OptionSet* + *ModeChanged* +ModeChanged After changing the mode. The pattern is + matched against `'old_mode:new_mode'`, for + example match against `*:c` to simulate + |CmdlineEnter|. + The following values of |v:event| are set: + old_mode The mode before it changed. + new_mode The new mode as also returned + by |mode()| called with a + non-zero argument. + When ModeChanged is triggered, old_mode will + have the value of new_mode when the event was + last triggered. + This will be triggered on every minor mode + change. + Usage example to use relative line numbers + when entering visual mode: > + :au ModeChanged [vV\x16]*:* let &l:rnu = mode() =~# '^[vV\x16]' + :au ModeChanged *:[vV\x16]* let &l:rnu = mode() =~# '^[vV\x16]' + :au WinEnter,WinLeave * let &l:rnu = mode() =~# '^[vV\x16]' +< *OptionSet* OptionSet After setting an option (except during |startup|). The |autocmd-pattern| is matched against the long option name. |<amatch>| @@ -793,7 +828,7 @@ QuitPre When using `:quit`, `:wq` or `:qall`, before before QuitPre is triggered. Can be used to close any non-essential window if the current window is the last ordinary window. - See also |ExitPre|, ||WinClosed|. + See also |ExitPre|, |WinClosed|. *RemoteReply* RemoteReply When a reply from a Vim that functions as server was received |server2client()|. The @@ -804,6 +839,25 @@ RemoteReply When a reply from a Vim that functions as Note that even if an autocommand is defined, the reply should be read with |remote_read()| to consume it. + *SearchWrapped* +SearchWrapped After making a search with |n| or |N| if the + search wraps around the document back to + the start/finish respectively. + *RecordingEnter* +RecordingEnter When a macro starts recording. + The pattern is the current file name, and + |reg_recording()| is the current register that + is used. + *RecordingLeave* +RecordingLeave When a macro stops recording. + The pattern is the current file name, and + |reg_recording()| is the recorded + 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. @@ -816,7 +870,7 @@ ShellCmdPost After executing a shell command with |:!cmd|, *Signal* Signal After Nvim receives a signal. The pattern is matched against the signal name. Only - "SIGUSR1" is supported. Example: > + "SIGUSR1" and "SIGWINCH" are supported. Example: > autocmd Signal SIGUSR1 call some#func() < *ShellFilterPost* ShellFilterPost After executing a shell command with @@ -1025,27 +1079,36 @@ WinLeave Before leaving a window. If the window to be executes the BufLeave autocommands before the WinLeave autocommands (but not for ":new"). Not used for ":qa" or ":q" when exiting Vim. - After WinClosed. + Before WinClosed. *WinNew* WinNew When a new window was created. Not done for the first window, when Vim has just started. Before WinEnter. - *WinScrolled* -WinScrolled After scrolling the viewport of the current - window. + *WinScrolled* +WinScrolled After scrolling the content of a window or + resizing a window. + The pattern is matched against the + |window-ID|. Both <amatch> and <afile> are + set to the |window-ID|. + Non-recursive (the event cannot trigger + itself). However, if the command causes the + window to scroll or change size another + WinScrolled event will be triggered later. + Does not trigger when the command is added, + only after the first scroll or resize. ============================================================================== -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). @@ -1356,7 +1419,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 @@ -1455,7 +1518,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..49aa52a92f --- /dev/null +++ b/runtime/doc/builtin.txt @@ -0,0 +1,9078 @@ +*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} +digraph_get({chars}) String get the digraph of {chars} +digraph_getlist([{listall}]) List get all |digraph|s +digraph_set({chars}, {digraph}) Boolean register |digraph| +digraph_setlist({digraphlist}) Boolean register multiple |digraph|s +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. The 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) +< + +digraph_get({chars}) *digraph_get()* *E1214* + Return the digraph of {chars}. This should be a string with + exactly two characters. If {chars} are not just two + characters, or the digraph of {chars} does not exist, an error + is given and an empty string is returned. + + Also see |digraph_getlist()|. + + Examples: > + " Get a built-in digraph + :echo digraph_get('00') " Returns '∞' + + " Get a user-defined digraph + :call digraph_set('aa', 'あ') + :echo digraph_get('aa') " Returns 'あ' +< + Can also be used as a |method|: > + GetChars()->digraph_get() +< + +digraph_getlist([{listall}]) *digraph_getlist()* + Return a list of digraphs. If the {listall} argument is given + and it is TRUE, return all digraphs, including the default + digraphs. Otherwise, return only user-defined digraphs. + + Also see |digraph_get()|. + + Examples: > + " Get user-defined digraphs + :echo digraph_getlist() + + " Get all the digraphs, including default digraphs + :echo digraph_getlist(1) +< + Can also be used as a |method|: > + GetNumber()->digraph_getlist() +< + +digraph_set({chars}, {digraph}) *digraph_set()* + Add digraph {chars} to the list. {chars} must be a string + with two characters. {digraph} is a string with one UTF-8 + encoded character. *E1215* + Be careful, composing characters are NOT ignored. This + function is similar to |:digraphs| command, but useful to add + digraphs start with a white space. + + The function result is v:true if |digraph| is registered. If + this fails an error message is given and v:false is returned. + + If you want to define multiple digraphs at once, you can use + |digraph_setlist()|. + + Example: > + call digraph_set(' ', 'あ') +< + Can be used as a |method|: > + GetString()->digraph_set('あ') +< + +digraph_setlist({digraphlist}) *digraph_setlist()* + Similar to |digraph_set()| but this function can add multiple + digraphs at once. {digraphlist} is a list composed of lists, + where each list contains two strings with {chars} and + {digraph} as in |digraph_set()|. *E1216* + Example: > + call digraph_setlist([['aa', 'あ'], ['ii', 'い']]) +< + It is similar to the following: > + for [chars, digraph] in [['aa', 'あ'], ['ii', 'い']] + call digraph_set(chars, digraph) + endfor +< Except that the function returns after the first error, + following digraphs will not be added. + + Can be used as a |method|: > + GetList()->digraph_setlist() +< + +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}. + + In addition to the items supported by |getqflist()| in {what}, + the following item is supported by |getloclist()|: + + filewinid 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 + qfbufnr number of the buffer displayed in the quickfix + window. Returns 0 if the quickfix buffer is + not present. See |quickfix-buffer|. + 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 + qfbufnr number of the buffer displayed in the quickfix + window. 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-matching| 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") +< + You can use `call()` to pass the items as a list. + + 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 + end_lnum end of lines, if the item spans multiple lines + 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 + end_col end column, if the item spans multiple columns + 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 + "underlineline" "1" if double underlined + "undercurl" "1" if undercurled + "underdot" "1" if dotted underlined + "underdash" "1" if dashed underlined + "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 + Universal/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..0d19f025ec 100644 --- a/runtime/doc/change.txt +++ b/runtime/doc/change.txt @@ -42,8 +42,6 @@ For inserting text see |insert.txt|. of the line and [count]-1 more lines [into register x]; synonym for "d$". (not |linewise|) - When the '#' flag is in 'cpoptions' the count is - ignored. {Visual}["x]x or *v_x* *v_d* *v_<Del>* {Visual}["x]d or @@ -355,14 +353,14 @@ CTRL-A Add [count] to the number or alphabetic character at *v_CTRL-A* {Visual}CTRL-A Add [count] to the number or alphabetic character in - the highlighted text. {not in Vi} + the highlighted text. *v_g_CTRL-A* {Visual}g CTRL-A Add [count] to the number or alphabetic character in the highlighted text. If several lines are highlighted, each one will be incremented by an additional [count] (so effectively creating a - [count] incrementing sequence). {not in Vi} + [count] incrementing sequence). For Example, if you have this list of numbers: 1. ~ 1. ~ @@ -381,14 +379,14 @@ CTRL-X Subtract [count] from the number or alphabetic *v_CTRL-X* {Visual}CTRL-X Subtract [count] from the number or alphabetic - character in the highlighted text. {not in Vi} + character in the highlighted text. *v_g_CTRL-X* {Visual}g CTRL-X Subtract [count] from the number or alphabetic character in the highlighted text. If several lines are highlighted, each value will be decremented by an additional [count] (so effectively creating a [count] - decrementing sequence). {not in Vi} + decrementing sequence). The CTRL-A and CTRL-X commands work for (signed) decimal numbers, unsigned binary/octal/hexadecimal numbers and alphabetic characters. @@ -906,7 +904,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 @@ -1018,7 +1016,7 @@ inside of strings can change! Also see 'softtabstop' option. > in [range] (default: current line |cmdline-ranges|), [into register x]. - *p* *put* *E353* + *p* *put* *E353* *E1240* ["x]p Put the text [from register x] after the cursor [count] times. @@ -1065,7 +1063,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 +1116,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 +1596,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 656bb10c45..d4bed7a5f2 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* @@ -210,6 +210,11 @@ effective prompt text for a buffer, with |prompt_getprompt()|. The user can go to Normal mode and navigate through the buffer. This can be useful to see older output or copy text. +The CTRL-W key can be used to start a window command, such as CTRL-W w to +switch to the next window. This also works in Insert mode (use Shift-CTRL-W +to delete a word). When leaving the window Insert mode will be stopped. When +coming back to the prompt window Insert mode will be restored. + Any command that starts Insert mode, such as "a", "i", "A" and "I", will move the cursor to the last line. "A" will move to the end of the line, "I" to the start of the line. @@ -224,12 +229,12 @@ prompt. > call chansend(g:shell_job, [a:text, '']) endfunc - " Function handling output from the shell: Added above the prompt. + " Function handling output from the shell: Add it above the prompt. func GotOutput(channel, msg, name) call append(line("$") - 1, a:msg) endfunc - " Function handling the shell exit: close the window. + " Function handling the shell exits: close the window. func JobExit(job, status, event) quit! endfunc diff --git a/runtime/doc/cmdline.txt b/runtime/doc/cmdline.txt index 6b46ac9cf2..9fa2034718 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 @@ -679,7 +679,7 @@ If more line specifiers are given than required for the command, the first one(s) will be ignored. Line numbers may be specified with: *:range* *{address}* - {number} an absolute line number + {number} an absolute line number *E1247* . the current line *:.* $ the last line in the file *:$* % equal to 1,$ (the entire file) *:%* @@ -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 @@ -863,9 +864,11 @@ Note: these are typed literally, they are not special keys! *:<amatch>* *<amatch>* <amatch> When executing autocommands, is replaced with the match for which this autocommand was executed. *E497* - It differs from <afile> only when the file name isn't used - to match with (for FileType, Syntax and SpellFileMissing + It differs from <afile> when the file name isn't used to + match with (for FileType, Syntax and SpellFileMissing events). + When the match is with a file name, it is expanded to the + full path. *:<sfile>* *<sfile>* <sfile> When executing a ":source" command, is replaced with the file name of the sourced file. *E498* @@ -905,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..b31ac47bda 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 @@ -204,7 +204,7 @@ Docstring format: - Use `<pre>` for code samples. Example: the help for |vim.paste()| is generated from a docstring decorating -vim.paste in src/nvim/lua/vim.lua like this: > +vim.paste in runtime/lua/vim/_editor.lua like this: > --- Paste handler, invoked by |nvim_paste()| when a conforming UI --- (such as the |TUI|) pastes text into the editor. @@ -243,12 +243,13 @@ If the function acts on an object then {thing} is the name of that object with a {thing} that groups functions under a common concept). Use existing common {action} names if possible: - add Append to, or insert into, a collection - del Delete a thing (or group of things) - exec Execute code - get Get a thing (or group of things by query) - list Get all things - set Set a thing (or group of things) + add Append to, or insert into, a collection + create Create a new thing + del Delete a thing (or group of things) + exec Execute code + get Get a thing (or group of things by query) + list Get all things + set Set a thing (or group of things) Use consistent names for {thing} in all API functions. E.g. a buffer is called "buf" everywhere, not "buffer" in some places and "buf" in others. diff --git a/runtime/doc/diagnostic.txt b/runtime/doc/diagnostic.txt index 1dd9c4f301..32936a7ee6 100644 --- a/runtime/doc/diagnostic.txt +++ b/runtime/doc/diagnostic.txt @@ -39,15 +39,19 @@ modify the diagnostics for a buffer (e.g. |vim.diagnostic.set()|) then it requires a namespace. *diagnostic-structure* -A diagnostic is a Lua table with the following keys: +A diagnostic is a Lua table with the following keys. Required keys are +indicated with (*): - lnum: The starting line of the diagnostic + bufnr: Buffer number + lnum(*): The starting line of the diagnostic end_lnum: The final line of the diagnostic - col: The starting column of the diagnostic + col(*): The starting column of the diagnostic end_col: The final column of the diagnostic severity: The severity of the diagnostic |vim.diagnostic.severity| - message: The diagnostic text + message(*): The diagnostic text source: The source of the diagnostic + code: The diagnostic code + user_data: Arbitrary data plugins or users can add Diagnostics use the same indexing as the rest of the Nvim API (i.e. 0-based rows and columns). |api-indexing| @@ -70,7 +74,7 @@ Functions that take a severity as an optional parameter (e.g. 2. A table with a "min" or "max" key (or both): > - vim.diagnostic.get(0, { severity = {min=vim.diagnostic.severity.WARN}) + vim.diagnostic.get(0, { severity = {min=vim.diagnostic.severity.WARN} }) The latter form allows users to specify a range of severities. @@ -130,7 +134,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 @@ -175,8 +179,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 @@ -239,7 +244,7 @@ DiagnosticUnderlineHint *hl-DiagnosticFloatingError* DiagnosticFloatingError Used to color "Error" diagnostic messages in diagnostics float. - See |vim.diagnostic.show_line_diagnostics()| + See |vim.diagnostic.open_float()| *hl-DiagnosticFloatingWarn* DiagnosticFloatingWarn @@ -289,14 +294,13 @@ option in the "signs" table of |vim.diagnostic.config()| or 10 if unset). ============================================================================== EVENTS *diagnostic-events* - *DiagnosticsChanged* -DiagnosticsChanged After diagnostics have changed. + *DiagnosticChanged* +DiagnosticChanged After diagnostics have changed. Example: > - autocmd User DiagnosticsChanged lua vim.diagnostic.setqflist({open = false }) + autocmd DiagnosticChanged * lua vim.diagnostic.setqflist({open = false }) < ============================================================================== -============================================================================== Lua module: vim.diagnostic *diagnostic-api* config({opts}, {namespace}) *vim.diagnostic.config()* @@ -324,16 +328,17 @@ config({opts}, {namespace}) *vim.diagnostic.config()* Note: Each of the configuration options below accepts one of the following: - • `false` : Disable this feature - • `true` : Enable this feature, use default settings. - • `table` : Enable this feature with overrides. Use an + • `false`: Disable this feature + • `true`: Enable this feature, use default settings. + • `table`: Enable this feature with overrides. Use an empty table to use default values. - • `function` : Function with signature (namespace, bufnr) + • `function`: Function with signature (namespace, bufnr) 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 @@ -341,13 +346,24 @@ 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| - • source: (string) Include the diagnostic - source in virtual text. One of "always" - or "if_many". + • 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 @@ -373,39 +389,8 @@ config({opts}, {namespace}) *vim.diagnostic.config()* Otherwise, all signs use the same priority. - • float: Options for floating windows: - • severity: See |diagnostic-severity|. - • header: (string or table) String to use - as the header for the floating window. If - a table, it is interpreted as a [text, - hl_group] tuple. Defaults to - "Diagnostics:". - • source: (string) Include the diagnostic - source in the message. One of "always" or - "if_many". - • 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. - • prefix: (function, string, or table) - Prefix each diagnostic in the floating - window. If a function, it must have the - signature (diagnostic, i, total) -> - (string, string), where {i} is the index - of the diagnostic being evaluated and - {total} is the total number of - diagnostics displayed in the window. The - function should return a string which is - prepended to each diagnostic in the - window as well as an (optional) highlight - group which will be used to highlight the - prefix. If {prefix} is a table, it is - interpreted as a [text, hl_group] tuple - as in |nvim_echo()|; otherwise, if - {prefix} is a string, it is prepended to - each diagnostic in the window with no - highlight. - + • float: Options for floating windows. See + |vim.diagnostic.open_float()|. • update_in_insert: (default false) Update diagnostics in Insert mode (if false, diagnostics are updated on InsertLeave) @@ -470,7 +455,7 @@ get_namespace({namespace}) *vim.diagnostic.get_namespace()* Get namespace metadata. Parameters: ~ - {ns} number Diagnostic namespace + {namespace} number Diagnostic namespace Return: ~ table Namespace metadata @@ -539,6 +524,9 @@ goto_next({opts}) *vim.diagnostic.goto_next()* "true", call |vim.diagnostic.open_float()| after moving. If a table, pass the table as the {opts} parameter to |vim.diagnostic.open_float()|. + Unless overridden, the float will show + diagnostics at the new cursor position (as if + "cursor" were passed to the "scope" option). • win_id: (number, default 0) Window ID goto_prev({opts}) *vim.diagnostic.goto_prev()* @@ -602,48 +590,67 @@ match({str}, {pat}, {groups}, {severity_map}, {defaults}) diagnostic |diagnostic-structure| or `nil` if {pat} fails to match {str}. -open_float({bufnr}, {opts}) *vim.diagnostic.open_float()* +open_float({opts}, {...}) *vim.diagnostic.open_float()* Show diagnostics in a floating window. Parameters: ~ - {bufnr} number|nil Buffer number. Defaults to the current - buffer. - {opts} table|nil Configuration table with the same keys - as |vim.lsp.util.open_floating_preview()| in - addition to the following: - • namespace: (number) Limit diagnostics to the - given namespace - • scope: (string, default "buffer") Show - diagnostics from the whole buffer ("buffer"), - the current cursor line ("line"), or the - current cursor position ("cursor"). - • pos: (number or table) If {scope} is "line" or - "cursor", use this position rather than the - cursor position. If a number, interpreted as a - line number; otherwise, a (row, col) tuple. - • severity_sort: (default false) Sort diagnostics - by severity. Overrides the setting from - |vim.diagnostic.config()|. - • severity: See |diagnostic-severity|. Overrides - the setting from |vim.diagnostic.config()|. - • header: (string or table) String to use as the - header for the floating window. If a table, it - is interpreted as a [text, hl_group] tuple. - Overrides the setting from - |vim.diagnostic.config()|. - • source: (string) Include the diagnostic source - in the message. One of "always" or "if_many". - Overrides the setting from - |vim.diagnostic.config()|. - • 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. Overrides the setting from - |vim.diagnostic.config()|. - • prefix: (function, string, or table) Prefix - each diagnostic in the floating window. - Overrides the setting from - |vim.diagnostic.config()|. + {opts} table|nil Configuration table with the same keys + as |vim.lsp.util.open_floating_preview()| in + addition to the following: + • bufnr: (number) Buffer number to show + diagnostics from. Defaults to the current + buffer. + • namespace: (number) Limit diagnostics to the + given namespace + • scope: (string, default "line") Show diagnostics + from the whole buffer ("buffer"), the current + cursor line ("line"), or the current cursor + position ("cursor"). Shorthand versions are also + accepted ("c" for "cursor", "l" for "line", "b" + for "buffer"). + • pos: (number or table) If {scope} is "line" or + "cursor", use this position rather than the + cursor position. If a number, interpreted as a + line number; otherwise, a (row, col) tuple. + • severity_sort: (default false) Sort diagnostics + by severity. Overrides the setting from + |vim.diagnostic.config()|. + • severity: See |diagnostic-severity|. Overrides + the setting from |vim.diagnostic.config()|. + • header: (string or table) String to use as the + header for the floating window. If a table, it + is interpreted as a [text, hl_group] tuple. + Overrides the setting from + |vim.diagnostic.config()|. + • source: (boolean or string) Include the + diagnostic source in the message. Use "if_many" + to only show sources if there is more than one + source of diagnostics in the buffer. Otherwise, + any truthy value means to always show the + diagnostic source. Overrides the setting from + |vim.diagnostic.config()|. + • 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. Overrides the setting from + |vim.diagnostic.config()|. + • prefix: (function, string, or table) Prefix each + diagnostic in the floating window. If a + function, it must have the signature + (diagnostic, i, total) -> (string, string), + where {i} is the index of the diagnostic being + evaluated and {total} is the total number of + diagnostics displayed in the window. The + function should return a string which is + prepended to each diagnostic in the window as + well as an (optional) highlight group which will + be used to highlight the prefix. If {prefix} is + a table, it is interpreted as a [text, hl_group] + tuple as in |nvim_echo()|; otherwise, if + {prefix} is a string, it is prepended to each + diagnostic in the window with no highlight. + Overrides the setting from + |vim.diagnostic.config()|. Return: ~ tuple ({float_bufnr}, {win_id}) 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/digraph.txt b/runtime/doc/digraph.txt index dd7e9a331a..eb3de0111f 100644 --- a/runtime/doc/digraph.txt +++ b/runtime/doc/digraph.txt @@ -35,6 +35,9 @@ An alternative is using the 'keymap' option. < Avoid defining a digraph with '_' (underscore) as the first character, it has a special meaning in the future. + NOTE: This command cannot add a digraph that starts + with a white space. If you want to add such digraph, + you can use |digraph_set()| instead. Example of the output of ":digraphs": > TH Þ 222 ss ß 223 a! à 224 a' á 225 a> â 226 a? ã 227 a: ä 228 diff --git a/runtime/doc/editing.txt b/runtime/doc/editing.txt index 4e3173cfa9..1cc8c21462 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 @@ -1253,10 +1253,12 @@ working directory. If a local working directory (tab or window) does not exist, the next-higher scope in the hierarchy applies. *:cd* *E747* *E472* -:cd[!] On non-Unix systems: Print the current directory - name. On Unix systems: Change the current directory - to the home directory. Use |:pwd| to print the - current directory on all systems. +:cd[!] On non-Unix systems when 'cdhome' is off: Print the + current directory name. + Otherwise: Change the current directory to the home + directory. Clear any window-local directory. + Use |:pwd| to print the current directory on all + systems. :cd[!] {path} Change the current directory to {path}. If {path} is relative, it is searched for in the @@ -1329,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. @@ -1447,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] @@ -1487,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: @@ -1566,6 +1574,10 @@ 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, and also does not search in the parent of + the non-existing directory if upwards searching is used. + 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 eb3dbd9b70..e3d4ef2085 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* @@ -196,7 +196,7 @@ position in the sequence. List creation ~ *E696* *E697* -A List is created with a comma separated list of items in square brackets. +A List is created with a comma-separated list of items in square brackets. Examples: > :let mylist = [1, two, 3, "four"] :let emptylist = [] @@ -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 ~ @@ -440,7 +446,7 @@ ordering. Dictionary creation ~ *E720* *E721* *E722* *E723* -A Dictionary is created with a comma separated list of entries in curly +A Dictionary is created with a comma-separated list of entries in curly braces. Each entry has a key and a value, separated by a colon. Each key can only appear once. Examples: > :let mydict = {1: 'one', 2: 'two', 3: 'three'} @@ -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 @@ -1644,6 +1651,7 @@ Some variables can be set by the user, but the type cannot be changed. *v:argv* *argv-variable* v:argv The command line arguments Vim was invoked with. This is a list of strings. The first item is the Vim command. + See |v:progpath| for the command with full path. *v:beval_col* *beval_col-variable* v:beval_col The number of the column, over which the mouse pointer is. @@ -1729,7 +1737,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 +1883,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 @@ -2015,6 +2028,9 @@ v:null Special value used to put "null" in JSON and NIL in msgpack. used as a String (e.g. in |expr5| with string concatenation operator) and to zero when used as a Number (e.g. in |expr5| or |expr7| when used with numeric operators). Read-only. + In some places `v:null` can be used for a List, Dict, etc. + that is not set. That is slightly different than an empty + List, Dict, etc. *v:numbermax* *numbermax-variable* v:numbermax Maximum value of a number. @@ -2042,10 +2058,29 @@ v:option_new New value of the option. Valid while executing an |OptionSet| autocommand. *v:option_old* v:option_old Old value of the option. Valid while executing an |OptionSet| - autocommand. + autocommand. Depending on the command used for setting and the + kind of option this is either the local old value or the + global old value. + *v:option_oldlocal* +v:option_oldlocal + Old local value of the option. Valid while executing an + |OptionSet| autocommand. + *v:option_oldglobal* +v:option_oldglobal + Old global value of the option. Valid while executing an + |OptionSet| autocommand. *v:option_type* v:option_type Scope of the set command. Valid while executing an |OptionSet| autocommand. Can be either "global" or "local" + *v:option_command* +v:option_command + Command used to set the option. Valid while executing an + |OptionSet| autocommand. + value option was set via ~ + "setlocal" |:setlocal| or ":let l:xxx" + "setglobal" |:setglobal| or ":let g:xxx" + "set" |:set| or |:let| + "modeline" |modeline| *v:operator* *operator-variable* v:operator The last operator given in Normal mode. This is a single character except for commands starting with <g> or <z>, @@ -2247,8386 +2282,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 -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_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. -< 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 - -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 second argument is passed - in: > - 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({expr}) *expandcmd()* - Expand special items in {expr} like what is done for an Ex - command such as `:edit`. This expands special keywords, like - with |expand()|, and environment variables, anywhere in - {expr}. "~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] - -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} - - *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') - -< - *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. - Tabs and windows are identified by their respective numbers, - 0 means current tab or window. Missing argument implies 0. - Thus the following are equivalent: > - getcwd() - 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 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. - -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 {regname} was not set the result is an empty string. - The {regname} argument is 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()| - 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 used for the - second argument: > - GetPattern()->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|, where the base is passed as - the argument to the called function: > - 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|, where the base is passed as - the argument to the called function: > - 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 |Q| or |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. - - .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, or the maximum number of - bytes 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. - -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_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. - -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. - -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. - -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. - -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. - -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. - - -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) - - -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 -< - *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|. - -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") -< -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()|. - 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. - -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|. - -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()|. - -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. - -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|. - -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. - -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}. - -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. - - *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()|. - - -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. - - - *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') - -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|. - -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|. - -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 -< - -setwinvar({nr}, {varname}, {val}) *setwinvar()* - Like |settabwinvar()| for the current tab page. - Examples: > - :call setwinvar(1, "&list", 0) - :call setwinvar(2, "myvar", "foobar") - -sha256({string}) *sha256()* - Returns a String with 64 hex characters, which is the SHA256 - checksum of {string}. - -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|. - - -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. - -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()|. - - -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. - - *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. - - *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. - - -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('123') -< - 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. - - -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 -< -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'. - -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()|. - -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. - -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()|. - -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(). - - *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) -< -strptime({format}, {timestring}) *strptime()* - The result is a Number, which is a unix timestamp representing - the date and time in {timestring}, which is expected to match - the format specified in {format}. - - The accepted {format} depends on your system, thus this is not - portable! See the manual page of the C function strptime() - for the format. Especially avoid "%c". The value of $TZ also - matters. - - If the {timestring} cannot be parsed with {format} zero is - returned. If you do not know the format of {timestring} you - can try different {format} values until you get a non-zero - result. - - See also |strftime()|. - Examples: > - :echo strptime("%Y %b %d %X", "1997 Apr 27 11:49:23") -< 862156163 > - :echo strftime("%c", strptime("%y%m%d %T", "970427 11:53:55")) -< Sun Apr 27 11:53:55 1997 > - :echo strftime("%c", strptime("%Y%m%d%H%M%S", "19970427115355") + 3600) -< Sun Apr 27 12:53:55 1997 - - -strridx({haystack}, {needle} [, {start}]) *strridx()* - The result is a Number, which gives the byte index in - {haystack} of the last occurrence of the String {needle}. - 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(). - -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. - -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 - -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. - -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. - - -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. - - *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. - -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 - -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|. - - *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. - -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. - -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). - -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). - -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}" - -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" - -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|. - -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, '$'])")) - - -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') - -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. - -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. - -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". - -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. - -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. - -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. - -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. - -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. - - *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." -< -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]]] -< - *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') -< - *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. - - *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. - - -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") - - -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* @@ -10776,7 +2438,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 @@ -10873,9 +2535,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 @@ -10914,7 +2576,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(".")) < @@ -10925,7 +2587,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() < @@ -11087,7 +2749,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. @@ -11149,7 +2811,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. @@ -11404,14 +3066,17 @@ text... opposite of |:lockvar|. :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. +: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 and to make + auto-indenting work properly. From Vim version 4.5 until 5.0, every Ex command in - between the ":if" and ":endif" is ignored. These two + between the `:if` and `:endif` is ignored. These two commands were just to allow for future expansions in a backward compatible way. Nesting was allowed. Note - that any ":else" or ":elseif" was ignored, the "else" + that any `:else` or `:elseif` was ignored, the `else` part was not executed either. You can use this to remain compatible with older @@ -11420,32 +3085,32 @@ text... : version-5-specific-commands :endif < The commands still need to be parsed to find the - "endif". Sometimes an older Vim has a problem with a - new command. For example, ":silent" is recognized as - a ":substitute" command. In that case ":execute" can + `endif`. Sometimes an older Vim has a problem with a + new command. For example, `:silent` is recognized as + a `:substitute` command. In that case `:execute` can avoid problems: > :if version >= 600 : execute "silent 1,$delete" :endif < - NOTE: The ":append" and ":insert" commands don't work - properly in between ":if" and ":endif". + NOTE: The `:append` and `:insert` commands don't work + properly in between `:if` and `:endif`. *:else* *:el* *E581* *E583* -:el[se] Execute the commands until the next matching ":else" - or ":endif" if they previously were not being +:el[se] Execute the commands until the next matching `:else` + or `:endif` if they previously were not being executed. *:elseif* *:elsei* *E582* *E584* -:elsei[f] {expr1} Short for ":else" ":if", with the addition that there - is no extra ":endif". +:elsei[f] {expr1} Short for `:else` `:if`, with the addition that there + is no extra `:endif`. :wh[ile] {expr1} *:while* *:endwhile* *:wh* *:endw* *E170* *E585* *E588* *E733* -:endw[hile] Repeat the commands between ":while" and ":endwhile", +:endw[hile] Repeat the commands between `:while` and `:endwhile`, as long as {expr1} evaluates to non-zero. When an error is detected from a command inside the - loop, execution continues after the "endwhile". + loop, execution continues after the `endwhile`. Example: > :let lnum = 1 :while lnum <= line("$") @@ -11453,16 +3118,16 @@ text... :let lnum = lnum + 1 :endwhile < - NOTE: The ":append" and ":insert" commands don't work - properly inside a ":while" and ":for" loop. + NOTE: The `:append` and `:insert` commands don't work + properly inside a `:while` and `:for` loop. :for {var} in {object} *:for* *E690* *E732* :endfo[r] *:endfo* *:endfor* - Repeat the commands between ":for" and ":endfor" for + Repeat the commands between `:for` and `:endfor` for each item in {object}. {object} can be a |List| or a |Blob|. Variable {var} is set to the value of each item. When an error is detected for a command inside - the loop, execution continues after the "endfor". + the loop, execution continues after the `endfor`. Changing {object} inside the loop affects what items are used. Make a copy if this is unwanted: > :for item in copy(mylist) @@ -11486,7 +3151,7 @@ text... :for [{var1}, {var2}, ...] in {listlist} :endfo[r] - Like ":for" above, but each item in {listlist} must be + Like `:for` above, but each item in {listlist} must be a list, of which each item is assigned to {var1}, {var2}, etc. Example: > :for [lnum, col] in [[1, 3], [2, 5], [3, 8]] @@ -11494,38 +3159,39 @@ text... :endfor < *:continue* *:con* *E586* -:con[tinue] When used inside a ":while" or ":for" loop, jumps back +:con[tinue] When used inside a `:while` or `:for` loop, jumps back to the start of the loop. - If it is used after a |:try| inside the loop but - before the matching |:finally| (if present), the - commands following the ":finally" up to the matching - |:endtry| are executed first. This process applies to - all nested ":try"s inside the loop. The outermost - ":endtry" then jumps back to the start of the loop. + + If it is used after a `:try` inside the loop but + before the matching `:finally` (if present), the + commands following the `:finally` up to the matching + `:endtry` are executed first. This process applies to + all nested `:try`s inside the loop. The outermost + `:endtry` then jumps back to the start of the loop. *:break* *:brea* *E587* -:brea[k] When used inside a ":while" or ":for" loop, skips to - the command after the matching ":endwhile" or - ":endfor". - If it is used after a |:try| inside the loop but - before the matching |:finally| (if present), the - commands following the ":finally" up to the matching - |:endtry| are executed first. This process applies to - all nested ":try"s inside the loop. The outermost - ":endtry" then jumps to the command after the loop. +:brea[k] When used inside a `:while` or `:for` loop, skips to + the command after the matching `:endwhile` or + `:endfor`. + If it is used after a `:try` inside the loop but + before the matching `:finally` (if present), the + commands following the `:finally` up to the matching + `:endtry` are executed first. This process applies to + all nested `:try`s inside the loop. The outermost + `:endtry` then jumps to the command after the loop. :try *:try* *:endt* *:endtry* *E600* *E601* *E602* :endt[ry] Change the error handling for the commands between - ":try" and ":endtry" including everything being - executed across ":source" commands, function calls, + `:try` and `:endtry` including everything being + executed across `:source` commands, function calls, or autocommand invocations. When an error or interrupt is detected and there is - a |:finally| command following, execution continues - after the ":finally". Otherwise, or when the - ":endtry" is reached thereafter, the next - (dynamically) surrounding ":try" is checked for - a corresponding ":finally" etc. Then the script + a `:finally` command following, execution continues + after the `:finally`. Otherwise, or when the + `:endtry` is reached thereafter, the next + (dynamically) surrounding `:try` is checked for + a corresponding `:finally` etc. Then the script processing is terminated. Whether a function definition has an "abort" argument does not matter. Example: > @@ -11533,9 +3199,9 @@ text... echomsg "not reached" < Moreover, an error or interrupt (dynamically) inside - ":try" and ":endtry" is converted to an exception. It - can be caught as if it were thrown by a |:throw| - command (see |:catch|). In this case, the script + `:try` and `:endtry` is converted to an exception. It + can be caught as if it were thrown by a `:throw` + command (see `:catch`). In this case, the script processing is not terminated. The value "Vim:Interrupt" is used for an interrupt @@ -11551,22 +3217,22 @@ text... try | edit | catch /^Vim(edit):E\d\+/ | echo "error" | endtry < *:cat* *:catch* *E603* *E604* *E605* -:cat[ch] /{pattern}/ The following commands until the next |:catch|, - |:finally|, or |:endtry| that belongs to the same - |:try| as the ":catch" are executed when an exception +:cat[ch] /{pattern}/ The following commands until the next `:catch`, + `:finally`, or `:endtry` that belongs to the same + `:try` as the `:catch` are executed when an exception matching {pattern} is being thrown and has not yet - been caught by a previous ":catch". Otherwise, these + been caught by a previous `:catch`. Otherwise, these commands are skipped. When {pattern} is omitted all errors are caught. Examples: > - :catch /^Vim:Interrupt$/ " catch interrupts (CTRL-C) - :catch /^Vim\%((\a\+)\)\=:E/ " catch all Vim errors - :catch /^Vim\%((\a\+)\)\=:/ " catch errors and interrupts - :catch /^Vim(write):/ " catch all errors in :write - :catch /^Vim\%((\a\+)\)\=:E123/ " catch error E123 - :catch /my-exception/ " catch user exception - :catch /.*/ " catch everything - :catch " same as /.*/ + :catch /^Vim:Interrupt$/ " catch interrupts (CTRL-C) + :catch /^Vim\%((\a\+)\)\=:E/ " catch all Vim errors + :catch /^Vim\%((\a\+)\)\=:/ " catch errors and interrupts + :catch /^Vim(write):/ " catch all errors in :write + :catch /^Vim\%((\a\+)\)\=:E123:/ " catch error E123 + :catch /my-exception/ " catch user exception + :catch /.*/ " catch everything + :catch " same as /.*/ < Another character can be used instead of / around the {pattern}, so long as it does not have a special @@ -11579,27 +3245,27 @@ text... locales. *:fina* *:finally* *E606* *E607* -:fina[lly] The following commands until the matching |:endtry| +:fina[lly] The following commands until the matching `:endtry` are executed whenever the part between the matching - |:try| and the ":finally" is left: either by falling - through to the ":finally" or by a |:continue|, - |:break|, |:finish|, or |:return|, or by an error or - interrupt or exception (see |:throw|). + `:try` and the `:finally` is left: either by falling + through to the `:finally` or by a `:continue`, + `:break`, `:finish`, or `:return`, or by an error or + interrupt or exception (see `:throw`). *:th* *:throw* *E608* :th[row] {expr1} The {expr1} is evaluated and thrown as an exception. - If the ":throw" is used after a |:try| but before the - first corresponding |:catch|, commands are skipped - until the first ":catch" matching {expr1} is reached. - If there is no such ":catch" or if the ":throw" is - used after a ":catch" but before the |:finally|, the - commands following the ":finally" (if present) up to - the matching |:endtry| are executed. If the ":throw" - is after the ":finally", commands up to the ":endtry" - are skipped. At the ":endtry", this process applies - again for the next dynamically surrounding ":try" + If the `:throw` is used after a `:try` but before the + first corresponding `:catch`, commands are skipped + until the first `:catch` matching {expr1} is reached. + If there is no such `:catch` or if the `:throw` is + used after a `:catch` but before the `:finally`, the + commands following the `:finally` (if present) up to + the matching `:endtry` are executed. If the `:throw` + is after the `:finally`, commands up to the `:endtry` + are skipped. At the `:endtry`, this process applies + again for the next dynamically surrounding `:try` (which may be found in a calling function or sourcing - script), until a matching ":catch" has been found. + script), until a matching `:catch` has been found. If the exception is not caught, the command processing is terminated. Example: > @@ -11614,7 +3280,7 @@ text... Also see |:comment|. Use "\n" to start a new line. Use "\r" to move the cursor to the first column. - Uses the highlighting set by the |:echohl| command. + Uses the highlighting set by the `:echohl` command. Cannot be followed by a comment. Example: > :echo "the value of 'shell' is" &shell @@ -11623,9 +3289,9 @@ text... And since Vim mostly postpones redrawing until it's finished with a sequence of commands this happens quite often. To avoid that a command from before the - ":echo" causes a redraw afterwards (redraws are often + `:echo` causes a redraw afterwards (redraws are often postponed until you type something), force a redraw - with the |:redraw| command. Example: > + with the `:redraw` command. Example: > :new | redraw | echo "there is a new window" < *:echo-self-refer* When printing nested containers echo prints second @@ -11644,13 +3310,13 @@ text... *:echon* :echon {expr1} .. Echoes each {expr1}, without anything added. Also see |:comment|. - Uses the highlighting set by the |:echohl| command. + Uses the highlighting set by the `:echohl` command. Cannot be followed by a comment. Example: > :echon "the value of 'shell' is " &shell < - Note the difference between using ":echo", which is a - Vim command, and ":!echo", which is an external shell + Note the difference between using `:echo`, which is a + Vim command, and `:!echo`, which is an external shell command: > :!echo % --> filename < The arguments of ":!" are expanded, see |:_%|. > @@ -11666,8 +3332,8 @@ text... *:echoh* *:echohl* :echoh[l] {name} Use the highlight group {name} for the following - |:echo|, |:echon| and |:echomsg| commands. Also used - for the |input()| prompt. Example: > + `:echo`, `:echon` and `:echomsg` commands. Also used + for the `input()` prompt. Example: > :echohl WarningMsg | echo "Don't panic!" | echohl None < Don't forget to set the group back to "None", otherwise all following echo's will be highlighted. @@ -11676,14 +3342,14 @@ text... :echom[sg] {expr1} .. Echo the expression(s) as a true message, saving the message in the |message-history|. Spaces are placed between the arguments as with the - |:echo| command. But unprintable characters are + `:echo` command. But unprintable characters are displayed, not interpreted. - The parsing works slightly different from |:echo|, - more like |:execute|. All the expressions are first + The parsing works slightly different from `:echo`, + more like `:execute`. All the expressions are first evaluated and concatenated before echoing anything. If expressions does not evaluate to a Number or String, string() is used to turn it into a string. - Uses the highlighting set by the |:echohl| command. + Uses the highlighting set by the `:echohl` command. Example: > :echomsg "It's a Zizzer Zazzer Zuzz, as you can plainly see." < See |:echo-redraw| to avoid the message disappearing @@ -11693,12 +3359,12 @@ text... message in the |message-history|. When used in a script or function the line number will be added. Spaces are placed between the arguments as with the - |:echomsg| command. When used inside a try conditional, + `:echomsg` command. When used inside a try conditional, the message is raised as an error exception instead (see |try-echoerr|). Example: > :echoerr "This script just failed!" -< If you just want a highlighted message use |:echohl|. +< If you just want a highlighted message use `:echohl`. And to get a beep: > :exe "normal \<Esc>" < @@ -12003,7 +3669,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 @@ -12406,8 +4072,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 @@ -12603,7 +4269,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 : @@ -12630,7 +4296,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 : @@ -12649,9 +4315,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" @@ -12719,7 +4385,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 @@ -12781,7 +4447,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 @@ -12792,7 +4458,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 @@ -12866,7 +4532,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 @@ -12875,6 +4541,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 42a9993c8c..5f7c1b57f8 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,24 +131,35 @@ 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| - *.asp g:filetype_asp |ft-aspvbs-syntax| |ft-aspperl-syntax| *.asm g:asmsyntax |ft-asm-syntax| - *.prg g:filetype_prg - *.pl g:filetype_pl - *.inc g:filetype_inc - *.w g:filetype_w |ft-cweb-syntax| + *.asp g:filetype_asp |ft-aspvbs-syntax| |ft-aspperl-syntax| + *.bas g:filetype_bas |ft-basic-syntax| + *.cfg g:filetype_cfg + *.dat g:filetype_dat + *.frm g:filetype_frm |ft-form-syntax| + *.fs g:filetype_fs |ft-forth-syntax| *.i g:filetype_i |ft-progress-syntax| + *.inc g:filetype_inc *.m g:filetype_m |ft-mathematica-syntax| + *.mod g:filetype_mod *.p g:filetype_p |ft-pascal-syntax| + *.pl g:filetype_pl *.pp g:filetype_pp |ft-pascal-syntax| + *.prg g:filetype_prg + *.src g:filetype_src *.sh g:bash_is_sh |ft-sh-syntax| *.tex g:tex_flavor |ft-tex-plugin| + *.w g:filetype_w |ft-cweb-syntax| + +For a few filetypes the global variable is used only when the filetype could +not be detected: + *.r g:filetype_r |ft-rexx-syntax| *filetype-ignore* To avoid that certain files are being inspected, the g:ft_ignore_pat variable @@ -148,9 +168,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 @@ -190,7 +211,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 @@ -205,9 +226,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. @@ -244,9 +294,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 @@ -313,12 +363,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 < @@ -494,7 +544,6 @@ Options: For further discussion of fortran_have_tabs and the method used for the detection of source format see |ft-fortran-syntax|. - GIT COMMIT *ft-gitcommit-plugin* One command, :DiffGitCached, is provided to show a diff of the current commit 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_ada.txt b/runtime/doc/ft_ada.txt index 771ccc3302..f6dfa708fb 100644 --- a/runtime/doc/ft_ada.txt +++ b/runtime/doc/ft_ada.txt @@ -89,9 +89,9 @@ file is opened and adds Ada related entries to the main and pop-up menu. *ft-ada-omni* The Ada omni-completions (|i_CTRL-X_CTRL-O|) uses tags database created either -by "gnat xref -v" or the "exuberant Ctags (http://ctags.sourceforge.net). The -complete function will automatically detect which tool was used to create the -tags file. +by "gnat xref -v" or the "Universal Ctags" (https://ctags.io). The complete +function will automatically detect which tool was used to create the tags +file. ------------------------------------------------------------------------------ 3.1 Omni Completion with "gnat xref" ~ @@ -125,18 +125,18 @@ NOTE: "gnat xref -v" is very tricky to use as it has almost no diagnostic 3.2 Omni Completion with "ctags"~ *ada-ctags* -Exuberant Ctags uses its own multi-language code parser. The parser is quite -fast, produces a lot of extra information (hence the name "Exuberant Ctags") -and can run on files which currently do not compile. +Universal/Exuberant Ctags use their own multi-language code parser. The +parser is quite fast, produces a lot of extra information and can run on files +which currently do not compile. -There are also lots of other Vim-tools which use exuberant Ctags. +There are also lots of other Vim-tools which use Universal/Exuberant Ctags. +Universal Ctags is preferred, Exuberant Ctags is no longer being developed. -You will need to install a version of the Exuberant Ctags which has Ada -support patched in. Such a version is available from the GNU Ada Project -(http://gnuada.sourceforge.net). +You will need to install Universal Ctags which is available from +https://ctags.io -The Ada parser for Exuberant Ctags is fairly new - don't expect complete -support yet. +The Ada parser for Universal/Exuberant Ctags is fairly new - don't expect +complete support yet. ============================================================================== 4. Compiler Support ~ 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..6972fe0768 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: > @@ -555,7 +555,7 @@ the SQL completion plugin. > < 1. After typing SELECT press <C-C>t to display a list of tables. 2. Highlight the table you need the column list for. 3. Press <Enter> to choose the table from the list. - 4. Press <C-C>l to request a comma separated list of all columns + 4. Press <C-C>l to request a comma-separated list of all columns for this table. 5. Based on the table name chosen in step 3, the plugin attempts to decide on a reasonable table alias. You are then prompted to @@ -609,7 +609,7 @@ your |init.vim|: > > omni_sql_use_tbl_alias < - Default: a - - This setting is only used when generating a comma separated + - This setting is only used when generating a comma-separated column list. By default the map is <C-C>l. When generating a column list, an alias can be prepended to the beginning of each column, for example: e.emp_id, e.emp_name. This option has three @@ -693,9 +693,9 @@ plugin. > <C-C>c < - Displays a list of columns for a specific table. > <C-C>l -< - Displays a comma separated list of columns for a specific table. > +< - Displays a comma-separated list of columns for a specific table. > <C-C>L -< - Displays a comma separated list of columns for a specific table. +< - Displays a comma-separated list of columns for a specific table. This should only be used when the completion window is active. > <Right> < - Displays a list of columns for the table currently highlighted in diff --git a/runtime/doc/help.txt b/runtime/doc/help.txt index 6416f49061..b97c9a2e3f 100644 --- a/runtime/doc/help.txt +++ b/runtime/doc/help.txt @@ -93,6 +93,7 @@ REFERENCE MANUAL: These files explain every detail of Vim. *reference_toc* General subjects ~ |intro.txt| general introduction to Vim; notation used in help files +|nvim.txt| Transitioning from Vim |help.txt| overview and quick reference (this file) |helphelp.txt| about using the help files |index.txt| alphabetical index of all commands @@ -128,22 +129,25 @@ 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 Special issues ~ -|testing.txt| testing Vim and Vim scripts -|print.txt| printing -|remote.txt| using Vim as a server or client +|testing.txt| testing Vim and Vim scripts +|print.txt| printing +|remote_plugin.txt| Nvim support for remote plugins Programming language support ~ |indent.txt| automatic indenting for C and other languages |lsp.txt| Language Server Protocol (LSP) |treesitter.txt| tree-sitter library for incremental parsing of buffers +|diagnostic.txt| Diagnostic framework |syntax.txt| syntax highlighting |filetype.txt| settings done specifically for a type of file |quickfix.txt| commands for a quick edit-compile-fix cycle +|provider.txt| Built-in remote plugin hosts |ft_ada.txt| Ada (the programming language) support |ft_ps1.txt| Filetype plugin for Windows PowerShell |ft_raku.txt| Filetype plugin for Raku @@ -164,6 +168,7 @@ GUI ~ Interfaces ~ |if_cscop.txt| using Cscope with Vim +|if_perl.txt| Perl interface |if_pyth.txt| Python interface |if_ruby.txt| Ruby interface |sign.txt| debugging signs @@ -171,6 +176,16 @@ Interfaces ~ Versions ~ |vim_diff.txt| Main differences between Nvim and Vim |vi_diff.txt| Main differences between Vim and Vi +|deprecated.txt| Deprecated items that have been or will be removed + +Other ~ +|terminal_emulator.txt| Terminal buffers +|term.txt| Terminal UI +|ui.txt| Nvim UI protocol +|channel.txt| Nvim asynchronous IO +|dev_style.txt| Nvim style guide +|job_control.txt| Spawn and control multiple processes + *standard-plugin-list* Standard plugins ~ |matchit.txt| Extended |%| matching diff --git a/runtime/doc/helphelp.txt b/runtime/doc/helphelp.txt index c884eea54f..569995d319 100644 --- a/runtime/doc/helphelp.txt +++ b/runtime/doc/helphelp.txt @@ -164,7 +164,7 @@ If you would like to open the help in the current window, see this tip: The initial height of the help window can be set with the 'helpheight' option (default 20). - + *help-buffer-options* When the help buffer is created, several local options are set to make sure the help text is displayed as it was intended: 'iskeyword' nearly all ASCII chars except ' ', '*', '"' and '|' @@ -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 @@ -319,7 +319,7 @@ Hints for translators: 3. Writing help files *help-writing* For ease of use, a Vim help file for a plugin should follow the format of the -standard Vim help files, except fot the fist line. If you are writing a new +standard Vim help files, except for the first line. If you are writing a new help file it's best to copy one of the existing files and use it as a template. @@ -332,7 +332,7 @@ remainder of the line, after a Tab, describes the plugin purpose in a short way. This will show up in the "LOCAL ADDITIONS" section of the main help file. Check there that it shows up properly: |local-additions|. -If you want to add a version number of last modification date, put it in the +If you want to add a version number or last modification date, put it in the second line, right aligned. At the bottom of the help file, place a Vim modeline to set the 'textwidth' @@ -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_lua.txt b/runtime/doc/if_lua.txt deleted file mode 100644 index 34bcf0f039..0000000000 --- a/runtime/doc/if_lua.txt +++ /dev/null @@ -1,8 +0,0 @@ - - - NVIM REFERENCE MANUAL - -Moved to |lua.txt| - -============================================================================== - vim:tw=78:ts=8:noet:ft=help:norl: 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..3992b2d3d7 100644 --- a/runtime/doc/indent.txt +++ b/runtime/doc/indent.txt @@ -38,11 +38,12 @@ is not a C compiler: it does not recognize all syntax. One requirement is that toplevel functions have a '{' in the first column. Otherwise they are easily confused with declarations. -These four options control C program indenting: +These five options control C program indenting: 'cindent' Enables Vim to perform C program indenting automatically. 'cinkeys' Specifies which keys trigger reindenting in insert mode. 'cinoptions' Sets your preferred indent style. 'cinwords' Defines keywords that start an extra indent in the next line. +'cinscopedecls' Defines strings that are recognized as a C++ scope declaration. If 'lisp' is not on and 'equalprg' is empty, the "=" operator indents using Vim's built-in algorithm rather than calling an external program. @@ -289,8 +290,9 @@ The examples below assume a 'shiftwidth' of 4. < *cino-g* gN Place C++ scope declarations N characters from the indent of the - block they are in. (default 'shiftwidth'). A scope declaration - can be "public:", "protected:" or "private:". + block they are in. (default 'shiftwidth'). By default, a scope + declaration is "public:", "protected:" or "private:". This can + be adjusted with the 'cinscopedecls' option. cino= cino=g0 > { { @@ -771,6 +773,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 +883,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 +1210,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 baa7bc1992..572b4e3f93 100644 --- a/runtime/doc/index.txt +++ b/runtime/doc/index.txt @@ -339,7 +339,6 @@ tag char note action in Normal mode ~ insert text, repeat N times |P| ["x]P 2 put the text [from register x] before the cursor N times -|Q| Q switch to "Ex" mode |R| R 2 enter replace mode: overtype existing characters, repeat the entered text N-1 times @@ -354,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) @@ -401,6 +401,7 @@ tag char note action in Normal mode ~ |q| q{0-9a-zA-Z"} record typed characters into named register {0-9a-zA-Z"} (uppercase to append) |q| q (while recording) stops recording +|Q| Q replay last recorded macro |q:| q: edit : command-line in command-line window |q/| q/ edit / command-line in command-line window |q?| q? edit ? command-line in command-line window @@ -430,6 +431,7 @@ tag char note action in Normal mode ~ |<C-LeftMouse>| <C-LeftMouse> ":ta" to the keyword at the mouse click |<C-Right>| <C-Right> 1 same as "w" |<C-RightMouse>| <C-RightMouse> same as "CTRL-T" +|<C-Tab>| <C-Tab> same as "g<Tab>" |<Del>| ["x]<Del> 2 same as "x" |N<Del>| {count}<Del> remove the last digit from {count} |<Down>| <Down> 1 same as "j" @@ -576,7 +578,7 @@ tag command action in Normal mode ~ following the file name. |CTRL-W_gt| CTRL-W g t same as `gt`: go to next tab page |CTRL-W_gT| CTRL-W g T same as `gT`: go to previous tab page -|CTRL-W_g<Tab>| CTRL-W g <Tab> same as `g<Tab>` : go to last accessed tab +|CTRL-W_g<Tab>| CTRL-W g <Tab> same as |g<Tab>|: go to last accessed tab page |CTRL-W_h| CTRL-W h go to Nth left window (stop at first window) |CTRL-W_i| CTRL-W i split window and jump to declaration of @@ -923,7 +925,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 +990,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..9db44eaaa0 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. @@ -256,7 +258,7 @@ CTRL-] Trigger abbreviation, without inserting a character. *i_backspacing* The effect of the <BS>, CTRL-W, and CTRL-U depend on the 'backspace' option -(unless 'revins' is set). This is a comma separated list of items: +(unless 'revins' is set). This is a comma-separated list of items: item action ~ indent allow backspacing over autoindent @@ -267,8 +269,8 @@ start allow backspacing over the start position of insert; CTRL-W and When 'backspace' is empty, Vi compatible backspacing is used. You cannot backspace over autoindent, before column 1 or before where insert started. -For backwards compatibility the values "0", "1" and "2" are also allowed, see -|'backspace'|. +For backwards compatibility the values "0", "1", "2" and "3" are also allowed, +see |'backspace'|. If the 'backspace' option does contain "eol" and the cursor is in column 1 when one of the three keys is used, the current line is joined with the @@ -778,7 +780,7 @@ If the previous expansion was split, because it got longer than 'textwidth', then just the text in the current line will be used. If the match found is at the end of a line, then the first word in the next -line will be inserted and the message "word from next line" displayed, if +line will be inserted and the message "Word from other line" displayed, if this word is accepted the next CTRL-X CTRL-P or CTRL-X CTRL-N will search for those lines starting with this word. @@ -796,6 +798,7 @@ CTRL-X CTRL-K Search the files given with the 'dictionary' option the 'dictionary' option is empty. For suggestions where to find a list of words, see the 'dictionary' option. + 'ignorecase', 'smartcase' and 'infercase' apply. CTRL-K or CTRL-N Search forward for next matching keyword. This @@ -828,7 +831,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 +843,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 +865,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 +1200,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 +1222,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 @@ -1339,11 +1342,16 @@ in 'runtimepath'. Thus for "java" it is autoload/javacomplete.vim. C *ft-c-omni* -Completion of C code requires a tags file. You should use Exuberant ctags, -because it adds extra information that is needed for completion. You can find -it here: http://ctags.sourceforge.net/ Version 5.6 or later is recommended. +Completion of C code requires a tags file. You should use Universal/ +Exuberant ctags, because it adds extra information that is needed for +completion. You can find it here: + Universal Ctags: https://ctags.io + Exuberant Ctags: http://ctags.sourceforge.net + +Universal Ctags is preferred, Exuberant Ctags is no longer being developed. -For version 5.5.4 you should add a patch that adds the "typename:" field: +For Exuberant ctags, version 5.6 or later is recommended. For version 5.5.4 +you should add a patch that adds the "typename:" field: ftp://ftp.vim.org/pub/vim/unstable/patches/ctags-5.5.4.patch A compiled .exe for MS-Windows can be found at: http://ctags.sourceforge.net/ @@ -1464,8 +1472,11 @@ will be suggested. All other elements are not placed in suggestion list. PHP *ft-php-omni* Completion of PHP code requires a tags file for completion of data from -external files and for class aware completion. You should use Exuberant ctags -version 5.5.4 or newer. You can find it here: http://ctags.sourceforge.net/ +external files and for class aware completion. You should use Universal/ +Exuberant ctags version 5.5.4 or newer. You can find it here: + + Universal Ctags: https://ctags.io + Exuberant Ctags: http://ctags.sourceforge.net Script completes: @@ -1861,14 +1872,10 @@ gi Insert text in the same position as where Insert mode *o* o Begin a new line below the cursor and insert text, repeat [count] times. - When the '#' flag is in 'cpoptions' the count is - ignored. *O* O Begin a new line above the cursor and insert text, repeat [count] times. - When the '#' flag is in 'cpoptions' the count is - ignored. These commands are used to start inserting text. You can end insert mode with <Esc>. See |mode-ins-repl| for the other special characters in Insert mode. @@ -1878,6 +1885,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 a89263861b..09739085a3 100644 --- a/runtime/doc/intro.txt +++ b/runtime/doc/intro.txt @@ -120,7 +120,7 @@ Vim would never have become what it is now, without the help of these people! Daniel Elstner GTK+ 2 port Eric Fischer Mac port, 'cindent', and other improvements Benji Fisher Answering lots of user questions - Bill Foster Athena GUI port + Bill Foster Athena GUI port (later removed) Google Lets me work on Vim one day a week Loic Grenie xvim (ideas for multi windows version) Sven Guckes Vim promoter and previous WWW page maintainer @@ -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>* @@ -563,8 +562,8 @@ The command CTRL-\ CTRL-G or <C-\><C-G> can be used to go to Insert mode when make sure Vim is in the mode indicated by 'insertmode', without knowing in what mode Vim currently is. - *gQ* *Q* *mode-Ex* *Ex-mode* *Ex* *EX* *E501* -Q or gQ Switch to Ex mode. This is like typing ":" commands + *gQ* *mode-Ex* *Ex-mode* *Ex* *EX* *E501* +gQ Switch to Ex mode. This is like typing ":" commands one after another, except: - You don't have to keep pressing ":". - The screen doesn't get updated after each command. 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 2fab111498..b704d2d6e8 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. @@ -355,8 +355,8 @@ To configure the behavior of a builtin |lsp-handler|, the convenient method *lsp-handler-resolution* Handlers can be set by: -- Setting a field in |vim.lsp.handlers|. *vim.lsp.handlers* - |vim.lsp.handlers| is a global table that contains the default mapping of +- Setting a field in vim.lsp.handlers. *vim.lsp.handlers* + vim.lsp.handlers is a global table that contains the default mapping of |lsp-method| names to |lsp-handlers|. To override the handler for the `"textDocument/definition"` method: > @@ -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* @@ -485,6 +488,16 @@ buf_attach_client({bufnr}, {client_id}) *vim.lsp.buf_attach_client()* {bufnr} (number) Buffer handle, or 0 for current {client_id} (number) Client id +buf_detach_client({bufnr}, {client_id}) *vim.lsp.buf_detach_client()* + Detaches client from the specified buffer. Note: While the + server is notified that the text document (buffer) was closed, + it is still able to send notifications should it ignore this + notification. + + Parameters: ~ + {bufnr} number Buffer handle, or 0 for current + {client_id} number Client id + buf_get_clients({bufnr}) *vim.lsp.buf_get_clients()* Gets a map of client_id:client pairs for the given buffer, where each value is a |vim.lsp.client| object. @@ -521,7 +534,9 @@ buf_request({bufnr}, {method}, {params}, {handler}) {method} (string) LSP method name {params} (optional, table) Parameters to send to the server - {handler} (optional, function) See |lsp-handler| + {handler} (optional, function) See |lsp-handler| If nil, + follows resolution strategy defined in + |lsp-handler-configuration| Return: ~ 2-tuple: @@ -548,7 +563,7 @@ buf_request_all({bufnr}, {method}, {params}, {callback}) Return: ~ (function) A function that will cancel all requests which - is the same as the one returned from `buf_request` . + is the same as the one returned from `buf_request`. *vim.lsp.buf_request_sync()* buf_request_sync({bufnr}, {method}, {params}, {timeout_ms}) @@ -573,9 +588,6 @@ buf_request_sync({bufnr}, {method}, {params}, {timeout_ms}) error, returns `(nil, err)` where `err` is a string describing the failure reason. -check_clients_closed() *vim.lsp.check_clients_closed()* - TODO: Documentation - client() *vim.lsp.client* LSP client object. You can get an active client object via |vim.lsp.get_client_by_id()| or @@ -588,9 +600,9 @@ client() *vim.lsp.client* {handler} is not specified, If one is not found there, then an error will occur. Returns: {status}, {[client_id]}. {status} is a boolean indicating if the - notification was successful. If it is `false` , then it + notification was successful. If it is `false`, then it will always be `false` (the client has shutdown). If - {status} is `true` , the function returns {request_id} as + {status} is `true`, the function returns {request_id} as the second result. You can use this with `client.cancel_request(request_id)` to cancel the request. • request_sync(method, params, timeout_ms, bufnr) Sends a @@ -600,13 +612,13 @@ client() *vim.lsp.client* `err` and `result` come from the |lsp-handler|. On timeout, cancel or error, returns `(nil, err)` where `err` is a string describing the failure reason. If the request - was unsuccessful returns `nil` . + was unsuccessful returns `nil`. • notify(method, params) Sends a notification to an LSP server. Returns: a boolean to indicate if the notification was successful. If it is false, then it will always be false (the client has shutdown). • cancel_request(id) Cancels a request with a given request - id. Returns: same as `notify()` . + id. Returns: same as `notify()`. • stop([force]) Stops a client, optionally with force. By default, it will just ask the server to shutdown without force. If you request to stop a client which has @@ -627,17 +639,23 @@ client() *vim.lsp.client* interaction with the client. See |vim.lsp.rpc.start()|. • {offset_encoding} (string): The encoding used for communicating with the server. You can modify this in the - `config` 's `on_init` method before text is sent to the + `config`'s `on_init` method before text is sent to the server. • {handlers} (table): The handlers used by the client as described in |lsp-handler|. + • {requests} (table): The current pending requests in flight + to the server. Entries are key-value pairs with the key + being the request ID while the value is a table with + `type`, `bufnr`, and `method` key-value pairs. `type` is + either "pending" for an active request, or "cancel" for a + cancel request. • {config} (table): copy of the table that was passed by the user to |vim.lsp.start_client()|. • {server_capabilities} (table): Response from the server sent on `initialize` describing the server's capabilities. • {resolved_capabilities} (table): Normalized table of capabilities that we have detected based on the initialize - response from the server in `server_capabilities` . + response from the server in `server_capabilities`. client_is_stopped({client_id}) *vim.lsp.client_is_stopped()* Checks whether a client is stopped. @@ -648,18 +666,31 @@ client_is_stopped({client_id}) *vim.lsp.client_is_stopped()* Return: ~ true if client is stopped, false otherwise. -flush({client}) *vim.lsp.flush()* - TODO: Documentation - *vim.lsp.for_each_buffer_client()* for_each_buffer_client({bufnr}, {fn}) - TODO: Documentation + Invokes a function for each LSP client attached to a buffer. + + Parameters: ~ + {bufnr} number Buffer number + {fn} function Function to run on each client attached + to buffer {bufnr}. The function takes the client, + client ID, and buffer number as arguments. + Example: > + + vim.lsp.for_each_buffer_client(0, function(client, client_id, bufnr) + print(vim.inspect(client)) + end) +< formatexpr({opts}) *vim.lsp.formatexpr()* Provides an interface between the built-in client and a `formatexpr` function. - Currently only supports a single client. This can be set via `setlocal formatexpr=v:lua.vim.lsp.formatexpr()` but will typically or in `on_attach` via vim.api.nvim_buf_set_option(bufnr, 'formatexpr , 'v:lua.vim.lsp.formatexpr(#{timeout_ms:250})')`. + Currently only supports a single client. This can be set via + `setlocal formatexpr=v:lua.vim.lsp.formatexpr()` but will + typically or in `on_attach` via + `vim.api.nvim_buf_set_option(bufnr, 'formatexpr', + 'v:lua.vim.lsp.formatexpr(#{timeout_ms:250})')`. Parameters: ~ {opts} table options for customizing the formatting @@ -676,6 +707,8 @@ get_active_clients() *vim.lsp.get_active_clients()* *vim.lsp.get_buffers_by_client_id()* get_buffers_by_client_id({client_id}) + Returns list of buffers attached to client_id. + Parameters: ~ {client_id} number client id @@ -717,21 +750,11 @@ omnifunc({findstart}, {base}) *vim.lsp.omnifunc()* |complete-items| |CompleteDone| - *vim.lsp.prepare()* -prepare({bufnr}, {firstline}, {lastline}, {new_lastline}, {changedtick}) - TODO: Documentation - -reset({client_id}) *vim.lsp.reset()* - TODO: Documentation - -reset_buf({client}, {bufnr}) *vim.lsp.reset_buf()* - TODO: Documentation - 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. @@ -745,21 +768,18 @@ set_log_level({level}) *vim.lsp.set_log_level()* start_client({config}) *vim.lsp.start_client()* Starts and initializes a client with the given configuration. - Parameters `cmd` and `root_dir` are required. + Parameter `cmd` is required. The following parameters describe fields in the {config} table. Parameters: ~ - {root_dir} (string) Directory where the LSP - server will base its rootUri on - initialization. {cmd} (required, string or list treated like |jobstart()|) Base command that initiates the LSP client. {cmd_cwd} (string, default=|getcwd()|) Directory to launch the `cmd` - process. Not related to `root_dir` . + process. Not related to `root_dir`. {cmd_env} (table) Environment flags to pass to the LSP on spawn. Can be specified using keys like a map or as a list @@ -768,6 +788,13 @@ start_client({config}) *vim.lsp.start_client()* { "PRODUCTION=true"; "TEST=123"; PORT = 8080; HOST = "0.0.0.0"; } < + {workspace_folders} (table) List of workspace folders + passed to the language server. For + backwards compatibility rootUri and + rootPath will be derived from the + first workspace folder in this list. + See `workspaceFolders` in the LSP + spec. {capabilities} Map overriding the default capabilities defined by |vim.lsp.protocol.make_client_capabilities()|, @@ -777,34 +804,31 @@ start_client({config}) *vim.lsp.start_client()* its result. • Note: To send an empty dictionary use - `{[vim.type_idx]=vim.types.dictionary}` - , else it will be encoded as an + `{[vim.type_idx]=vim.types.dictionary}`, + else it will be encoded as an array. {handlers} Map of language server method names to |lsp-handler| {settings} Map with language server specific settings. These are returned to the language server if requested via - `workspace/configuration` . Keys are + `workspace/configuration`. Keys are case-sensitive. {commands} table Table that maps string of clientside commands to user-defined functions. Commands passed to start_client take precedence over the global command registry. Each key - must be a unique comand name, and the - value is a function which is called - if any LSP action (code action, code - lenses, ...) triggers the command. + must be a unique command name, and + the value is a function which is + called if any LSP action (code + action, code lenses, ...) triggers + the command. {init_options} Values to pass in the initialization - request as `initializationOptions` . + request as `initializationOptions`. See `initialize` in the LSP spec. {name} (string, default=client-id) Name in log messages. - {workspace_folders} (table) List of workspace folders - passed to the language server. - Defaults to root_dir if not set. See - `workspaceFolders` in the LSP spec {get_language_id} function(bufnr, filetype) -> language ID as string. Defaults to the filetype. @@ -818,10 +842,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, @@ -873,6 +897,15 @@ start_client({config}) *vim.lsp.start_client()* debounce occurs if nil • exit_timeout (number, default 500): Milliseconds to wait for server to + exit cleanly after sending the + 'shutdown' request before sending + kill -15. If set to false, nvim + exits immediately after sending the + 'shutdown' request to the server. + {root_dir} string Directory where the LSP server + will base its workspaceFolders, + rootUri, and rootPath on + initialization. Return: ~ Client id. |vim.lsp.get_client_by_id()| Note: client may @@ -947,7 +980,7 @@ code_action({context}) *vim.lsp.buf.code_action()* • only: (string|nil) LSP `CodeActionKind` used to filter the code actions. Most language servers support values like `refactor` or - `quickfix` . + `quickfix`. See also: ~ https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_codeAction @@ -978,12 +1011,12 @@ definition() *vim.lsp.buf.definition()* 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: + triggered by a key mapping or by events such as `CursorHold`, + e.g.: > - vim.api.nvim_command [[autocmd CursorHold <buffer> lua vim.lsp.buf.document_highlight()]] - vim.api.nvim_command [[autocmd CursorHoldI <buffer> lua vim.lsp.buf.document_highlight()]] - vim.api.nvim_command [[autocmd CursorMoved <buffer> lua vim.lsp.buf.clear_references()]] + autocmd CursorHold <buffer> lua vim.lsp.buf.document_highlight() + autocmd CursorHoldI <buffer> lua vim.lsp.buf.document_highlight() + autocmd CursorMoved <buffer> lua vim.lsp.buf.clear_references() < Note: Usage of |vim.lsp.buf.document_highlight()| requires the @@ -995,11 +1028,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 @@ -1051,7 +1085,7 @@ formatting_sync({options}, {timeout_ms}) |vim.lsp.buf_request_sync()|. Example: > - vim.api.nvim_command[[autocmd BufWritePre <buffer> lua vim.lsp.buf.formatting_sync()]] + autocmd BufWritePre <buffer> lua vim.lsp.buf.formatting_sync() < Parameters: ~ @@ -1083,9 +1117,6 @@ outgoing_calls() *vim.lsp.buf.outgoing_calls()* cursor in the |quickfix| window. If the symbol can resolve to multiple items, the user can pick one in the |inputlist|. -prepare_rename({err}, {result}) *vim.lsp.buf.prepare_rename()* - TODO: Documentation - *vim.lsp.buf.range_code_action()* range_code_action({context}, {start_pos}, {end_pos}) Performs |vim.lsp.buf.code_action()| for a given range. @@ -1097,7 +1128,7 @@ range_code_action({context}, {start_pos}, {end_pos}) • only: (string|nil) LSP `CodeActionKind` used to filter the code actions. Most language servers support values like - `refactor` or `quickfix` . + `refactor` or `quickfix`. {start_pos} ({number, number}, optional) mark-indexed position. Defaults to the start of the last visual selection. @@ -1198,8 +1229,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, @@ -1219,8 +1250,8 @@ display({lenses}, {bufnr}, {client_id}) *vim.lsp.codelens.display()* Display the lenses using virtual text Parameters: ~ - {lenses} table of lenses to display ( `CodeLens[] | - null` ) + {lenses} table of lenses to display (`CodeLens[] | + null`) {bufnr} number {client_id} number @@ -1232,7 +1263,7 @@ get({bufnr}) *vim.lsp.codelens.get()* current buffer. Return: ~ - table ( `CodeLens[]` ) + table (`CodeLens[]`) *vim.lsp.codelens.on_codelens()* on_codelens({err}, {result}, {ctx}, {_}) @@ -1254,8 +1285,8 @@ save({lenses}, {bufnr}, {client_id}) *vim.lsp.codelens.save()* Store lenses for a specific buffer and client Parameters: ~ - {lenses} table of lenses to store ( `CodeLens[] | - null` ) + {lenses} table of lenses to store (`CodeLens[] | + null`) {bufnr} number {client_id} number @@ -1278,7 +1309,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}) @@ -1305,8 +1336,8 @@ 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}) - Applies a `TextDocumentEdit` , which is a list of changes to a +apply_text_document_edit({text_document_edit}, {index}, {offset_encoding}) + Applies a `TextDocumentEdit`, which is a list of changes to a single document. Parameters: ~ @@ -1319,84 +1350,74 @@ apply_text_document_edit({text_document_edit}, {index}) https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocumentEdit *vim.lsp.util.apply_text_edits()* -apply_text_edits({text_edits}, {bufnr}) +apply_text_edits({text_edits}, {bufnr}, {offset_encoding}) Applies a list of text edits to a buffer. Parameters: ~ - {text_edits} table list of `TextEdit` objects - {bufnr} number Buffer id + {text_edits} table list of `TextEdit` objects + {bufnr} number Buffer id + {offset_encoding} string utf-8|utf-16|utf-32 See also: ~ https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textEdit *vim.lsp.util.apply_workspace_edit()* -apply_workspace_edit({workspace_edit}) - Applies a `WorkspaceEdit` . +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. Parameters: ~ - {bufnr} buffer id + {bufnr} number Buffer id *vim.lsp.util.buf_highlight_references()* buf_highlight_references({bufnr}, {references}, {offset_encoding}) Shows a list of document highlights for a certain buffer. Parameters: ~ - {bufnr} buffer id - {references} List of `DocumentHighlight` objects to - highlight - {offset_encoding} string utf-8|utf-16|utf-32|nil defaults - to utf-16 + {bufnr} number Buffer id + {references} table List of `DocumentHighlight` + objects to highlight + {offset_encoding} string One of "utf-8", "utf-16", + "utf-32". See also: ~ https://microsoft.github.io/language-server-protocol/specifications/specification-3-17/#documentHighlight -buf_lines({bufnr}) *vim.lsp.util.buf_lines()* - TODO: Documentation - *vim.lsp.util.character_offset()* -character_offset({bufnr}, {row}, {col}) +character_offset({buf}, {row}, {col}, {offset_encoding}) Returns the UTF-32 and UTF-16 offsets for a position in a certain buffer. Parameters: ~ - {buf} buffer id (0 for current) - {row} 0-indexed line - {col} 0-indexed byte offset in line + {buf} buffer id (0 for current) + {row} 0-indexed line + {col} 0-indexed byte offset in line + {offset_encoding} string utf-8|utf-16|utf-32|nil defaults + to `offset_encoding` of first client of + `buf` Return: ~ - (number, number) UTF-32 and UTF-16 index of the character + (number, number) `offset_encoding` index of the character in line {row} column {col} in buffer {buf} - *vim.lsp.util.close_preview_autocmd()* -close_preview_autocmd({events}, {winnr}) - Creates autocommands to close a preview window when events - happen. - - Parameters: ~ - {events} (table) list of events - {winnr} (number) window id of preview window - - See also: ~ - |autocmd-events| - *vim.lsp.util.convert_input_to_markdown_lines()* convert_input_to_markdown_lines({input}, {contents}) Converts any of `MarkedString` | `MarkedString[]` | `MarkupContent` into a list of lines containing valid markdown. Useful to populate the hover window for - `textDocument/hover` , for parsing the result of - `textDocument/signatureHelp` , and potentially others. + `textDocument/hover`, for parsing the result of + `textDocument/signatureHelp`, and potentially others. Parameters: ~ - {input} ( `MarkedString` | `MarkedString[]` | - `MarkupContent` ) - {contents} (table, optional, default `{}` ) List of + {input} (`MarkedString` | `MarkedString[]` | + `MarkupContent`) + {contents} (table, optional, default `{}`) List of strings to extend with converted lines Return: ~ @@ -1425,12 +1446,6 @@ convert_signature_help_to_markdown_lines({signature_help}, {ft}, {triggers}) See also: ~ https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_signatureHelp -create_file({change}) *vim.lsp.util.create_file()* - TODO: Documentation - -delete_file({change}) *vim.lsp.util.delete_file()* - TODO: Documentation - *vim.lsp.util.extract_completion_items()* extract_completion_items({result}) Can be used to extract the completion items from a `textDocument/completion` request, which may return one of `CompletionItem[]` , `CompletionList` or null. @@ -1446,51 +1461,31 @@ extract_completion_items({result}) https://microsoft.github.io/language-server-protocol/specification#textDocument_completion get_effective_tabstop({bufnr}) *vim.lsp.util.get_effective_tabstop()* - Returns visual width of tabstop. + Returns indentation size. Parameters: ~ {bufnr} (optional, number): Buffer handle, defaults to current Return: ~ - (number) tabstop visual width + (number) indentation size See also: ~ - |softtabstop| + |shiftwidth| -get_line({uri}, {row}) *vim.lsp.util.get_line()* - Gets the zero-indexed line from the given uri. - - Parameters: ~ - {uri} string uri of the resource to get the line from - {row} number zero-indexed line number - - Return: ~ - string the line at row in filename - -get_lines({uri}, {rows}) *vim.lsp.util.get_lines()* - Gets the zero-indexed lines from the given uri. - - Parameters: ~ - {uri} string uri of the resource to get the lines from - {rows} number[] zero-indexed line numbers - - Return: ~ - table<number string> a table mapping rows to lines - -get_progress_messages() *vim.lsp.util.get_progress_messages()* - TODO: Documentation - -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. @@ -1499,8 +1494,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 @@ -1532,7 +1529,7 @@ make_floating_popup_options({width}, {height}, {opts}) • border (string or table) override `border` • focusable (string or table) override `focusable` - • zindex (string or table) override `zindex` , + • zindex (string or table) override `zindex`, defaults to 50 Return: ~ @@ -1553,47 +1550,73 @@ make_formatting_params({options}) https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_formatting *vim.lsp.util.make_given_range_params()* -make_given_range_params({start_pos}, {end_pos}) +make_given_range_params({start_pos}, {end_pos}, {bufnr}, {offset_encoding}) Using the given range in the current buffer, creates an object that is similar to |vim.lsp.util.make_range_params()|. Parameters: ~ - {start_pos} ({number, number}, optional) mark-indexed - position. Defaults to the start of the last - visual selection. - {end_pos} ({number, number}, optional) mark-indexed - position. Defaults to the end of the last - visual selection. + {start_pos} ({number, number}, optional) + mark-indexed position. Defaults to the + start of the last visual selection. + {end_pos} ({number, number}, optional) + mark-indexed position. Defaults to the + end of the last visual selection. + {bufnr} (optional, number): buffer handle or 0 + for current, defaults to current + {offset_encoding} string utf-8|utf-16|utf-32|nil defaults + to `offset_encoding` of first client of + `bufnr` Return: ~ { textDocument = { uri = `current_file_uri` }, range = { - start = `start_position` , end = `end_position` } } + start = `start_position`, end = `end_position` } } -make_position_params() *vim.lsp.util.make_position_params()* + *vim.lsp.util.make_position_params()* +make_position_params({window}, {offset_encoding}) Creates a `TextDocumentPositionParams` object for the current buffer and cursor position. + Parameters: ~ + {window} (optional, number): window handle or 0 + for current, defaults to current + {offset_encoding} string utf-8|utf-16|utf-32|nil defaults + to `offset_encoding` of first client of + buffer of `window` + Return: ~ `TextDocumentPositionParams` object See also: ~ https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocumentPositionParams -make_range_params() *vim.lsp.util.make_range_params()* + *vim.lsp.util.make_range_params()* +make_range_params({window}, {offset_encoding}) Using the current position in the current buffer, creates an object that can be used as a building block for several LSP - requests, such as `textDocument/codeAction` , - `textDocument/colorPresentation` , - `textDocument/rangeFormatting` . + requests, such as `textDocument/codeAction`, + `textDocument/colorPresentation`, + `textDocument/rangeFormatting`. + + Parameters: ~ + {window} (optional, number): window handle or 0 + for current, defaults to current + {offset_encoding} string utf-8|utf-16|utf-32|nil defaults + to `offset_encoding` of first client of + buffer of `window` Return: ~ { textDocument = { uri = `current_file_uri` }, range = { - start = `current_position` , end = `current_position` } } + start = `current_position`, end = `current_position` } } -make_text_document_params() *vim.lsp.util.make_text_document_params()* + *vim.lsp.util.make_text_document_params()* +make_text_document_params({bufnr}) Creates a `TextDocumentIdentifier` object for the current buffer. + Parameters: ~ + {bufnr} (optional, number): Buffer handle, defaults to + current + Return: ~ `TextDocumentIdentifier` @@ -1615,25 +1638,32 @@ open_floating_preview({contents}, {syntax}, {opts}) Parameters: ~ {contents} table of lines to show in window {syntax} string of syntax to set for opened buffer - {opts} dictionary with optional fields - • height of floating window - • width of floating window - • wrap boolean enable wrapping of long lines - (defaults to true) - • wrap_at character to wrap at for computing - height when wrap is enabled - • max_width maximal width of floating window - • max_height maximal height of floating window - • pad_top number of lines to pad contents at - top - • pad_bottom number of lines to pad contents - at bottom - • focus_id if a popup with this id is opened, - then focus it - • close_events list of events that closes the + {opts} table with optional fields (additional keys + are passed on to |vim.api.nvim_open_win()|) + • height: (number) height of floating window + • width: (number) width of floating window + • wrap: (boolean, default true) wrap long + lines + • wrap_at: (string) character to wrap at for + computing height when wrap is enabled + • max_width: (number) maximal width of floating window - • focusable (boolean, default true): Make + • max_height: (number) maximal height of + floating window + • pad_top: (number) number of lines to pad + contents at top + • pad_bottom: (number) number of lines to pad + contents at bottom + • focus_id: (string) if a popup with this id + is opened, then focus it + • close_events: (table) list of events that + closes the floating window + • focusable: (boolean, default true) Make float focusable + • focus: (boolean, default true) If `true`, + and if {focusable} is also `true`, focus an + existing floating window with the same + {focus_id} Return: ~ bufnr,winnr buffer and window number of the newly created @@ -1730,7 +1760,7 @@ text_document_completion_list_to_complete_items({result}, {prefix}) Parameters: ~ {result} The result of a `textDocument/completion` call, e.g. from |vim.lsp.buf.completion()|, which may - be one of `CompletionItem[]` , `CompletionList` + be one of `CompletionItem[]`, `CompletionList` or `null` {prefix} (string) the prefix to filter the completion items @@ -1774,7 +1804,10 @@ get_filename() *vim.lsp.log.get_filename()* (string) log filename get_level() *vim.lsp.log.get_level()* - TODO: Documentation + Gets the current log level. + + Return: ~ + string current log level set_format_func({handle}) *vim.lsp.log.set_format_func()* Sets formatting function used to format logs @@ -1853,7 +1886,8 @@ rpc_response_error({code}, {message}, {data}) *vim.lsp.rpc.start()* start({cmd}, {cmd_args}, {dispatchers}, {extra_spawn_params}) Starts an LSP server process and create an LSP RPC client - object to interact with it. + object to interact with it. Communication with the server is + currently limited to stdio. Parameters: ~ {cmd} (string) Command to start the LSP @@ -1912,10 +1946,6 @@ compute_diff({prev_lines}, {curr_lines}, {firstline}, {lastline}, Return: ~ table TextDocumentContentChangeEvent see https://microsoft.github.io/language-server-protocol/specifications/specification-3-17/#textDocumentContentChangeEvent - *vim.lsp.sync.compute_line_length()* -compute_line_length({line}, {offset_encoding}) - TODO: Documentation - ============================================================================== Lua module: vim.lsp.protocol *lsp-protocol* @@ -1927,14 +1957,14 @@ make_client_capabilities() *vim.lsp.protocol.resolve_capabilities()* resolve_capabilities({server_capabilities}) - `*` to match one or more characters in a path segment `?` to - match on one character in a path segment `**` to match any - number of path segments, including none `{}` to group - conditions (e.g. `**/*.{ts,js}` matches all TypeScript and - JavaScript files) `[]` to declare a range of characters to - match in a path segment (e.g., `example.[0-9]` to match on - `example.0` , `example.1` , …) `[!...]` to negate a range of - characters to match in a path segment (e.g., `example.[!0-9]` - to match on `example.a` , `example.b` , but not `example.0` ) + Creates a normalized object describing LSP server + capabilities. + + Parameters: ~ + {server_capabilities} table Table of capabilities + supported by the server + + Return: ~ + table Normalized table of capabilities vim:tw=78:ts=8:ft=help:norl: diff --git a/runtime/doc/lua.txt b/runtime/doc/lua.txt index d9a820913d..46b9f0576f 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. The |:source| and |:runtime| commands can run Lua scripts as well as Vim scripts. Lua modules can be loaded with `require('name')`, which @@ -274,13 +274,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] @@ -297,7 +299,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 @@ -387,8 +389,8 @@ cases there is the following agreement: converted to a dictionary `{'a': 42}`: non-string keys are ignored. Without `vim.type_idx` key tables with keys not fitting in 1., 2. or 3. are errors. - - `{[vim.type_idx]=vim.types.list}` is converted to an empty list. As well - as `{[vim.type_idx]=vim.types.list, [42]=1}`: integral keys that do not + - `{[vim.type_idx]=vim.types.array}` is converted to an empty list. As well + as `{[vim.type_idx]=vim.types.array, [42]=1}`: integral keys that do not form a 1-step sequence from 1 to N are ignored, as well as all non-integral keys. @@ -591,6 +593,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* @@ -618,13 +640,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* @@ -733,6 +775,38 @@ vim.mpack.decode({str}) *vim.mpack.decode* Decodes (or "unpacks") the msgpack-encoded {str} to a Lua object. ------------------------------------------------------------------------------ +VIM.SPELL *lua-spell* + +vim.spell.check({str}) *vim.spell.check()* + Check {str} for spelling errors. Similar to the Vimscript function + |spellbadword()|. + + Note: The behaviour of this function is dependent on: 'spelllang', + 'spellfile', 'spellcapcheck' and 'spelloptions' which can all be local + to the buffer. Consider calling this with |nvim_buf_call()|. + + Example: > + vim.spell.check("the quik brown fox") + --> + { + {'quik', 'bad', 4} + } +< + + Parameters: ~ + {str} String to spell check. + + Return: ~ + List of tuples with three items: + - The badly spelled word. + - 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 + - The position in {str} where the word begins. + +------------------------------------------------------------------------------ VIM *lua-builtin* vim.api.{func}({...}) *vim.api* @@ -784,9 +858,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 @@ -906,6 +980,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* @@ -1025,7 +1108,7 @@ from within Lua. `vim.opt.wildignore = '*.o,*.a,__pycache__'` However, vim.opt also supports a more elegent way of setting - list-style options, but using lua tables: + list-style options by using lua tables: `vim.opt.wildignore = { '*.o', '*.a', '__pycache__' }` To replicate the behavior of |:set+=|, use: > @@ -1201,6 +1284,10 @@ vim.wo *vim.wo* ============================================================================== Lua module: vim *lua-vim* + *vim.connection_failure_errmsg()* +connection_failure_errmsg({consequence}) + TODO: Documentation + defer_fn({fn}, {timeout}) *vim.defer_fn()* Defers calling `fn` until `timeout` ms passes. @@ -1215,9 +1302,6 @@ defer_fn({fn}, {timeout}) *vim.defer_fn()* Return: ~ timer luv timer object -insert_keys({obj}) *vim.insert_keys()* - TODO: Documentation - inspect({object}, {options}) *vim.inspect()* Return a human-readable representation of the given object. @@ -1225,23 +1309,32 @@ inspect({object}, {options}) *vim.inspect()* https://github.com/kikito/inspect.lua https://github.com/mpeterv/vinspect -make_dict_accessor({scope}, {handle}) *vim.make_dict_accessor()* - TODO: Documentation - -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} Content of the notification to show to the - user - {log_level} Optional log level - {opts} Dictionary with optional 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 @@ -1264,7 +1357,7 @@ on_key({fn}, {ns_id}) *vim.on_key()* it removes the callback for the associated {ns_id} {ns_id} number? Namespace ID. If nil or 0, generates and - returns a new |nvim_create_namesapce()| id. + returns a new |nvim_create_namespace()| id. Return: ~ number Namespace id associated with {fn}. Or count of all @@ -1305,6 +1398,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 @@ -1342,7 +1447,7 @@ deep_equal({a}, {b}) *vim.deep_equal()* {b} second value Return: ~ - `true` if values are equals, else `false` . + `true` if values are equals, else `false`. deepcopy({orig}) *vim.deepcopy()* Returns a deep copy of the given object. Non-table objects are @@ -1353,13 +1458,13 @@ deepcopy({orig}) *vim.deepcopy()* and will throw an error. Parameters: ~ - {orig} Table to copy + {orig} table Table to copy Return: ~ New table of copied keys and (nested) values. endswith({s}, {suffix}) *vim.endswith()* - Tests if `s` ends with `suffix` . + Tests if `s` ends with `suffix`. Parameters: ~ {s} (string) a string @@ -1394,9 +1499,6 @@ is_callable({f}) *vim.is_callable()* Return: ~ true if `f` is callable, else false -is_valid({opt}) *vim.is_valid()* - TODO: Documentation - list_extend({dst}, {src}, {start}, {finish}) *vim.list_extend()* Extends a list-like table with the values of another list-like table. @@ -1466,7 +1568,7 @@ split({s}, {sep}, {kwargs}) *vim.split()* |vim.gsplit()| startswith({s}, {prefix}) *vim.startswith()* - Tests if `s` starts with `prefix` . + Tests if `s` starts with `prefix`. Parameters: ~ {s} (string) a string @@ -1477,13 +1579,13 @@ startswith({s}, {prefix}) *vim.startswith()* tbl_add_reverse_lookup({o}) *vim.tbl_add_reverse_lookup()* Add the reverse lookup values to an existing table. For - example: tbl_add_reverse_lookup { A = 1 } == { [1] = 'A , A = 1 }` + example: `tbl_add_reverse_lookup { A = 1 } == { [1] = 'A', A = 1 }` Parameters: ~ {o} table The table to add the reverse to. tbl_contains({t}, {value}) *vim.tbl_contains()* - Checks if a list-like (vector) table contains `value` . + Checks if a list-like (vector) table contains `value`. Parameters: ~ {t} Table to check @@ -1493,7 +1595,7 @@ tbl_contains({t}, {value}) *vim.tbl_contains()* true if `t` contains `value` tbl_count({t}) *vim.tbl_count()* - Counts the number of non-nil values in table `t` . + Counts the number of non-nil values in table `t`. > vim.tbl_count({ a=1, b=2 }) => 2 @@ -1557,6 +1659,22 @@ tbl_flatten({t}) *vim.tbl_flatten()* See also: ~ From https://github.com/premake/premake-core/blob/master/src/base/table.lua +tbl_get({o}, {...}) *vim.tbl_get()* + Index into a table (first argument) via string keys passed as + subsequent arguments. Return `nil` if the key does not exist. Examples: > + + vim.tbl_get({ key = { nested_key = true }}, 'key', 'nested_key') == true + vim.tbl_get({ key = {}}, 'key', 'nested_key') == nil +< + + Parameters: ~ + {o} Table to index + {...} Optional strings (0 or more, variadic) via which to + index the table + + Return: ~ + nested value indexed by key if it exists, else nil + tbl_isempty({t}) *vim.tbl_isempty()* Checks if a table is empty. @@ -1578,7 +1696,7 @@ tbl_islist({t}) *vim.tbl_islist()* {t} Table Return: ~ - `true` if array-like table, else `false` . + `true` if array-like table, else `false`. tbl_keys({t}) *vim.tbl_keys()* Return a list of all keys used in a table. However, the order @@ -1642,26 +1760,33 @@ validate({opt}) *vim.validate()* vim.validate{arg1={{'foo'}, 'table'}, arg2={'foo', 'string'}} => NOP (success) + + vim.validate{arg1={1, 'table'}} + => error('arg1: expected table, got number') + + vim.validate{arg1={3, function(a) return (a % 2) == 0 end, 'even number'}} + => error('arg1: expected even number, got 3') < -> - vim.validate{arg1={1, 'table'}} - => error('arg1: expected table, got number') -< -> - vim.validate{arg1={3, function(a) return (a % 2) == 0 end, 'even number'}} - => error('arg1: expected even number, got 3') + + 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 @@ -1723,10 +1848,17 @@ 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. - Defaults to `Input:` . + Defaults to `Input:`. • default (string|nil) Default reply to the input • completion (string|nil) Specifies type of @@ -1746,6 +1878,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 @@ -1753,16 +1901,169 @@ select({items}, {opts}, {on_choice}) *vim.ui.select()* Defaults to `Select one of:` • format_item (function item -> text) Function to format an individual item from - `items` . Defaults to `tostring` . + `items`. Defaults to `tostring`. • kind (string|nil) Arbitrary hint string indicating the item shape. Plugins reimplementing `vim.ui.select` may wish to use this to infer the structure or - semantics of `items` , or the context in + semantics of `items`, or the context in which select() was called. {on_choice} function ((item|nil, idx|nil) -> ()) Called once the user made a choice. `idx` is the - 1-based index of `item` within `item` . `nil` + 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. If a Lua + function and `opts.expr == true`, returning `nil` + is equivalent to an empty string. + {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 0b9ac42898..b874d6dc61 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: When <Plug> appears in the {rhs} this part is + always applied even if remapping is disallowed. :unm[ap] {lhs} |mapmode-nvo| *:unm* *:unmap* @@ -81,6 +83,8 @@ modes. Remove the mapping of {lhs} for the modes where the 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 an abbreviation applied. Note: Trailing spaces are included in the {lhs}. This unmap does NOT work: > :map @@ foo @@ -155,7 +159,7 @@ type "a", then "bar" will get inserted. "<unique>" can be used in any order. They must appear right after the command, before any other arguments. - *:map-local* *:map-<buffer>* *E224* *E225* + *:map-local* *:map-<buffer>* *:map-buffer* *E224* *E225* If the first argument to one of these commands is "<buffer>" the mapping will be effective in the current buffer only. Example: > :map <buffer> ,w /[.,;]<CR> @@ -208,7 +212,7 @@ Note: ":map <script>" and ":noremap <script>" do the same thing. The "<script>" overrules the command name. Using ":noremap <script>" is preferred, because it's clearer that remapping is (mostly) disabled. - *:map-<unique>* *E226* *E227* + *:map-<unique>* *:map-unique* *E226* *E227* If the first argument to one of these commands is "<unique>" and it is used to define a new mapping or abbreviation, the command will fail if the mapping or abbreviation already exists. Example: > @@ -242,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('.') @@ -283,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() @@ -320,6 +324,8 @@ Note: - For the same reason, |keycodes| like <C-R><C-W> are interpreted as plain, unmapped keys. - The command is not echo'ed, no need for <silent>. +- The {rhs} is not subject to abbreviations nor to other mappings, even if the + mapping is recursive. - In Visual mode you can use `line('v')` and `col('v')` to get one end of the Visual area, the cursor is at the other end. - In select-mode, |:map| and |:vmap| command mappings are executed in @@ -498,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 @@ -832,8 +840,7 @@ g@{motion} Call the function set by the 'operatorfunc' option. "line" {motion} was |linewise| "char" {motion} was |charwise| "block" {motion} was |blockwise-visual| - Although "block" would rarely appear, since it can - only result from Visual mode where "g@" is not useful. + The type can be forced, see |forced-motion|. Here is an example that counts the number of spaces with <F4>: > @@ -1214,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 ~ @@ -1238,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. @@ -1329,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. @@ -1431,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 @@ -1475,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 @@ -1556,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..dac4df5ee9 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. @@ -62,7 +61,7 @@ If you are lazy, it also works without the shift key: > When an error message is displayed, but it is removed before you could read it, you can see it again with: > - :echo errmsg + :echo v:errmsg Or view a list of recent messages with: > :messages See `:messages` above. @@ -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..2cc6842402 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 @@ -1044,6 +1044,9 @@ The "file/text" column shows the file name, or the text at the jump if it is in the current file (an indent is removed and a long line is truncated to fit in the window). +The marker ">" indicates the current position in the jumplist. It may not be +shown when filtering the |:jump| command using |:filter| + You are currently in line 1167. If you then use the CTRL-O command, the cursor is put in line 1154. This results in: diff --git a/runtime/doc/msgpack_rpc.txt b/runtime/doc/msgpack_rpc.txt deleted file mode 100644 index 5368cf0f4f..0000000000 --- a/runtime/doc/msgpack_rpc.txt +++ /dev/null @@ -1,8 +0,0 @@ - - - NVIM REFERENCE MANUAL - -This document was merged into |api.txt| and |develop.txt|. - -============================================================================== - vim:tw=78:ts=8:noet:ft=help:norl: diff --git a/runtime/doc/nvim_terminal_emulator.txt b/runtime/doc/nvim_terminal_emulator.txt index e83b17f9a0..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 @@ -391,6 +390,8 @@ GDB command *termdebug-customizing* To change the name of the gdb command, set the "termdebugger" variable before invoking `:Termdebug`: > let termdebugger = "mygdb" +If the command needs an argument use a List: > + let g:termdebugger = ['rr', 'replay', '--'] To not use neovim floating windows for previewing variable evaluation, set the `g:termdebug_useFloatingHover` variable like this: > @@ -426,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 038808b760..8d353804a4 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}. @@ -65,7 +69,7 @@ achieve special effects. These options come in three forms: :se[t] {option}+={value} *:set+=* Add the {value} to a number option, or append the {value} to a string option. When the option is a - comma separated list, a comma is added, unless the + comma-separated list, a comma is added, unless the value was empty. If the option is a list of flags, superfluous flags are removed. When adding a flag that was already @@ -75,7 +79,7 @@ achieve special effects. These options come in three forms: :se[t] {option}^={value} *:set^=* Multiply the {value} to a number option, or prepend the {value} to a string option. When the option is a - comma separated list, a comma is added, unless the + comma-separated list, a comma is added, unless the value was empty. Also see |:set-args| above. @@ -83,7 +87,7 @@ achieve special effects. These options come in three forms: Subtract the {value} from a number option, or remove the {value} from a string option, if it is there. If the {value} is not found in a string option, there - is no error or warning. When the option is a comma + is no error or warning. When the option is a comma- separated list, a comma is deleted, unless the option becomes empty. When the option is a list of flags, {value} must be @@ -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. @@ -684,9 +688,12 @@ A jump table for the options with a short description can be found at |Q_op|. 'autowrite' 'aw' boolean (default off) global 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 to another file. + `: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 + to another file. + A buffer is not written if it becomes hidden, e.g. when 'bufhidden' is + 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 @@ -744,7 +751,8 @@ A jump table for the options with a short description can be found at |Q_op|. nostop like start, except CTRL-W and CTRL-U do not stop at the start of insert. - When the value is empty, Vi compatible backspacing is used. + When the value is empty, Vi compatible backspacing is used, none of + the ways mentioned for the items above are possible. For backwards compatibility with version 5.4 and earlier: value effect ~ @@ -771,7 +779,7 @@ A jump table for the options with a short description can be found at |Q_op|. 'backupcopy' 'bkc' string (Vi default for Unix: "yes", otherwise: "auto") global or local to buffer |global-local| When writing a file and a backup is made, this option tells how it's - done. This is a comma separated list of words. + done. This is a comma-separated list of words. The main values are: "yes" make a copy of the file and overwrite the original one @@ -795,10 +803,10 @@ A jump table for the options with a short description can be found at |Q_op|. file. - When the file is a link the new file will not be a link. - The "auto" value is the middle way: When Vim sees that renaming file - is possible without side effects (the attributes can be passed on and - the file is not a link) that is used. When problems are expected, a - copy will be made. + The "auto" value is the middle way: When Vim sees that renaming the + file is possible without side effects (the attributes can be passed on + and the file is not a link) that is used. When problems are expected, + a copy will be made. The "breaksymlink" and "breakhardlink" values can be used in combination with any of "yes", "no" and "auto". When included, they @@ -817,13 +825,13 @@ A jump table for the options with a short description can be found at |Q_op|. When a copy is made, the original file is truncated and then filled with the new text. This means that protection bits, owner and - symbolic links of the original file are unmodified. The backup file + symbolic links of the original file are unmodified. The backup file, however, is a new file, owned by the user who edited the file. The group of the backup is set to the group of the original file. If this fails, the protection bits for the group are made the same as for others. - When the file is renamed this is the other way around: The backup has + When the file is renamed, this is the other way around: The backup has the same attributes of the original file, and the newly written file is owned by the current user. When the file was a (hard/symbolic) link, the new file will not! That's why the "auto" value doesn't @@ -885,12 +893,12 @@ A jump table for the options with a short description can be found at |Q_op|. accidentally overwriting existing files with a backup file. You might prefer using ".bak", but make sure that you don't have files with ".bak" that you want to keep. - Only normal file name characters can be used, "/\*?[|<>" are illegal. + Only normal file name characters can be used; "/\*?[|<>" are illegal. 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'* @@ -913,7 +921,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 @@ -931,7 +939,7 @@ A jump table for the options with a short description can be found at |Q_op|. *'belloff'* *'bo'* 'belloff' 'bo' string (default "all") global - Specifies for which events the bell will not be rung. It is a comma + Specifies for which events the bell will not be rung. It is a comma- separated list of items. For each item that is present, the bell will be silenced. This is most useful to specify specific events in insert mode to be silenced. @@ -950,7 +958,6 @@ A jump table for the options with a short description can be found at |Q_op|. error Other Error occurred (e.g. try to join last line) (mostly used in |Normal-mode| or |Cmdline-mode|). esc hitting <Esc> in |Normal-mode|. - ex In |Visual-mode|, hitting |Q| results in an error. hangul Ignored. insertmode Pressing <Esc> in 'insertmode'. lang Calling the beep module for Lua/Mzscheme/TCL. @@ -1023,7 +1030,7 @@ A jump table for the options with a short description can be found at |Q_op|. This option lets you choose which characters might cause a line break if 'linebreak' is on. Only works for ASCII characters. - *'breakindent'* *'bri'* + *'breakindent'* *'bri'* *'nobreakindent'* *'nobri'* 'breakindent' 'bri' boolean (default off) local to window Every wrapped line will continue visually indented (same amount of @@ -1070,16 +1077,16 @@ A jump table for the options with a short description can be found at |Q_op|. This option specifies what happens when a buffer is no longer displayed in a window: <empty> follow the global 'hidden' option - hide hide the buffer (don't unload it), also when 'hidden' - is not set - unload unload the buffer, also when 'hidden' is set or using - |:hide| - delete delete the buffer from the buffer list, also when - 'hidden' is set or using |:hide|, like using - |:bdelete| - wipe wipe out the buffer from the buffer list, also when - 'hidden' is set or using |:hide|, like using - |:bwipeout| + hide hide the buffer (don't unload it), even if 'hidden' is + not set + unload unload the buffer, even if 'hidden' is set; the + |:hide| command will also unload the buffer + delete delete the buffer from the buffer list, even if + 'hidden' is set; the |:hide| command will also delete + the buffer, making it behave like |:bdelete| + wipe wipe the buffer from the buffer list, even if + 'hidden' is set; the |:hide| command will also wipe + out the buffer, making it behave like |:bwipeout| CAREFUL: when "unload", "delete" or "wipe" is used changes in a buffer are lost without a warning. Also, these values may break autocommands @@ -1157,6 +1164,14 @@ A jump table for the options with a short description can be found at |Q_op|. case mapping, the current locale is not effective. This probably only matters for Turkish. + *'cdhome'* *'cdh'* +'cdhome' 'cdh' boolean (default: off) + global + When on, |:cd|, |:tcd| and |:lcd| without an argument changes the + current working directory to the |$HOME| directory like in Unix. + When off, those commands just print the current directory name. + On Unix this option has no effect. + *'cdpath'* *'cd'* *E344* *E346* 'cdpath' 'cd' string (default: equivalent to $CDPATH or ",,") global @@ -1171,7 +1186,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). @@ -1206,8 +1221,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. @@ -1218,8 +1233,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: @@ -1273,10 +1288,20 @@ A jump table for the options with a short description can be found at |Q_op|. matter, include the keyword both the uppercase and lowercase: "if,If,IF". - *'clipboard'* *'cb'* + *'cinscopedecls'* *'cinsd'* +'cinscopedecls' 'cinsd' string (default "public,protected,private") + local to buffer + {not available when compiled without the |+cindent| + feature} + Keywords that are interpreted as a C++ scope declaration by |cino-g|. + Useful e.g. for working with the Qt framework that defines additional + scope declarations "signals", "public slots" and "private slots": > + set cinscopedecls+=signals,public\ slots,private\ slots + +< *'clipboard'* *'cb'* 'clipboard' 'cb' string (default "") global - This option is a list of comma separated names. + This option is a list of comma-separated names. These names are recognized: *clipboard-unnamed* @@ -1315,7 +1340,7 @@ A jump table for the options with a short description can be found at |Q_op|. *'colorcolumn'* *'cc'* 'colorcolumn' 'cc' string (default "") local to window - 'colorcolumn' is a comma separated list of screen columns that are + 'colorcolumn' is a comma-separated list of screen columns that are highlighted with ColorColumn |hl-ColorColumn|. Useful to align text. Will make screen redrawing slower. The screen column can be an absolute number, or a number preceded with @@ -1348,7 +1373,7 @@ A jump table for the options with a short description can be found at |Q_op|. 'comments' 'com' string (default "s1:/*,mb:*,ex:*/,://,b:#,:%,:XCOMM,n:>,fb:-") local to buffer - A comma separated list of strings that can start a comment line. See + A comma-separated list of strings that can start a comment line. See |format-comments|. See |option-backslash| about using backslashes to insert a space. @@ -1365,7 +1390,7 @@ A jump table for the options with a short description can be found at |Q_op|. This option specifies how keyword completion |ins-completion| works when CTRL-P or CTRL-N are used. It is also used for whole-line completion |i_CTRL-X_CTRL-L|. It indicates the type of completion - and the places to scan. It is a comma separated list of flags: + and the places to scan. It is a comma-separated list of flags: . scan the current buffer ('wrapscan' is ignored) w scan buffers from other windows b scan other loaded buffers that are in the buffer list @@ -1422,7 +1447,7 @@ A jump table for the options with a short description can be found at |Q_op|. *'completeopt'* *'cot'* 'completeopt' 'cot' string (default: "menu,preview") global - A comma separated list of options for Insert mode completion + A comma-separated list of options for Insert mode completion |ins-completion|. The supported values are: menu Use a popup menu to show the possible completions. The @@ -1827,7 +1852,7 @@ A jump table for the options with a short description can be found at |Q_op|. *'cursorlineopt'* *'culopt'* 'cursorlineopt' 'culopt' string (default: "number,line") local to window - Comma separated list of settings for how 'cursorline' is displayed. + Comma-separated list of settings for how 'cursorline' is displayed. Valid values: "line" Highlight the text line of the cursor with CursorLine |hl-CursorLine|. @@ -2092,7 +2117,7 @@ A jump table for the options with a short description can be found at |Q_op|. *'display'* *'dy'* 'display' 'dy' string (default "lastline,msgsep", Vi default: "") global - Change the way text is displayed. This is comma separated list of + Change the way text is displayed. This is comma-separated list of flags: lastline When included, as much as possible of the last line in a window will be displayed. "@@@" is put in the @@ -2118,7 +2143,7 @@ A jump table for the options with a short description can be found at |Q_op|. hor horizontally, height of windows is not affected both width and height of windows is affected - *'emoji'* *'emo'* + *'emoji'* *'emo'* *'noemoji'* *'noemo'* 'emoji' 'emo' boolean (default: on) global When on all Unicode emoji characters are considered to be full width. @@ -2210,7 +2235,7 @@ A jump table for the options with a short description can be found at |Q_op|. A list of autocommand event names, which are to be ignored. When set to "all" or when "all" is one of the items, all autocommand events are ignored, autocommands will not be executed. - Otherwise this is a comma separated list of event names. Example: > + Otherwise this is a comma-separated list of event names. Example: > :set ei=WinEnter,WinLeave < *'expandtab'* *'et'* *'noexpandtab'* *'noet'* @@ -2422,12 +2447,19 @@ A jump table for the options with a short description can be found at |Q_op|. 'fillchars' 'fcs' string (default "") global or local to window |global-local| Characters to fill the statuslines and vertical separators. - It is a comma separated list of items: + It is a comma-separated list of items: item default Used for ~ stl:c ' ' or '^' statusline of the current window stlnc:c ' ' or '=' statusline of the non-current windows + horiz:c '─' or '-' horizontal separators |:split| + horizup:c '┴' or '-' upwards facing horizontal separator + horizdown:c '┬' or '-' downwards facing horizontal separator vert:c '│' or '|' vertical separators |:vsplit| + vertleft:c '┤' or '|' left facing vertical separator + vertright:c '├' or '|' right facing vertical separator + verthoriz:c '┼' or '+' overlapping vertical and horizontal + separator fold:c '·' or '-' filling 'foldtext' foldopen:c '-' mark the beginning of a fold foldclose:c '+' show a closed fold @@ -2440,21 +2472,33 @@ A jump table for the options with a short description can be found at |Q_op|. "stlnc" the space will be used when there is highlighting, '^' or '=' otherwise. - If 'ambiwidth' is "double" then "vert", "foldsep" and "fold" default to - single-byte alternatives. + Note that "horiz", "horizup", "horizdown", "vertleft", "vertright" and + "verthoriz" are only used when 'laststatus' is 3, since only vertical + window separators are used otherwise. + + If 'ambiwidth' is "double" then "horiz", "horizup", "horizdown", + "vert", "vertleft", "vertright", "verthoriz", "foldsep" and "fold" + default to single-byte alternatives. Example: > :set fillchars=stl:^,stlnc:=,vert:│,fold:·,diff:- < 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 ~ stl:c StatusLine |hl-StatusLine| stlnc:c StatusLineNC |hl-StatusLineNC| - vert:c VertSplit |hl-VertSplit| + horiz:c WinSeparator |hl-WinSeparator| + horizup:c WinSeparator |hl-WinSeparator| + horizdown:c WinSeparator |hl-WinSeparator| + vert:c WinSeparator |hl-WinSeparator| + vertleft:c WinSeparator |hl-WinSeparator| + vertright:c WinSeparator |hl-WinSeparator| + verthoriz:c WinSeparator |hl-WinSeparator| fold:c Folded |hl-Folded| diff:c DiffDelete |hl-DiffDelete| eob:c EndOfBuffer |hl-EndOfBuffer| @@ -2584,7 +2628,7 @@ A jump table for the options with a short description can be found at |Q_op|. search,tag,undo") global Specifies for which type of commands folds will be opened, if the - command moves the cursor into a closed fold. It is a comma separated + command moves the cursor into a closed fold. It is a comma-separated list of items. NOTE: When the command is part of a mapping this option is not used. Add the |zv| command to the mapping to get the same effect. @@ -2699,7 +2743,7 @@ A jump table for the options with a short description can be found at |Q_op|. This option cannot be set from a |modeline| or in the |sandbox|, for security reasons. - *'fsync'* *'fs'* + *'fsync'* *'fs'* *'nofsync'* *'nofs'* 'fsync' 'fs' boolean (default off) global When on, the OS function fsync() will be called after saving a file @@ -2775,7 +2819,7 @@ A jump table for the options with a short description can be found at |Q_op|. \,a:blinkwait700-blinkoff400-blinkon250-Cursor/lCursor \,sm:block-blinkwait175-blinkoff150-blinkon175 -< The option is a comma separated list of parts. Each part consists of a +< The option is a comma-separated list of parts. Each part consists of a mode-list and an argument-list: mode-list:argument-list,mode-list:argument-list,.. The mode-list is a dash separated list of these modes: @@ -3025,7 +3069,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. @@ -3042,7 +3086,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|: > @@ -3075,7 +3119,7 @@ A jump table for the options with a short description can be found at |Q_op|. *'helplang'* *'hlg'* 'helplang' 'hlg' string (default: messages language or empty) global - Comma separated list of languages. Vim will use the first language + Comma-separated list of languages. Vim will use the first language for which the desired help can be found. The English help will always be used as a last resort. You can add "en" to prefer English over another language, but that will only find tags that exist in that @@ -3095,10 +3139,14 @@ A jump table for the options with a short description can be found at |Q_op|. when it is |abandon|ed. When on a buffer becomes hidden when it is |abandon|ed. A buffer displayed in another window does not become hidden, of course. + Commands that move through the buffer list sometimes hide a buffer - although the 'hidden' option is off: when the buffer is modified, - 'autowrite' is off or writing is not possible, and the '!' flag was - used. See also |windows|. + although the 'hidden' option is off when these three are true: + - the buffer is modified + - 'autowrite' is off or writing is not possible + - the '!' flag was used + Also see |windows|. + To hide a specific buffer use the 'bufhidden' option. 'hidden' is set for one command with ":hide {command}" |:hide|. @@ -3226,10 +3274,14 @@ A jump table for the options with a short description can be found at |Q_op|. 'inccommand' 'icm' string (default "nosplit") global - "nosplit": Shows the effects of a command incrementally, as you type. - "split" : Also shows partial off-screen results in a preview window. + When nonempty, shows the effects of |:substitute|, |:smagic|, and + |:snomagic| as you type. - Works for |:substitute|, |:smagic|, |:snomagic|. |hl-Substitute| + Possible values: + nosplit Shows the effects of a command incrementally in the + buffer. + split Like "nosplit", but also shows partial off-screen + results in a preview window. If the preview is too slow (exceeds 'redrawtime') then 'inccommand' is automatically disabled until |Command-line-mode| is done. @@ -3525,7 +3577,7 @@ A jump table for the options with a short description can be found at |Q_op|. *'keymodel'* *'km'* 'keymodel' 'km' string (default "") global - List of comma separated words, which enable special things that keys + List of comma-separated words, which enable special things that keys can do. These values can be used: startsel Using a shifted special key starts selection (either Select mode or Visual mode, depending on "key" being @@ -3601,7 +3653,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 @@ -3633,6 +3685,7 @@ A jump table for the options with a short description can be found at |Q_op|. 0: never 1: only if there are at least two windows 2: always + 3: always and ONLY the last window The screen looks nicer with a status line if you have several windows, but it takes another screen line. |status-line| @@ -3699,7 +3752,7 @@ A jump table for the options with a short description can be found at |Q_op|. *'lispwords'* *'lw'* 'lispwords' 'lw' string (default is very long) global or local to buffer |global-local| - Comma separated list of words that influence the Lisp indenting. + Comma-separated list of words that influence the Lisp indenting. |'lisp'| *'list'* *'nolist'* @@ -3724,7 +3777,7 @@ A jump table for the options with a short description can be found at |Q_op|. Vi default: "eol:$") global or local to window |global-local| Strings to use in 'list' mode and for the |:list| command. It is a - comma separated list of string settings. + comma-separated list of string settings. *lcs-eol* eol:c Character to show at the end of each line. When @@ -4181,7 +4234,7 @@ A jump table for the options with a short description can be found at |Q_op|. m:no,ml:up-arrow,v:rightup-arrow") global This option tells Vim what the mouse pointer should look like in - different modes. The option is a comma separated list of parts, much + different modes. The option is a comma-separated list of parts, much like used for 'guicursor'. Each part consist of a mode/location-list and an argument-list: mode-list:shape,mode-list:shape,.. @@ -4503,7 +4556,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. @@ -4619,26 +4672,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. @@ -5067,7 +5105,7 @@ A jump table for the options with a short description can be found at |Q_op|. *'selectmode'* *'slm'* 'selectmode' 'slm' string (default "") global - This is a comma separated list of words, which specifies when to start + This is a comma-separated list of words, which specifies when to start Select mode instead of Visual mode, when a selection is started. Possible values: mouse when using the mouse @@ -5082,7 +5120,7 @@ A jump table for the options with a short description can be found at |Q_op|. Vi default: "blank,buffers,curdir,folds, help,options,tabpages,winsize") global - Changes the effect of the |:mksession| command. It is a comma + Changes the effect of the |:mksession| command. It is a comma- separated list of words. Each word enables saving and restoring something: word save and restore ~ @@ -5117,7 +5155,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: @@ -5125,7 +5164,7 @@ A jump table for the options with a short description can be found at |Q_op|. Vi default: "") global When non-empty, the shada file is read upon startup and written - when exiting Vim (see |shada-file|). The string should be a comma + when exiting Vim (see |shada-file|). The string should be a comma- separated list of parameters, each consisting of a single character identifying the particular parameter, followed by a number or string which specifies the value of that parameter. If a particular @@ -5457,7 +5496,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]" @@ -5642,7 +5681,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. @@ -5744,7 +5783,7 @@ A jump table for the options with a short description can be found at |Q_op|. commands. It must end in ".{encoding}.add". You need to include the path, otherwise the file is placed in the current directory. *E765* - It may also be a comma separated list of names. A count before the + It may also be a comma-separated list of names. A count before the |zg| and |zw| commands can be used to access each. This allows using a personal word list file and a project word list file. When a word is added while this option is empty Vim will set it for @@ -5764,7 +5803,7 @@ A jump table for the options with a short description can be found at |Q_op|. *'spelllang'* *'spl'* 'spelllang' 'spl' string (default "en") local to buffer - A comma separated list of word list names. When the 'spell' option is + A comma-separated list of word list names. When the 'spell' option is on spellchecking will be done for these languages. Example: > set spelllang=en_us,nl,medical < This means US English, Dutch and medical words are recognized. Words @@ -5804,7 +5843,7 @@ A jump table for the options with a short description can be found at |Q_op|. *'spelloptions'* *'spo'* 'spelloptions' 'spo' string (default "") local to buffer - A comma separated list of options for spell checking: + A comma-separated list of options for spell checking: camel When a word is CamelCased, assume "Cased" is a separate word: every upper-case character in a word that comes after a lower case character indicates the @@ -5901,7 +5940,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 @@ -5924,7 +5963,7 @@ A jump table for the options with a short description can be found at |Q_op|. empty to avoid further errors. Otherwise screen updating would loop. Note that the only effect of 'ruler' when this option is set (and - 'laststatus' is 2) is controlling the output of |CTRL-G|. + 'laststatus' is 2 or 3) is controlling the output of |CTRL-G|. field meaning ~ - Left justify the item. The default is right justified @@ -6124,7 +6163,7 @@ A jump table for the options with a short description can be found at |Q_op|. *'suffixesadd'* *'sua'* 'suffixesadd' 'sua' string (default "") local to buffer - Comma separated list of suffixes, which are used when searching for a + Comma-separated list of suffixes, which are used when searching for a file for the "gf", "[I", etc. commands. Example: > :set suffixesadd=.java < @@ -6156,7 +6195,7 @@ A jump table for the options with a short description can be found at |Q_op|. This option controls the behavior when switching between buffers. Mostly for |quickfix| commands some values are also used for other commands, as mentioned below. - Possible values (comma separated list): + Possible values (comma-separated list): useopen If included, jump to the first open window that contains the specified buffer (if there is one). Otherwise: Do not examine other windows. @@ -6217,7 +6256,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. @@ -6250,10 +6289,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 @@ -6307,9 +6347,10 @@ A jump table for the options with a short description can be found at |Q_op|. linear search can be avoided when case is ignored. Use a value of '2' in the "!_TAG_FILE_SORTED" line for this. A tag file can be case-fold sorted with the -f switch to "sort" in most unices, as in the command: - "sort -f -o tags tags". For "Exuberant ctags" version 5.x or higher - (at least 5.5) the --sort=foldcase switch can be used for this as - well. Note that case must be folded to uppercase for this to work. + "sort -f -o tags tags". For Universal ctags and Exuberant ctags + version 5.x or higher (at least 5.5) the --sort=foldcase switch can be + used for this as well. Note that case must be folded to uppercase for + this to work. By default, tag searches are case-sensitive. Case is ignored when 'ignorecase' is set and 'tagcase' is "followic", or when 'tagcase' is @@ -6402,7 +6443,7 @@ A jump table for the options with a short description can be found at |Q_op|. 'arabicshape' is ignored, but 'rightleft' isn't changed automatically. For further details see |arabic.txt|. - *'termguicolors'* *'tgc'* + *'termguicolors'* *'tgc'* *'notermguicolors'* *'notgc'* 'termguicolors' 'tgc' boolean (default off) global Enables 24-bit RGB color in the |TUI|. Uses "gui" |:highlight| @@ -6412,7 +6453,7 @@ A jump table for the options with a short description can be found at |Q_op|. *'termpastefilter'* *'tpf'* 'termpastefilter' 'tpf' string (default: "BS,HT,ESC,DEL") global - A comma separated list of options for specifying control characters + A comma-separated list of options for specifying control characters to be removed from the text pasted into the terminal window. The supported values are: @@ -6564,7 +6605,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. @@ -6710,8 +6751,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. @@ -6752,7 +6793,7 @@ A jump table for the options with a short description can be found at |Q_op|. *'viewoptions'* *'vop'* 'viewoptions' 'vop' string (default: "folds,cursor,curdir") global - Changes the effect of the |:mkview| command. It is a comma separated + Changes the effect of the |:mkview| command. It is a comma-separated list of words. Each word enables saving and restoring something: word save and restore ~ cursor cursor position in file and in window @@ -6767,12 +6808,16 @@ A jump table for the options with a short description can be found at |Q_op|. *'virtualedit'* *'ve'* 'virtualedit' 've' string (default "") - global - A comma separated list of these words: + 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 @@ -6921,7 +6966,7 @@ A jump table for the options with a short description can be found at |Q_op|. 'wildmode' 'wim' string (default: "full") global Completion mode that is used for the character specified with - 'wildchar'. It is a comma separated list of up to four parts. Each + 'wildchar'. It is a comma-separated list of up to four parts. Each part specifies what to do for each consecutive use of 'wildchar'. The first part specifies the behavior for the first use of 'wildchar', The second part for the second use, etc. diff --git a/runtime/doc/pattern.txt b/runtime/doc/pattern.txt index dfed39dba6..35f5b311ff 100644 --- a/runtime/doc/pattern.txt +++ b/runtime/doc/pattern.txt @@ -153,9 +153,10 @@ index, on which the cursor is. This can look like this: > Note: the count does not take offset into account. When no match is found you get the error: *E486* Pattern not found -Note that for the |:global| command this behaves like a normal message, for Vi -compatibility. For the |:s| command the "e" flag can be used to avoid the -error message |:s_flags|. +Note that for the `:global` command, you get a normal message "Pattern not +found", for Vi compatibility. +For the |:s| command the "e" flag can be used to avoid the error message +|:s_flags|. *search-offset* *{offset}* These commands search for the specified pattern. With "/" and "?" an @@ -304,7 +305,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|. @@ -913,17 +914,24 @@ $ At end of pattern or in front of "\|", "\)" or "\n" ('magic' on): becomes invalid. Vim doesn't automatically update the matches. Similar to moving the cursor for "\%#" |/\%#|. - */\%l* */\%>l* */\%<l* *E951* + */\%l* */\%>l* */\%<l* *E951* *E1204* \%23l Matches in a specific line. \%<23l Matches above a specific line (lower line number). \%>23l Matches below a specific line (higher line number). - These three can be used to match specific lines in a buffer. The "23" +\%.l Matches at the cursor line. +\%<.l Matches above the cursor line. +\%>.l Matches below the cursor line. + These six can be used to match specific lines in a buffer. The "23" can be any line number. The first line is 1. WARNING: When inserting or deleting lines Vim does not automatically update the matches. This means Syntax highlighting quickly becomes - wrong. + wrong. Also when referring to the cursor position (".") and + the cursor moves the display isn't updated for this change. An update + is done when using the |CTRL-L| command (the whole screen is updated). Example, to highlight the line where the cursor currently is: > - :exe '/\%' . line(".") . 'l.*' + :exe '/\%' .. line(".") .. 'l' +< Alternatively use: > + /\%.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. @@ -931,15 +939,22 @@ $ At end of pattern or in front of "\|", "\)" or "\n" ('magic' on): \%23c Matches in a specific column. \%<23c Matches before a specific column. \%>23c Matches after a specific column. - These three can be used to match specific columns in a buffer or - string. The "23" can be any column number. The first column is 1. - Actually, the column is the byte number (thus it's not exactly right - for multibyte characters). +\%.c Matches at the cursor column. +\%<.c Matches before the cursor column. +\%>.c Matches after the cursor column. + These six can be used to match specific columns in a buffer or string. + The "23" can be any column number. The first column is 1. Actually, + the column is the byte number (thus it's not exactly right for + multibyte characters). WARNING: When inserting or deleting text Vim does not automatically update the matches. This means Syntax highlighting quickly becomes - wrong. + wrong. Also when referring to the cursor position (".") and + the cursor moves the display isn't updated for this change. An update + is done when using the |CTRL-L| command (the whole screen is updated). Example, to highlight the column where the cursor currently is: > - :exe '/\%' . col(".") . 'c' + :exe '/\%' .. col(".") .. 'c' +< Alternatively use: > + /\%.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: > @@ -950,8 +965,11 @@ $ At end of pattern or in front of "\|", "\)" or "\n" ('magic' on): \%23v Matches in a specific virtual column. \%<23v Matches before a specific virtual column. \%>23v Matches after a specific virtual column. - These three can be used to match specific virtual columns in a buffer - or string. When not matching with a buffer in a window, the option +\%.v Matches at the current virtual column. +\%<.v Matches before the current virtual column. +\%>.v Matches after the current virtual column. + These six can be used to match specific virtual columns in a buffer or + string. When not matching with a buffer in a window, the option values of the current window are used (e.g., 'tabstop'). The "23" can be any column number. The first column is 1. Note that some virtual column positions will never match, because they @@ -959,13 +977,18 @@ $ At end of pattern or in front of "\|", "\)" or "\n" ('magic' on): one screen character. WARNING: When inserting or deleting text Vim does not automatically update highlighted matches. This means Syntax highlighting quickly - becomes wrong. + becomes wrong. Also when referring to the cursor position (".") and + the cursor moves the display isn't updated for this change. An update + is done when using the |CTRL-L| command (the whole screen is updated). Example, to highlight all the characters after virtual column 72: > /\%>72v.* < When 'hlsearch' is set and you move the cursor around and make changes this will clearly show when the match is updated or not. To match the text up to column 17: > /^.*\%17v +< To match all characters after the current virtual column (where the + cursor is): > + /\%>.v.* < Column 17 is not included, because this is a |/zero-width| match. To include the column use: > /^.*\%17v. @@ -1036,6 +1059,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 +1083,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 +1444,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-matching* + +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 951b897f55..801c56e49f 100644 --- a/runtime/doc/pi_msgpack.txt +++ b/runtime/doc/pi_msgpack.txt @@ -68,7 +68,7 @@ msgpack#strftime({format}, {msgpack-integer}) *msgpack#strftime()* *msgpack#strptime* msgpack#strptime({format}, {time}) *msgpack#strptime()* - Reverse of |msgpack#strptime()|: for any time and format + Reverse of |msgpack#strftime()|: for any time and format |msgpack#equal|( |msgpack#strptime|(format, |msgpack#strftime|(format, time)), time) be true. Requires |+python| or |+python3|, without it only supports non-|msgpack-special-dict| nonnegative times and format @@ -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..2fe3d3d8e0 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. < @@ -2814,7 +2814,7 @@ your browsing preferences. (see also: |netrw-settings|) = 2: wide listing (multiple files in columns) = 3: tree style listing - *g:netrw_list_hide* comma separated pattern list for hiding files + *g:netrw_list_hide* comma-separated pattern list for hiding files Patterns are regular expressions (see |regexp|) There's some special support for git-ignore files: you may add the output from the helper @@ -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 9c1f584415..8e91b101cd 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 @@ -497,7 +497,6 @@ EXECUTE A COMMAND IN ALL THE BUFFERS IN QUICKFIX OR LOCATION LIST: autocommand event is disabled by adding it to 'eventignore'. This considerably speeds up editing each buffer. - {not in Vi} Also see |:bufdo|, |:tabdo|, |:argdo|, |:windo|, |:ldo|, |:cfdo| and |:lfdo|. @@ -510,7 +509,6 @@ EXECUTE A COMMAND IN ALL THE BUFFERS IN QUICKFIX OR LOCATION LIST: :{cmd} etc. < Otherwise it works the same as `:cdo`. - {not in Vi} *:ldo* :ld[o][!] {cmd} Execute {cmd} in each valid entry in the location list @@ -523,7 +521,6 @@ EXECUTE A COMMAND IN ALL THE BUFFERS IN QUICKFIX OR LOCATION LIST: etc. < Only valid entries in the location list are used. Otherwise it works the same as `:cdo`. - {not in Vi} *:lfdo* :lfdo[!] {cmd} Execute {cmd} in each file in the location list for @@ -535,7 +532,6 @@ EXECUTE A COMMAND IN ALL THE BUFFERS IN QUICKFIX OR LOCATION LIST: :{cmd} etc. < Otherwise it works the same as `:ldo`. - {not in Vi} FILTERING A QUICKFIX OR LOCATION LIST: *cfilter-plugin* *:Cfilter* *:Lfilter* @@ -575,6 +571,7 @@ location list. second quickfix window. If [height] is given the existing window will be resized to it. + *quickfix-buffer* The window will contain a special buffer, with 'buftype' equal to "quickfix". Don't change this! The window will have the w:quickfix_title variable set @@ -583,7 +580,11 @@ location list. status line if the value of 'statusline' is adjusted properly. Whenever this buffer is modified by a quickfix command or function, the |b:changedtick| - variable is incremented. + variable is incremented. You can get the number of + this buffer using the getqflist() and getloclist() + functions by passing the 'qfbufnr' item. For a + location list, this buffer is wiped out when the + location list is removed. *:lop* *:lopen* :lop[en] [height] Open a window to show the location list for the @@ -641,6 +642,27 @@ 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. + +When you select a file from the quickfix window, the following steps are used +to find a window to edit the file: + +1. If a window displaying the selected file is present in the current tabpage + (starting with the window before the quickfix window), then that window is + used. +2. If the above step fails and if 'switchbuf' contains "usetab" and a window + displaying the selected file is present in any one of the tabpages + (starting with the first tabpage) then that window is used. +3. If the above step fails then a window in the current tabpage displaying a + buffer with 'buftype' not set (starting with the window before the quickfix + window) is used. +4. If the above step fails and if 'switchbuf' contains "uselast", then the + previously accessed window is used. +5. If the above step fails then the window before the quickfix window is used. + If there is no previous window, then the window after the quickfix window + is used. +6. If the above step fails, then a new horizontally split window above the + quickfix window is used. + *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 +672,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 @@ -679,13 +701,15 @@ this window, the displayed location list is used. When you select a file from the location list window, the following steps are used to find a window to edit the file: -1. If a window with the location list displayed in the location list window is - present, then the file is opened in that window. -2. If the above step fails and if the file is already opened in another - window, then that window is used. -3. If the above step fails then an existing window showing a buffer with - 'buftype' not set is used. -4. If the above step fails, then the file is edited in a new window. +1. If a non-quickfix window associated with the location list is present in + the current tabpage, then that window is used. +2. If the above step fails and if the file is already opened in another window + in the current tabpage, then that window is used. +3. If the above step fails and 'switchbuf' contains "usetab" and if the file + is opened in a window in any one of the tabpages, then that window is used. +4. If the above step fails then a window in the current tabpage showing a + buffer with 'buftype' not set is used. +5. If the above step fails, then the file is edited in a new window. In all of the above cases, if the location list for the selected window is not yet set, then it is set to the location list displayed in the location list @@ -749,12 +773,18 @@ using these functions are below: " get the quickfix list window id :echo getqflist({'winid' : 0}).winid + " get the quickfix list window buffer number + :echo getqflist({'qfbufnr' : 0}).qfbufnr + " get the context of the current location list :echo getloclist(0, {'context' : 0}).context " get the location list window id of the third window :echo getloclist(3, {'winid' : 0}).winid + " get the location list window buffer number of the third window + :echo getloclist(3, {'qfbufnr' : 0}).qfbufnr + " get the file window id of a location list window (winnr: 4) :echo getloclist(4, {'filewinid' : 0}).filewinid < @@ -837,9 +867,9 @@ lists. They set one of the existing error lists as the current one. *:chistory* *:chi* :[count]chi[story] Show the list of error lists. The current list is marked with ">". The output looks like: - error list 1 of 3; 43 errors ~ - > error list 2 of 3; 0 errors ~ - error list 3 of 3; 15 errors ~ + error list 1 of 3; 43 errors :make ~ + > error list 2 of 3; 0 errors :helpgrep tag ~ + error list 3 of 3; 15 errors :grep ex_help *.c ~ When [count] is given, then the count'th quickfix list is made the current list. Example: > @@ -989,7 +1019,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 +1044,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-matching| 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 +1079,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 +1369,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 +1488,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 8cabf05053..c3badd5401 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 @@ -489,6 +490,7 @@ In Insert or Command-line mode: |q| q{a-z} record typed characters into register {a-z} |q| q{A-Z} record typed characters, appended to register {a-z} |q| q stop recording +|Q| Q replay last recorded macro |@| N @{a-z} execute the contents of register {a-z} (N times) |@@| N @@ repeat previous @{a-z} (N times) |:@| :@{a-z} execute the contents of register {a-z} as an Ex @@ -628,6 +630,7 @@ Short explanation of each option: *option-list* 'buflisted' 'bl' whether the buffer shows up in the buffer list 'buftype' 'bt' special type of buffer 'casemap' 'cmp' specifies how case of letters is changed +'cdhome' 'cdh' change directory to the home directory by ":cd" 'cdpath' 'cd' list of directories searched with ":cd" 'cedit' key used to open the command-line window 'charconvert' 'ccv' expression for character encoding conversion @@ -635,6 +638,7 @@ Short explanation of each option: *option-list* 'cinkeys' 'cink' keys that trigger indent when 'cindent' is set 'cinoptions' 'cino' how to do indenting when 'cindent' is set 'cinwords' 'cinw' words where 'si' and 'cin' add an indent +'cinscopedecls' 'cinsd' words that are recognized by 'cino-g' 'clipboard' 'cb' use the clipboard as the unnamed register 'cmdheight' 'ch' number of lines to use for the command-line 'cmdwinheight' 'cwh' height of the command-line window @@ -997,7 +1001,7 @@ Short explanation of each option: *option-list* |:version| :ve[rsion] show version information |:normal| :norm[al][!] {commands} execute Normal mode commands -|Q| Q switch to "Ex" mode +|gQ| gQ switch to "Ex" mode |:redir| :redir >{file} redirect messages to {file} |:silent| :silent[!] {command} execute {command} silently diff --git a/runtime/doc/recover.txt b/runtime/doc/recover.txt index 9ef5a37452..d9aaa757ad 100644 --- a/runtime/doc/recover.txt +++ b/runtime/doc/recover.txt @@ -108,7 +108,7 @@ command: *:pre* *:preserve* *E313* *E314* :pre[serve] Write all text for the current buffer into its swap file. The original file is no longer needed for - recovery. This sets a flag in the current buffer. + recovery. A Vim swap file can be recognized by the first six characters: "b0VIM ". After that comes the version number, e.g., "3.0". diff --git a/runtime/doc/remote.txt b/runtime/doc/remote.txt new file mode 100644 index 0000000000..0c1e3438de --- /dev/null +++ b/runtime/doc/remote.txt @@ -0,0 +1,131 @@ +*remote.txt* Nvim + + + VIM REFERENCE MANUAL by Bram Moolenaar + + +Vim client-server communication *client-server* + + Type |gO| to see the table of contents. + +============================================================================== +1. Common functionality *clientserver* + +Nvim's |RPC| functionality allows clients to programmatically control Nvim. Nvim +itself takes command-line arguments that cause it to become a client to another +Nvim running as a server. These arguments match those provided by Vim's +clientserver option. + +The following command line arguments are available: + + argument meaning ~ + + --remote [+{cmd}] {file} ... *--remote* + Open the file list in a remote Vim. When + there is no Vim server, execute locally. + Vim allows one init command: +{cmd}. + This must be an Ex command that can be + followed by "|". It's not yet supported by + Nvim. + The rest of the command line is taken as the + file list. Thus any non-file arguments must + come before this. + You cannot edit stdin this way |--|. + The remote Vim is raised. If you don't want + this use > + nvim --remote-send "<C-\><C-N>:n filename<CR>" +< + --remote-silent [+{cmd}] {file} ... *--remote-silent* + As above, but don't complain if there is no + server and the file is edited locally. + *--remote-tab* + --remote-tab Like --remote but open each file in a new + tabpage. + *--remote-tab-silent* + --remote-tab-silent Like --remote-silent but open each file in a + new tabpage. + *--remote-send* + --remote-send {keys} Send {keys} to server and exit. The {keys} + are not mapped. Special key names are + recognized, e.g., "<CR>" results in a CR + character. + *--remote-expr* + --remote-expr {expr} Evaluate {expr} in server and print the result + on stdout. + *--server* + --server {addr} Connect to the named pipe or socket at the + given address for executing remote commands. + See |--listen| for specifying an address when + starting a server. + +Examples ~ + +Start an Nvim server listening on a named pipe at '~/.cache/nvim/server.pipe': > + nvim --listen ~/.cache/nvim/server.pipe + +Edit "file.txt" in an Nvim server listening at '~/.cache/nvim/server.pipe': > + nvim --server ~/.cache/nvim/server.pipe --remote file.txt + +This doesn't work, all arguments after --remote will be used as file names: > + nvim --remote --server ~/.cache/nvim/server.pipe file.txt + +Tell the remote server to write all files and exit: > + nvim --server ~/.cache/nvim/server.pipe --remote-send '<C-\><C-N>:wqa<CR>' + + +REMOTE EDITING + +The --remote argument will cause a |:drop| command to be constructed from the +rest of the command line and sent as described above. +Note that the --remote and --remote-wait arguments will consume the rest of +the command line. I.e. all remaining arguments will be regarded as filenames. +You can not put options there! + + +============================================================================== +2. Missing functionality *E5600* *clientserver-missing* + +Vim supports additional functionality in clientserver that's not yet +implemented in Nvim. In particular, none of the "wait" variants are supported +yet. The following command line arguments are not yet available: + + argument meaning ~ + + --remote-wait [+{cmd}] {file} ... *--remote-wait* + Not yet supported by Nvim. + As --remote, but wait for files to complete + (unload) in remote Vim. + --remote-wait-silent [+{cmd}] {file} ... *--remote-wait-silent* + Not yet supported by Nvim. + As --remote-wait, but don't complain if there + is no server. + *--remote-tab-wait* + --remote-tab-wait Not yet supported by Nvim. + Like --remote-wait but open each file in a new + tabpage. + *--remote-tab-wait-silent* + --remote-tab-wait-silent Not yet supported by Nvim. + Like --remote-wait-silent but open each file + in a new tabpage. + *--servername* + --servername {name} Not yet supported by Nvim. + Become the server {name}. When used together + with one of the --remote commands: connect to + server {name} instead of the default (see + below). The name used will be uppercase. + + *--serverlist* + --serverlist Not yet supported by Nvim. + Output a list of server names. + + + + +SERVER NAME *client-server-name* + +By default Vim will try to register the name under which it was invoked (gvim, +egvim ...). This can be overridden with the --servername argument. Nvim +either listens on a named pipe or a socket and does not yet support this +--servername functionality. + + vim:tw=78:sw=4:ts=8:noet:ft=help:norl: diff --git a/runtime/doc/repeat.txt b/runtime/doc/repeat.txt index 7e8d93aa71..508565dea4 100644 --- a/runtime/doc/repeat.txt +++ b/runtime/doc/repeat.txt @@ -103,7 +103,7 @@ Which is two characters shorter! When using "global" in Ex mode, a special case is using ":visual" as a command. This will move to a matching line, go to Normal mode to let you -execute commands there until you use |Q| to return to Ex mode. This will be +execute commands there until you use |gQ| to return to Ex mode. This will be repeated for each matching line. While doing this you cannot use ":global". To abort this type CTRL-C twice. @@ -147,6 +147,10 @@ q Stops recording. *@@* *E748* @@ Repeat the previous @{0-9a-z":*} [count] times. + *Q* +Q Repeat the last recorded register [count] times. + See |reg_recorded()|. + *:@* :[addr]@{0-9a-z".=*+} Execute the contents of register {0-9a-z".=*+} as an Ex command. First set cursor at line [addr] (default is @@ -157,6 +161,11 @@ q Stops recording. result of evaluating the expression is executed as an Ex command. Mappings are not recognized in these commands. + When the |line-continuation| character (\) is present + at the beginning of a line in a linewise register, + then it is combined with the previous line. This is + useful for yanking and executing parts of a Vim + script. *:@:* :[addr]@: Repeat last command-line. First set cursor at line @@ -173,8 +182,7 @@ For writing a Vim script, see chapter 41 of the user manual |usr_41.txt|. *:so* *:source* *load-vim-script* :[range]so[urce] [file] Runs |Ex| commands or Lua code (".lua" files) from - [file], or from the current buffer if no [file] is - given. + [file], or current buffer if no [file]. Triggers the |SourcePre| autocommand. *:source!* :[range]so[urce]! {file} @@ -249,21 +257,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|. @@ -461,8 +470,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 5079d900c9..a2a5645baa 100644 --- a/runtime/doc/sign.txt +++ b/runtime/doc/sign.txt @@ -47,6 +47,8 @@ The color of the column is set with the SignColumn highlight group :highlight SignColumn guibg=darkgrey < +If 'cursorline' is enabled, then the CursorLineSign highlight group is used +|hl-CursorLineSign|. *sign-identifier* Each placed sign is identified by a number called the sign identifier. This identifier is used to jump to the sign or to remove the sign. The identifier @@ -85,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 @@ -120,8 +122,9 @@ See |sign_define()| for the equivalent Vim script function. in. Most useful is defining a background color. numhl={group} - Highlighting group used for 'number' column at the associated - line. Overrides |hl-LineNr|, |hl-CursorLineNr|. + Highlighting group used for the line number on the line where + the sign is placed. Overrides |hl-LineNr|, |hl-LineNrAbove|, + |hl-LineNrBelow|, and |hl-CursorLineNr|. text={text} *E239* Define the text that is displayed when there is no icon or the @@ -131,6 +134,10 @@ See |sign_define()| for the equivalent Vim script function. texthl={group} Highlighting group used for the text item. + culhl={group} + Highlighting group used for the text item when the cursor is + on the same line as the sign and 'cursorline' is enabled. + Example: > :sign define MySign text=>> texthl=Search linehl=DiffText < @@ -377,6 +384,9 @@ sign_define({list}) text text that is displayed when there is no icon or the GUI is not being used. texthl highlight group used for the text item + culhl highlight group used for the text item when + the cursor is on the same line as the sign and + 'cursorline' is enabled. numhl highlight group used for 'number' column at the associated line. Overrides |hl-LineNr|, |hl-CursorLineNr|. @@ -404,6 +414,9 @@ sign_define({list}) \ 'text' : '!!'} \ ]) < + Can also be used as a |method|: > + GetSignList()->sign_define() + sign_getdefined([{name}]) *sign_getdefined()* Get a list of defined signs and their attributes. This is similar to the |:sign-list| command. @@ -416,14 +429,19 @@ sign_getdefined([{name}]) *sign_getdefined()* following entries: icon full path to the bitmap file of the sign linehl highlight group used for the whole line the - sign is placed in. + sign is placed in; not present if not set. name name of the sign text text that is displayed when there is no icon or the GUI is not being used. - texthl highlight group used for the text item + texthl highlight group used for the text item; not + present if not set. + culhl highlight group used for the text item when + the cursor is on the same line as the sign and + 'cursorline' is enabled; not present if not + set. numhl highlight group used for 'number' column at the associated line. Overrides |hl-LineNr|, - |hl-CursorLineNr|. + |hl-CursorLineNr|; not present if not set. Returns an empty List if there are no signs and when {name} is not found. @@ -435,6 +453,9 @@ sign_getdefined([{name}]) *sign_getdefined()* " Get the attribute of the sign named mySign echo sign_getdefined("mySign") < + Can also be used as a |method|: > + GetSignList()->sign_getdefined() + sign_getplaced([{buf} [, {dict}]]) *sign_getplaced()* Return a list of signs placed in a buffer or all the buffers. This is similar to the |:sign-place-list| command. @@ -495,6 +516,9 @@ sign_getplaced([{buf} [, {dict}]]) *sign_getplaced()* " Get a List of all the placed signs echo sign_getplaced() < + Can also be used as a |method|: > + GetBufname()->sign_getplaced() +< *sign_jump()* sign_jump({id}, {group}, {buf}) Open the buffer {buf} or jump to the window that contains @@ -510,7 +534,9 @@ sign_jump({id}, {group}, {buf}) " Jump to sign 10 in the current buffer call sign_jump(10, '', '') < - + Can also be used as a |method|: > + GetSignid()->sign_jump() +< *sign_place()* sign_place({id}, {group}, {name}, {buf} [, {dict}]) Place the sign defined as {name} at line {lnum} in file or @@ -560,7 +586,9 @@ sign_place({id}, {group}, {name}, {buf} [, {dict}]) call sign_place(10, 'g3', 'sign4', 'json.c', \ {'lnum' : 40, 'priority' : 90}) < - + Can also be used as a |method|: > + GetSignid()->sign_place(group, name, expr) +< *sign_placelist()* sign_placelist({list}) Place one or more signs. This is similar to the @@ -620,6 +648,8 @@ sign_placelist({list}) \ 'lnum' : 50} \ ]) < + Can also be used as a |method|: > + GetSignlist()->sign_placelist() sign_undefine([{name}]) *sign_undefine()* sign_undefine({list}) @@ -644,6 +674,8 @@ sign_undefine({list}) " Delete all the signs call sign_undefine() < + Can also be used as a |method|: > + GetSignlist()->sign_undefine() sign_unplace({group} [, {dict}]) *sign_unplace()* Remove a previously placed sign in one or more buffers. This @@ -686,6 +718,9 @@ sign_unplace({group} [, {dict}]) *sign_unplace()* " Remove all the placed signs from all the buffers call sign_unplace('*') + +< Can also be used as a |method|: > + GetSigngroup()->sign_unplace() < sign_unplacelist({list}) *sign_unplacelist()* Remove previously placed signs from one or more buffers. This @@ -715,5 +750,8 @@ sign_unplacelist({list}) *sign_unplacelist()* \ {'id' : 20, 'buffer' : 'b.vim'}, \ ]) < + Can also be used as a |method|: > + GetSignlist()->sign_unplacelist() +< vim:tw=78:ts=8:noet:ft=help:norl: 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 e423c59efe..6875f43b86 100644 --- a/runtime/doc/syntax.txt +++ b/runtime/doc/syntax.txt @@ -188,7 +188,8 @@ A syntax group name doesn't specify any color or attributes itself. The name for a highlight or syntax group must consist of ASCII letters, digits and the underscore. As a regexp: "[a-zA-Z0-9_]*". However, Vim does not give -an error when using other characters. +an error when using other characters. The maxium length of a group name is +about 200 bytes. *E1249* To be able to allow each user to pick their favorite set of colors, there must be preferred names for highlight groups that are common for many languages. @@ -616,7 +617,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 +921,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 +1411,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 +1442,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 @@ -1496,6 +1501,22 @@ The enhanced mode also takes advantage of additional color features for a dark gvim display. Here, statements are colored LightYellow instead of Yellow, and conditionals are LightBlue for better distinction. +Both Visual Basic and FORM use the extension ".frm". 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 found, filetype will be "vb", otherwise "form". + +If the automatic detection doesn't work for you or you only edit, for +example, FORM files, use this in your startup vimrc: > + :let filetype_frm = "form" + + +FORTH *forth.vim* *ft-forth-syntax* + +Files matching "*.fs" could be F# or Forth. If the automatic detection +doesn't work for you, or you don't edit F# at all, use this in your +startup vimrc: > + :let filetype_fs = "forth" + FORTRAN *fortran.vim* *ft-fortran-syntax* @@ -3153,6 +3174,14 @@ buffer by buffer basis. For more detailed instructions see |ft_sql.txt|. +SQUIRREL *squirrel.vim* *ft-squirrel-syntax* + +Squirrel is a high level imperative, object-oriented programming language, +designed to be a light-weight scripting language that fits in the size, memory +bandwidth, and real-time requirements of applications like video games. Files +with the following extensions are recognized as squirrel files: .nut. + + TCSH *tcsh.vim* *ft-tcsh-syntax* This covers the shell named "tcsh". It is a superset of csh. See |csh.vim| @@ -3530,8 +3559,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 @@ -4368,7 +4397,7 @@ Leading context *:syn-lc* *:syn-leading* *:syn-context* Note: This is an obsolete feature, only included for backwards compatibility with previous Vim versions. It's now recommended to use the |/\@<=| construct -in the pattern. +in the pattern. You can also often use |/\zs|. The "lc" offset specifies leading context -- a part of the pattern that must be present, but is not considered part of the match. An offset of "lc=n" will @@ -4439,7 +4468,7 @@ it marks the "\(\I\i*\)" sub-expression as external; in the end pattern, it changes the \z1 back-reference into an external reference referring to the first external sub-expression in the start pattern. External references can also be used in skip patterns: > - :syn region foo start="start \(\I\i*\)" skip="not end \z1" end="end \z1" + :syn region foo start="start \z(\I\i*\)" skip="not end \z1" end="end \z1" Note that normal and external sub-expressions are completely orthogonal and indexed separately; for instance, if the pattern "\z(..\)\(..\)" is applied @@ -4801,6 +4830,7 @@ in their own color. :hi[ghlight] {group-name} List one highlight group. + *highlight-clear* *:hi-clear* :hi[ghlight] clear Reset all highlighting to the defaults. Removes all highlighting for groups added by the user! Uses the current value of 'background' to decide which @@ -4855,15 +4885,19 @@ the same syntax file on all UIs. 1. TUI highlight arguments - *bold* *underline* *undercurl* + *bold* *underline* *underlineline* + *undercurl* *underdot* *underdash* *inverse* *italic* *standout* *nocombine* *strikethrough* cterm={attr-list} *attr-list* *highlight-cterm* *E418* - attr-list is a comma separated list (without spaces) of the + attr-list is a comma-separated list (without spaces) of the following items (in any order): bold underline + underlineline double underline undercurl curly underline + underdot dotted underline + underdash dashed underline strikethrough reverse inverse same as reverse @@ -4874,8 +4908,9 @@ cterm={attr-list} *attr-list* *highlight-cterm* *E418* Note that "bold" can be used here and by using a bold font. They have the same effect. - "undercurl" falls back to "underline" in a terminal that does not - support it. The color is set using |highlight-guisp|. + "underlineline", "undercurl", "underdot", and "underdash" fall back + to "underline" in a terminal that does not support them. The color is + set using |highlight-guisp|. start={term-list} *highlight-start* *E422* stop={term-list} *term-list* *highlight-stop* @@ -5008,8 +5043,8 @@ guifg={color-name} *highlight-guifg* guibg={color-name} *highlight-guibg* guisp={color-name} *highlight-guisp* These give the foreground (guifg), background (guibg) and special - (guisp) color to use in the GUI. "guisp" is used for undercurl - and underline. + (guisp) color to use in the GUI. "guisp" is used for various + underlines. There are a few special names: NONE no color (transparent) bg use normal background color @@ -5085,8 +5120,8 @@ TermCursor cursor in a focused terminal TermCursorNC cursor in an unfocused terminal *hl-ErrorMsg* ErrorMsg error messages on the command line - *hl-VertSplit* -VertSplit the column separating vertically split windows + *hl-WinSeparator* +WinSeparator separators between window splits *hl-Folded* Folded line used for closed folds *hl-FoldColumn* @@ -5111,6 +5146,10 @@ LineNrBelow Line number for when the 'relativenumber' *hl-CursorLineNr* CursorLineNr Like LineNr when 'cursorline' is set and 'cursorlineopt' contains "number" or is "both", for the cursor line. + *hl-CursorLineSign* +CursorLineSign Like SignColumn when 'cursorline' is set for the cursor line. + *hl-CursorLineFold* +CursorLineFold Like FoldColumn when 'cursorline' is set for the cursor line. *hl-MatchParen* MatchParen The character under the cursor or just before it, if it is a paired bracket, and its match. |pi_paren.txt| @@ -5188,7 +5227,8 @@ VisualNOS Visual mode selection when vim is "Not Owning the Selection". *hl-WarningMsg* WarningMsg warning messages *hl-Whitespace* -Whitespace "nbsp", "space", "tab" and "trail" in 'listchars' +Whitespace "nbsp", "space", "tab", "multispace", "lead" and "trail" + in 'listchars' *hl-WildMenu* WildMenu current match in 'wildmenu' completion @@ -5325,11 +5365,12 @@ WARNING: The longer the tags file, the slower this will be, and the more memory Vim will consume. Only highlighting typedefs, unions and structs can be done too. For this you -must use Exuberant ctags (found at http://ctags.sf.net). +must use Universal Ctags (found at https://ctags.io) or Exuberant ctags (found +at http://ctags.sf.net). Put these lines in your Makefile: -# Make a highlight file for types. Requires Exuberant ctags and awk +# Make a highlight file for types. Requires Universal/Exuberant ctags and awk types: types.vim types.vim: *.[ch] ctags --c-kinds=gstu -o- *.[ch] |\ @@ -5339,9 +5380,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 ============================================================================== @@ -5382,7 +5423,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..f06a6bcc34 100644 --- a/runtime/doc/tabpage.txt +++ b/runtime/doc/tabpage.txt @@ -133,7 +133,10 @@ something else. :tabclose + " close the next tab page :tabclose 3 " close the third tab page :tabclose $ " close the last tab page -< + :tabclose # " close the last accessed tab page + +When a tab is closed the next tab page will become the current one. + *:tabo* *:tabonly* :tabo[nly][!] Close all other tab pages. When the 'hidden' option is set, all buffers in closed windows @@ -159,6 +162,8 @@ something else. " one :tabonly 1 " close all tab pages except the first one :tabonly $ " close all tab pages except the last one + :tabonly # " close all tab pages except the last + " accessed one SWITCHING TO ANOTHER TAB PAGE: @@ -181,6 +186,7 @@ gt *i_CTRL-<PageDown>* *i_<C-PageDown>* :+2tabnext " go to the two next tab page :1tabnext " go to the first tab page :$tabnext " go to the last tab page + :tabnext # " go to the last accessed tab page :tabnext $ " as above :tabnext - " go to the previous tab page :tabnext -1 " as above @@ -190,10 +196,6 @@ gt *i_CTRL-<PageDown>* *i_<C-PageDown>* {count}<C-PageDown> {count}gt Go to tab page {count}. The first tab page has number one. -CTRL-<Tab> *CTRL-<Tab>* -CTRL-W g<Tab> *g<Tab>* *CTRL-W_g<Tab>* -g<Tab> Go to previous (last accessed) tab page. - :tabp[revious] *:tabp* *:tabprevious* *gT* *:tabN* :tabN[ext] *:tabNext* *CTRL-<PageUp>* <C-PageUp> *<C-PageUp>* *i_CTRL-<PageUp>* *i_<C-PageUp>* @@ -213,6 +215,9 @@ gT Go to the previous tab page. Wraps around from the first one *:tabl* *:tablast* :tabl[ast] Go to the last tab page. +<C-Tab> *CTRL-<Tab>* *<C-Tab>* +CTRL-W g<Tab> *g<Tab>* *CTRL-W_g<Tab>* +g<Tab> Go to the last accessed tab page. Other commands: *:tabs* @@ -245,6 +250,8 @@ REORDERING TAB PAGES: :tabmove " move the tab page to the last :$tabmove " as above :tabmove $ " as above + :tabmove # " move the tab page after the last accessed + " tab page :tabm[ove] +[N] :tabm[ove] -[N] @@ -366,24 +373,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 +453,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..2485290667 100644 --- a/runtime/doc/tagsrch.txt +++ b/runtime/doc/tagsrch.txt @@ -544,7 +544,8 @@ also works. The <CR> and <NL> characters can never appear inside a line. The second format is new. It includes additional information in optional fields at the end of each line. It is backwards compatible with Vi. It is -only supported by new versions of ctags (such as Exuberant ctags). +only supported by new versions of ctags (such as Universal ctags or Exuberant +ctags). {tagname} The identifier. Normally the name of a function, but it can be any identifier. It cannot contain a <Tab>. @@ -705,7 +706,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..ddf52b65c6 100644 --- a/runtime/doc/term.txt +++ b/runtime/doc/term.txt @@ -108,6 +108,15 @@ and right scroll margins as well. If Nvim detects that the terminal is Xterm, it will make use of this ability to speed up scrolling that is not the full width of the terminal. + *tui-input* +Nvim uses libtermkey to convert terminal escape sequences to key codes. +|terminfo| is used first, and CSI sequences not in |terminfo| (including +exteneded keys a.k.a. modifyOtherKeys or `CSI u`) can also be parsed. +For example, when running Nvim in tmux, this makes Nvim leave Insert mode and +go to the window below: > + tmux send-keys 'Escape' [ 2 7 u 'C-W' j +Where `'Escape' [ 2 7 u` is an unambiguous `CSI u` sequence for the <Esc> key. + *tui-colors* Nvim uses 256 colours by default, ignoring |terminfo| for most terminal types, including "linux" (whose virtual terminals have had 256-colour support since @@ -133,7 +142,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 +330,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 f0bda5aaf8..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* @@ -157,6 +157,9 @@ assert_nobeep({cmd}) *assert_nobeep()* produces a beep or visual bell. Also see |assert_beeps()|. + Can also be used as a |method|: > + GetCmd()->assert_nobeep() +< *assert_notequal()* assert_notequal({expected}, {actual} [, {msg}]) The opposite of `assert_equal()`: add an error message to 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 441ae13df4..a9d9f81849 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,69 +195,70 @@ 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 - strings : > + 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 - corresponding to a node : > - ((idenfitier) @constant (#match? @constant "^[A-Z_]+$")) + `vim-match?` *ts-predicate-vim-match?* + This will match if the provided vim regex matches the text + corresponding to a node: > + ((identifier) @constant (#match? @constant "^[A-Z_]+$")) < Note: the `^` and `$` anchors will respectively match the start and end of the node's text. - `lua-match?` *ts-predicate-lua-match?* + `lua-match?` *ts-predicate-lua-match?* This will match the same way than |match?| but using lua regexes. - `contains?` *ts-predicate-contains?* + `contains?` *ts-predicate-contains?* Will check if any of the following arguments appears in the - text corresponding to the node : > + text corresponding to the node: > ((identifier) @foo (#contains? @foo "foo")) ((identifier) @foo-bar (#contains @foo-bar "foo" "bar")) < - `any-of?` *ts-predicate-any-of?* - Will check if the text is the same as any of the following. + `any-of?` *ts-predicate-any-of?* + Will check if the text is the same as any of the following + arguments: > + ((identifier) @foo (#any-of? @foo "foo" "bar")) +< This is the recommended way to check if the node matches one of many keywords for example, as it has been optimized for this. - 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 +271,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 +283,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 +342,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 @@ -353,7 +358,7 @@ Lua module: vim.treesitter *lua-treesitter-core* get_parser({bufnr}, {lang}, {opts}) *get_parser()* Gets the parser for this bufnr / ft combination. - If needed this will create the parser. Unconditionnally attach + If needed this will create the parser. Unconditionally attach the provided callback Parameters: ~ @@ -380,7 +385,7 @@ Lua module: vim.treesitter.language *treesitter-language* inspect_language({lang}) *inspect_language()* Inspects the provided language. - Inspecting provides some useful informations on the language + Inspecting provides some useful information on the language like node names, ... Parameters: ~ @@ -421,9 +426,9 @@ get_node_text({node}, {source}) *get_node_text()* Gets the text corresponding to a given node Parameters: ~ - {node} the node - {bsource} The buffer or string from which the node is - extracted + {node} the node + {source} The buffer or string from which the node is + extracted get_query({lang}, {query_name}) *get_query()* Returns the runtime query {query_name} for {lang}. @@ -461,14 +466,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` . + 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 @@ -479,7 +485,7 @@ Query:iter_captures({self}, {node}, {source}, {start}, {stop}) {source} is needed if the query contains predicates, then the caller must ensure to use a freshly parsed tree consistent - with the current text of the buffer (if relevent). {start_row} + with the current text of the buffer (if relevant). {start_row} and {end_row} can be used to limit matches inside a row range (this is typically used with root node as the node, i e to get syntax highlight matches in the current viewport). When @@ -503,8 +509,7 @@ Query:iter_captures({self}, {node}, {source}, {start}, {stop}) Parameters: ~ {node} The node under which the search will occur - {source} The source buffer or string to exctract text - from + {source} The source buffer or string to extract text from {start} The starting line of the search {stop} The stopping line of the search (end-exclusive) {self} @@ -523,19 +528,17 @@ Query:iter_matches({self}, {node}, {source}, {start}, {stop}) a table mapping capture indices to nodes, and metadata from any directives processing the match. If the query has more than one pattern the capture table might be sparse, and e.g. - `pairs()` method should be used over `ipairs` . Here an - example iterating over all captures in every match: + `pairs()` method should be used over `ipairs`. Here an example + iterating over all captures in every match: > for pattern, match, metadata in cquery:iter_matches(tree:root(), bufnr, first, last) do for id, node in pairs(match) do local name = query.captures[id] -- `node` was captured by the `name` capture in the match -< -> - local node_data = metadata[id] -- Node level metadata -< -> + + local node_data = metadata[id] -- Node level metadata + ... use the info here ... end end @@ -611,10 +614,9 @@ LanguageTree:children({self}) *LanguageTree:children()* {self} LanguageTree:contains({self}, {range}) *LanguageTree:contains()* - Determines wether This goes down the tree to recursively check childs. + 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, @@ -624,8 +626,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} @@ -668,7 +671,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} @@ -681,7 +685,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| @@ -697,13 +701,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/uganda.txt b/runtime/doc/uganda.txt index 79519da51e..23dfa082a0 100644 --- a/runtime/doc/uganda.txt +++ b/runtime/doc/uganda.txt @@ -129,11 +129,12 @@ Kibaale Children's Centre *kcc* *Kibaale* *charity* Kibaale Children's Centre (KCC) is located in Kibaale, a small town in the south of Uganda, near Tanzania, in East Africa. The area is known as Rakai District. The population is mostly farmers. Although people are poor, there -is enough food. But this district is suffering from AIDS more than any other -part of the world. Some say that it started there. Estimations are that 10 -to 30% of the Ugandans are infected with HIV. Because parents die, there are -many orphans. In this district about 60,000 children have lost one or both -parents, out of a population of 350,000. And this is still continuing. +usually is enough food. But this district is suffering from AIDS more than +any other part of the world. Some say that it started there. Estimations are +that in the past 10 to 30% of the Ugandans are infected with HIV. Because +parents die, there are many orphans. In this district about 60,000 children +have lost one or both parents, out of a population of 350,000. Although AIDS +is now mostly under control, the problems are still continuing. The children need a lot of help. The KCC is working hard to provide the needy with food, medical care and education. Food and medical care to keep them diff --git a/runtime/doc/ui.txt b/runtime/doc/ui.txt index e7be14e732..c5e3b60079 100644 --- a/runtime/doc/ui.txt +++ b/runtime/doc/ui.txt @@ -283,19 +283,24 @@ numerical highlight ids to the actual attributes. attributes specified by the `rgb_attr` and `cterm_attr` dicts, with the following (all optional) keys. - `foreground`: foreground color. - `background`: background color. - `special`: color to use for underline and undercurl, when present. - `reverse`: reverse video. Foreground and background colors are - switched. - `italic`: italic text. - `bold`: bold text. - `strikethrough`: struckthrough text. - `underline`: underlined text. The line has `special` color. - `undercurl`: undercurled text. The curl has `special` color. - `blend`: Blend level (0-100). Could be used by UIs to support - blending floating windows to the background or to - signal a transparent cursor. + `foreground`: foreground color. + `background`: background color. + `special`: color to use for various underlines, when + present. + `reverse`: reverse video. Foreground and background colors + are switched. + `italic`: italic text. + `bold`: bold text. + `strikethrough`: struckthrough text. + `underline`: underlined text. The line has `special` color. + `underlineline`: double underlined text. The lines have `special` + color. + `undercurl`: undercurled text. The curl has `special` color. + `underdot`: underdotted text. The dots have `special` color. + `underdash`: underdashed text. The dashes have `special` color. + `blend`: Blend level (0-100). Could be used by UIs to + support blending floating windows to the + background or to signal a transparent cursor. For absent color keys the default color should be used. Don't store the default value in the table, rather a sentinel value, so that @@ -444,14 +449,17 @@ is not active. New UIs should implement |ui-linegrid| instead. `foreground`: foreground color. `background`: background color. - `special`: color to use for underline and undercurl, when present. + `special`: color to use for various underlines, when present. `reverse`: reverse video. Foreground and background colors are switched. `italic`: italic text. `bold`: bold text. `strikethrough`: struckthrough text. `underline`: underlined text. The line has `special` color. + `underlineline`: double underlined text. The lines have `special` color. `undercurl`: undercurled text. The curl has `special` color. + `underdot`: underdotted text. The dots have `special` color. + `underdash`: underdashed text. The dashes have `special` color. ["put", text] The (utf-8 encoded) string `text` is put at the cursor position 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_08.txt b/runtime/doc/usr_08.txt index 8ccaa73006..1d20913a14 100644 --- a/runtime/doc/usr_08.txt +++ b/runtime/doc/usr_08.txt @@ -482,6 +482,8 @@ statusline: 0 never 1 only when there are split windows (the default) 2 always + 3 have a global statusline at the bottom instead of one for each + window Many commands that edit another file have a variant that splits the window. For Command-line commands this is done by prepending an "s". For example: 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_20.txt b/runtime/doc/usr_20.txt index cff5c7d2f2..6a8836c8e8 100644 --- a/runtime/doc/usr_20.txt +++ b/runtime/doc/usr_20.txt @@ -292,7 +292,7 @@ to newer commands. There are actually five histories. The ones we will mention here are for ":" commands and for "/" and "?" search commands. The "/" and "?" commands share the same history, because they are both search commands. The three other -histories are for expressions, debug more commands and input lines for the +histories are for expressions, debug mode commands and input lines for the input() function. |cmdline-history| Suppose you have done a ":set" command, typed ten more colon commands and then diff --git a/runtime/doc/usr_29.txt b/runtime/doc/usr_29.txt index 3381d1870c..d8c556c281 100644 --- a/runtime/doc/usr_29.txt +++ b/runtime/doc/usr_29.txt @@ -33,10 +33,12 @@ following command: > ctags *.c "ctags" is a separate program. Most Unix systems already have it installed. -If you do not have it yet, you can find Exuberant ctags here: - +If you do not have it yet, you can find Universal/Exuberant ctags at: + http://ctags.io ~ http://ctags.sf.net ~ +Universal ctags is preferred, Exuberant ctags is no longer being developed. + Now when you are in Vim and you want to go to a function definition, you can jump to it by using the following command: > @@ -142,15 +144,15 @@ ONE TAGS FILE When Vim has to search many places for tags files, you can hear the disk rattling. It may get a bit slow. In that case it's better to spend this time while generating one big tags file. You might do this overnight. - This requires the Exuberant ctags program, mentioned above. It offers an -argument to search a whole directory tree: > + This requires the Universal or Exuberant ctags program, mentioned above. +It offers an argument to search a whole directory tree: > cd ~/proj ctags -R . -The nice thing about this is that Exuberant ctags recognizes various file -types. Thus this doesn't work just for C and C++ programs, also for Eiffel -and even Vim scripts. See the ctags documentation to tune this. +The nice thing about this is that Universal/Exuberant ctags recognizes various +file types. Thus this doesn't work just for C and C++ programs, also for +Eiffel and even Vim scripts. See the ctags documentation to tune this. Now you only need to tell Vim where your big tags file is: > :set tags=~/proj/tags @@ -232,7 +234,8 @@ A TAGS BROWSER Since CTRL-] takes you to the definition of the identifier under the cursor, you can use a list of identifier names as a table of contents. Here is an example. - First create a list of identifiers (this requires Exuberant ctags): > + First create a list of identifiers (this requires Universal or Exuberant +ctags): > ctags --c-types=f -f functions *.c diff --git a/runtime/doc/usr_41.txt b/runtime/doc/usr_41.txt index 6a9284dac9..ff69b8f224 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 @@ -852,6 +861,7 @@ Command line: *command-line-functions* getcmdtype() return the current command-line type getcmdwintype() return the current command-line window type getcompletion() list of command-line completion matches + fullcommand() get full command name Quickfix and location lists: *quickfix-functions* getqflist() list of quickfix errors @@ -950,6 +960,10 @@ Window size and position: *window-size-functions* winrestview() restore saved view of current window Mappings: *mapping-functions* + digraph_get() get |digraph| + digraph_getlist() get all |digraph|s + digraph_set() register |digraph| + digraph_setlist() register multiple |digraph|s hasmapto() check if a mapping exists mapcheck() check if a matching mapping exists maparg() get rhs of a mapping 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 b0e0bdcb84..75ee0fdfdf 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,7 +388,9 @@ 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 - |:set| - filter by variable name + |:registers| - filter by register contents + (does not work multi-line) + |:set| - filter by option name Only normal messages are filtered, error messages are not. @@ -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 d88f4f42e8..59f085977b 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 @@ -82,7 +84,7 @@ Nvim creates the following default mappings at |startup|. You can disable any of these in your config by simply removing the mapping, e.g. ":unmap Y". > nnoremap Y y$ - nnoremap <C-L> <Cmd>nohlsearch<Bar>diffupdate<CR><C-L> + nnoremap <C-L> <Cmd>nohlsearch<Bar>diffupdate<Bar>normal! <C-L><CR> inoremap <C-U> <C-G>u<C-U> inoremap <C-W> <C-G>u<C-W> < @@ -178,8 +180,12 @@ Commands: |:Man| is available by default, with many improvements such as completion |:sign-define| accepts a `numhl` argument, to highlight the line number |:match| can be invoked before highlight group is defined + |:source| works with Lua and anonymous (no file) scripts Events: + |RecordingEnter| + |RecordingLeave| + |SearchWrapped| |Signal| |TabNewEntered| |TermClose| @@ -206,6 +212,7 @@ Highlight groups: |hl-Substitute| |hl-TermCursor| |hl-TermCursorNC| + |hl-WinSeparator| highlights window separators |hl-Whitespace| highlights 'listchars' whitespace Input/Mappings: @@ -222,9 +229,11 @@ Options: 'cpoptions' flags: |cpo-_| 'display' flags: "msgsep" minimizes scrolling when showing messages 'guicursor' works in the terminal - 'fillchars' flags: "msgsep" (see 'display') + 'fillchars' flags: "msgsep" (see 'display'), "horiz", "horizup", + "horizdown", "vertleft", "vertright", "verthoriz" 'foldcolumn' supports up to 9 dynamic/fixed columns 'inccommand' shows interactive results for |:substitute|-like commands + 'laststatus' global statusline support 'pumblend' pseudo-transparent popupmenu 'scrollback' 'signcolumn' supports up to 9 dynamic/fixed columns @@ -317,6 +326,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 @@ -341,6 +352,7 @@ Highlight groups: |hl-ColorColumn|, |hl-CursorColumn| are lower priority than most other groups |hl-CursorLine| is low-priority unless foreground color is set + *hl-VertSplit* superseded by |hl-WinSeparator| Macro/|recording| behavior Replay of a macro recorded during :lmap produces the same actions as when it @@ -355,7 +367,8 @@ Motion: The |jumplist| avoids useless/phantom jumps. Normal commands: - |Q| is the same as |gQ| + |Q| replays the last recorded macro instead of switching to Ex mode. + Instead |gQ| can be used to enter Ex mode. Options: 'ttimeout', 'ttimeoutlen' behavior was simplified @@ -425,10 +438,11 @@ 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()| only checks for tab-local directory when -1 is passed as +- |haslocaldir()| checks for tab-local directory if and only if -1 is passed as window number, and its only possible returns values are 0 and 1. - `getcwd(-1)` is equivalent to `getcwd(-1, 0)` instead of returning the global working directory. Use `getcwd(-1, -1)` to get the global working directory. @@ -477,7 +491,6 @@ Commands: :tearoff Compile-time features: - EBCDIC Emacs tags support X11 integration (see |x11-selection|) @@ -486,6 +499,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. @@ -568,6 +584,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 111588e43a..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| @@ -360,7 +361,8 @@ same amount of text as the last time: last line the same number of characters as in the last line the last time. The start of the text is the Cursor position. If the "$" command was used as one of the last commands to extend the highlighted text, the repeating will -be applied up to the rightmost column of the longest line. +be applied up to the rightmost column of the longest line. Any count passed +to the `.` command is not used. ============================================================================== @@ -477,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 e0c33fa2c9..cd5425336f 100644 --- a/runtime/doc/windows.txt +++ b/runtime/doc/windows.txt @@ -104,6 +104,8 @@ when the last window also has a status line: 'laststatus' = 0 never a status line 'laststatus' = 1 status line if there is more than one window 'laststatus' = 2 always a status line + 'laststatus' = 3 have a global statusline at the bottom instead + of one for each window You can change the contents of the status line with the 'statusline' option. This option can be local to the window, so that you can have a different @@ -116,13 +118,15 @@ other windows. If 'mouse' is enabled, a status line can be dragged to resize windows. *filler-lines* -The lines after the last buffer line in a window are called filler lines. -These lines start with a tilde (~) character. By default, these are -highlighted as NonText (|hl-NonText|). The EndOfBuffer highlight group -(|hl-EndOfBuffer|) can be used to change the highlighting of filler lines. +The lines after the last buffer line in a window are called filler lines. By +default, these lines start with a tilde (~) character. The 'eob' item in the +'fillchars' option can be used to change this character. By default, these +characters are highlighted as NonText (|hl-NonText|). The EndOfBuffer +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* @@ -221,6 +225,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} @@ -441,7 +449,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". ============================================================================== @@ -903,12 +911,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" @@ -931,14 +939,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 @@ -950,10 +958,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..8224b79534 --- /dev/null +++ b/runtime/filetype.lua @@ -0,0 +1,43 @@ +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 + +vim.api.nvim_create_augroup("filetypedetect", {clear = false}) + +vim.api.nvim_create_autocmd({"BufRead", "BufNewFile"}, { + group = "filetypedetect", + callback = function() + vim.filetype.match(vim.fn.expand("<afile>")) + end, +}) + +-- These *must* be sourced after the autocommand above is created +vim.cmd [[ +runtime! ftdetect/*.vim +runtime! ftdetect/*.lua +]] + +-- Set a marker so that the ftdetect scripts are not sourced a second time by filetype.vim +vim.g.did_load_ftdetect = 1 + +-- If filetype.vim is disabled, set up the autocmd to use scripts.vim +if vim.g.did_load_filetypes then + vim.api.nvim_create_autocmd({"BufRead", "BufNewFile"}, { + group = "filetypedetect", + command = "if !did_filetype() && expand('<amatch>') !~ g:ft_ignore_pat | runtime! scripts.vim | endif", + }) + + vim.api.nvim_create_autocmd("StdinReadPost", { + group = "filetypedetect", + command = "if !did_filetype() | runtime! scripts.vim | endif", + }) +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 1b48070128..f7c0317eff 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 Nov 16 +" Last Change: 2022 Apr 07 " 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 @@ -119,7 +119,7 @@ au BufNewFile,BufRead *.aml setf aml " APT config file au BufNewFile,BufRead apt.conf setf aptconf au BufNewFile,BufRead */.aptitude/config setf aptconf -au BufNewFile,BufRead */etc/apt/apt.conf.d/{[-_[:alnum:]]\+,[-_.[:alnum:]]\+.conf} setf aptconf +" more generic pattern far down " Arch Inventory file au BufNewFile,BufRead .arch-inventory,=tagging-method setf arch @@ -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,13 +199,15 @@ 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 +" Batch file for MSDOS. See dist#ft#FTsys for *.sys +au BufNewFile,BufRead *.bat setf dosbatch " *.cmd is close to a Batch file, but on OS/2 Rexx files also use *.cmd. au BufNewFile,BufRead *.cmd \ if getline(1) =~ '^/\*' | setf rexx | else | setf dosbatch | endif +" ABB RAPID or Batch file for MSDOS. +au BufNewFile,BufRead *.sys\c call dist#ft#FTsys() " Batch file for 4DOS au BufNewFile,BufRead *.btm call dist#ft#FTbtm() @@ -224,6 +227,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 @@ -256,7 +262,7 @@ au BufNewFile,BufRead *.lpc,*.ulpc setf lpc au BufNewFile,BufRead calendar setf calendar " C# -au BufNewFile,BufRead *.cs setf cs +au BufNewFile,BufRead *.cs,*.csx setf cs " CSDL au BufNewFile,BufRead *.csdl setf csdl @@ -352,13 +358,8 @@ au BufNewFile,BufRead *.eni setf cl " Clever or dtd au BufNewFile,BufRead *.ent call dist#ft#FTent() -" Clipper (or FoxPro; could also be eviews) -au BufNewFile,BufRead *.prg - \ if exists("g:filetype_prg") | - \ exe "setf " . g:filetype_prg | - \ else | - \ setf clipper | - \ endif +" Clipper, FoxPro, ABB RAPID or eviews +au BufNewFile,BufRead *.prg\c call dist#ft#FTprg() " Clojure au BufNewFile,BufRead *.clj,*.cljs,*.cljx,*.cljc setf clojure @@ -389,10 +390,14 @@ au BufNewFile,BufRead *.cfm,*.cfi,*.cfc setf cf " Configure scripts au BufNewFile,BufRead configure.in,configure.ac setf config +" Cooklang +au BufNewFile,BufRead *.cook setf cook + " 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 @@ -411,6 +416,9 @@ au BufNewFile,BufRead *.ex call dist#ft#ExCheck() au BufRead,BufNewFile mix.lock,*.exs setf elixir au BufRead,BufNewFile *.eex,*.leex setf eelixir +" Elvish +au BufRead,BufNewFile *.elv setf elvish + " Euphoria 3 or 4 au BufNewFile,BufRead *.eu,*.ew,*.exu,*.exw call dist#ft#EuphoriaCheck() if has("fname_case") @@ -432,7 +440,7 @@ au BufNewFile,BufRead *quake[1-3]/*.cfg setf quake au BufNewFile,BufRead *.qc setf c " Configure files -au BufNewFile,BufRead *.cfg setf cfg +au BufNewFile,BufRead *.cfg\c call dist#ft#FTcfg() " Cucumber au BufNewFile,BufRead *.feature setf cucumber @@ -475,6 +483,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 @@ -484,12 +493,15 @@ au BufNewFile,BufRead *.desktop,*.directory setf desktop au BufNewFile,BufRead dict.conf,.dictrc setf dictconf " Dictd config -au BufNewFile,BufRead dictd.conf setf dictdconf +au BufNewFile,BufRead dictd*.conf setf dictdconf + +" DEP3 formatted patch files +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 | @@ -628,7 +640,7 @@ au BufNewFile,BufRead auto.master setf conf au BufNewFile,BufRead *.mas,*.master setf master " Forth -au BufNewFile,BufRead *.fs,*.ft,*.fth setf forth +au BufNewFile,BufRead *.ft,*.fth setf forth " Reva Forth au BufNewFile,BufRead *.frt setf reva @@ -645,12 +657,27 @@ 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() + +" F# +au BufNewFile,BufRead *.fsi,*.fsx setf fsharp + " GDB command files -au BufNewFile,BufRead .gdbinit,gdbinit setf gdb +au BufNewFile,BufRead .gdbinit,gdbinit,.gdbearlyinit,gdbearlyinit,*.gdb 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 @@ -662,26 +689,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 @@ -701,16 +730,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 @@ -726,12 +763,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 @@ -744,12 +787,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 @@ -780,6 +832,10 @@ au BufNewFile,BufRead *.hb setf hb " Httest au BufNewFile,BufRead *.htt,*.htb setf httest +" i3 (and sway) +au BufNewFile,BufRead */i3/config,*/sway/config setf i3config +au BufNewFile,BufRead */.i3/config,*/.sway/config setf i3config + " Icon au BufNewFile,BufRead *.icn setf icon @@ -870,6 +926,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 @@ -888,6 +947,11 @@ au BufNewFile,BufRead *.jl setf julia " Kixtart au BufNewFile,BufRead *.kix setf kix +" Kuka Robot Language +au BufNewFile,BufRead *.src\c call dist#ft#FTsrc() +au BufNewFile,BufRead *.dat\c call dist#ft#FTdat() +au BufNewFile,BufRead *.sub\c setf krl + " Kimwitu[++] au BufNewFile,BufRead *.k setf kwt @@ -912,7 +976,7 @@ au BufNewFile,BufRead *.latte,*.lte setf latte " Limits au BufNewFile,BufRead */etc/limits,*/etc/*limits.conf,*/etc/*limits.d/*.conf setf limits -" LambdaProlog (*.mod too, see Modsim) +" LambdaProlog (see dist#ft#FTmod for *.mod) au BufNewFile,BufRead *.sig setf lprolog " LDAP LDIF @@ -921,6 +985,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 @@ -945,9 +1012,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 @@ -1072,16 +1139,11 @@ au BufNewFile,BufRead *.mms call dist#ft#FTmms() " Symbian meta-makefile definition (MMP) au BufNewFile,BufRead *.mmp setf mmp -" Modsim III (or LambdaProlog) -au BufNewFile,BufRead *.mod - \ if getline(1) =~ '\<module\>' | - \ setf lprolog | - \ else | - \ setf modsim3 | - \ endif +" ABB Rapid, Modula-2, Modsim III or LambdaProlog +au BufNewFile,BufRead *.mod\c call dist#ft#FTmod() -" Modula-2 (.md removed in favor of Markdown) -au BufNewFile,BufRead *.m2,*.DEF,*.MOD,*.mi setf modula2 +" Modula-2 (.md removed in favor of Markdown, see dist#ft#FTmod for *.MOD) +au BufNewFile,BufRead *.m2,*.DEF,*.mi setf modula2 " Modula-3 (.m3, .i3, .mg, .ig) au BufNewFile,BufRead *.[mi][3g] setf modula3 @@ -1095,6 +1157,9 @@ au BufNewFile,BufRead *.moo setf moo " Modconf au BufNewFile,BufRead */etc/modules.conf,*/etc/modules,*/etc/conf.modules setf modconf +" MPD is based on XML +au BufNewFile,BufRead *.mpd setf xml + " Mplayer config au BufNewFile,BufRead mplayer.conf,*/.mplayer/config setf mplayerconf @@ -1110,14 +1175,15 @@ au BufNewFile,BufRead *.msql setf msql " Mysql au BufNewFile,BufRead *.mysql setf mysql -" Mutt setup files (must be before catch *.rc) -au BufNewFile,BufRead */etc/Muttrc.d/* call s:StarSetf('muttrc') - " Tcl Shell RC file au BufNewFile,BufRead tclsh.rc setf tcl " M$ Resource files -au BufNewFile,BufRead *.rc,*.rch setf rc +" /etc/Muttrc.d/file.rc is muttrc +au BufNewFile,BufRead *.rc,*.rch + \ if expand("<afile>") !~ "/etc/Muttrc.d/" | + \ setf rc | + \ endif " MuPAD source au BufRead,BufNewFile *.mu setf mupad @@ -1152,6 +1218,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 @@ -1193,6 +1262,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 @@ -1202,6 +1274,9 @@ au BufNewFile,BufRead *.[Oo][Pp][Ll] setf opl " Oracle config file au BufNewFile,BufRead *.ora setf ora +" Org +au BufNewFile,BufRead *.org,*.org_archive setf org + " Packet filter conf au BufNewFile,BufRead pf.conf setf pf @@ -1266,9 +1341,10 @@ au BufNewFile,BufRead *.pm au BufNewFile,BufRead *.pod setf pod " Php, php3, php4, etc. -" Also Phtml (was used for PHP 2 in the past) -" Also .ctp for Cake template file -au BufNewFile,BufRead *.php,*.php\d,*.phtml,*.ctp setf php +" Also Phtml (was used for PHP 2 in the past). +" Also .ctp for Cake template file. +" Also .phpt for php tests. +au BufNewFile,BufRead *.php,*.php\d,*.phtml,*.ctp,*.phpt setf php " PHP config au BufNewFile,BufRead php.ini-* setf dosini @@ -1334,6 +1410,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 @@ -1341,6 +1420,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 @@ -1406,6 +1488,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 @@ -1471,6 +1556,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 @@ -1542,16 +1630,22 @@ au BufNewFile,BufRead *.sass setf sass au BufNewFile,BufRead *.sa setf sather " Scala -au BufNewFile,BufRead *.scala,*.sc setf scala +au BufNewFile,BufRead *.scala setf scala " SBT - Scala Build Tool au BufNewFile,BufRead *.sbt setf sbt +" SuperCollider +au BufNewFile,BufRead *.sc call dist#ft#FTsc() + +au BufNewFile,BufRead *.quark setf supercollider + +" scdoc +au BufNewFile,BufRead *.scd call dist#ft#FTscd() + " Scilab au BufNewFile,BufRead *.sci,*.sce setf scilab -" scdoc -au BufNewFile,BufRead *.scd setf scdoc " SCSS au BufNewFile,BufRead *.scss setf scss @@ -1636,13 +1730,16 @@ au BufNewFile,BufRead .tcshrc,*.tcsh,tcsh.tcshrc,tcsh.login call dist#ft#SetFile " (patterns ending in a start further below) au BufNewFile,BufRead .login,.cshrc,csh.cshrc,csh.login,csh.logout,*.csh,.alias call dist#ft#CSH() +" Zig +au BufNewFile,BufRead *.zig setf zig + " Z-Shell script (patterns ending in a star further below) au BufNewFile,BufRead .zprofile,*/etc/zprofile,.zfbfmarks setf zsh 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 @@ -1710,6 +1807,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 @@ -1722,6 +1822,10 @@ au BufNewFile,BufRead *.speedup,*.spdata,*.spd setf spup " Slice au BufNewFile,BufRead *.ice setf slice +" Microsoft Visual Studio Solution +au BufNewFile,BufRead *.sln setf solution +au BufNewFile,BufRead *.slnf setf json + " Spice au BufNewFile,BufRead *.sp,*.spice setf spice @@ -1743,9 +1847,12 @@ au BufNewFile,BufRead *.sqlj setf sqlj " SQR au BufNewFile,BufRead *.sqr,*.sqi setf sqr +" Squirrel +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 @@ -1800,6 +1907,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 @@ -1817,6 +1927,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 @@ -1834,6 +1947,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() @@ -1851,7 +1967,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 @@ -1860,7 +1982,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 @@ -1920,9 +2042,15 @@ 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 +" Vagrant (uses Ruby syntax) +au BufNewFile,BufRead Vagrantfile setf ruby + " Verilog HDL au BufNewFile,BufRead *.v setf verilog @@ -1950,7 +2078,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 @@ -2055,9 +2183,15 @@ au BufNewFile,BufRead *.xml call dist#ft#FTxml() " XMI (holding UML models) is also XML au BufNewFile,BufRead *.xmi setf xml -" CSPROJ files are Visual Studio.NET's XML-based project config files +" CSPROJ files are Visual Studio.NET's XML-based C# project config files au BufNewFile,BufRead *.csproj,*.csproj.user setf xml +" FSPROJ files are Visual Studio.NET's XML-based F# project config files +au BufNewFile,BufRead *.fsproj,*.fsproj.user setf xml + +" VBPROJ files are Visual Studio.NET's XML-based Visual Basic project config files +au BufNewFile,BufRead *.vbproj,*.vbproj.user setf xml + " Qt Linguist translation source and Qt User Interface Files are XML " However, for .ts Typescript is more common. au BufNewFile,BufRead *.ui setf xml @@ -2108,6 +2242,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 @@ -2143,6 +2280,12 @@ au BufNewFile,BufRead * au StdinReadPost * if !did_filetype() | runtime! scripts.vim | endif +" Plain text files, needs to be far down to not override others. This avoids +" the "conf" type being used if there is a line starting with '#'. +" But before patterns matching everything in a directory. +au BufNewFile,BufRead *.text,README,LICENSE,COPYING,AUTHORS setf text + + " Extra checks for when no filetype has been detected now. Mostly used for " patterns that end in "*". E.g., "zsh*" matches "zsh.vim", but that's a Vim " script file. @@ -2155,7 +2298,10 @@ au BufNewFile,BufRead proftpd.conf* call s:StarSetf('apachestyle') " More Apache config files au BufNewFile,BufRead access.conf*,apache.conf*,apache2.conf*,httpd.conf*,srm.conf* call s:StarSetf('apache') -au BufNewFile,BufRead */etc/apache2/*.conf*,*/etc/apache2/conf.*/*,*/etc/apache2/mods-*/*,*/etc/apache2/sites-*/*,*/etc/httpd/conf.d/*.conf* call s:StarSetf('apache') +au BufNewFile,BufRead */etc/apache2/*.conf*,*/etc/apache2/conf.*/*,*/etc/apache2/mods-*/*,*/etc/apache2/sites-*/*,*/etc/httpd/conf.*/*,*/etc/httpd/mods-*/*,*/etc/httpd/sites-*/*,*/etc/httpd/conf.d/*.conf* call s:StarSetf('apache') + +" APT config file +au BufNewFile,BufRead */etc/apt/apt.conf.d/{[-_[:alnum:]]\+,[-_.[:alnum:]]\+.conf} call s:StarSetf('aptconf') " Asterisk config file au BufNewFile,BufRead *asterisk/*.conf* call s:StarSetf('asterisk') @@ -2193,6 +2339,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') @@ -2237,6 +2386,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') @@ -2258,6 +2410,9 @@ au BufNewFile,BufRead */etc/modutils/* \|endif au BufNewFile,BufRead */etc/modprobe.* call s:StarSetf('modconf') +" Mutt setup files (must be before catch *.rc) +au BufNewFile,BufRead */etc/Muttrc.d/* call s:StarSetf('muttrc') + " Mutt setup file au BufNewFile,BufRead .mutt{ng,}rc*,*/.mutt{ng,}/mutt{ng,}rc* call s:StarSetf('muttrc') au BufNewFile,BufRead mutt{ng,}rc*,Mutt{ng,}rc* call s:StarSetf('muttrc') @@ -2350,20 +2505,18 @@ au BufNewFile,BufRead .zsh*,.zlog*,.zcompdump* call s:StarSetf('zsh') au BufNewFile,BufRead zsh*,zlog* call s:StarSetf('zsh') -" Plain text files, needs to be far down to not override others. This avoids -" the "conf" type being used if there is a line starting with '#'. -au BufNewFile,BufRead *.text,README setf text - " Help files match *.txt but should have a last line that is a modeline. au BufNewFile,BufRead *.txt \ if getline('$') !~ 'vim:.*ft=help' \| 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 @@ -2389,7 +2542,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/ant.vim b/runtime/ftplugin/ant.vim index 5905858896..aee07ca4b9 100644 --- a/runtime/ftplugin/ant.vim +++ b/runtime/ftplugin/ant.vim @@ -1,8 +1,10 @@ " Vim filetype plugin file " Language: ant -" Maintainer: Dan Sharp <dwsharp at users dot sourceforge dot net> +" +" This runtime file is looking for a new maintainer. +" +" Former maintainer: Dan Sharp " Last Changed: 20 Jan 2009 -" URL: http://dwsharp.users.sourceforge.net/vim/ftplugin if exists("b:did_ftplugin") | finish | endif diff --git a/runtime/ftplugin/aspvbs.vim b/runtime/ftplugin/aspvbs.vim index 660dab4685..70a130d287 100644 --- a/runtime/ftplugin/aspvbs.vim +++ b/runtime/ftplugin/aspvbs.vim @@ -1,8 +1,10 @@ " Vim filetype plugin file " Language: aspvbs -" Maintainer: Dan Sharp <dwsharp at users dot sourceforge dot net> +" +" This runtime file is looking for a new maintainer. +" +" Former maintainer: Dan Sharp " Last Changed: 20 Jan 2009 -" URL: http://dwsharp.users.sourceforge.net/vim/ftplugin if exists("b:did_ftplugin") | finish | endif 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/c.vim b/runtime/ftplugin/c.vim index d4564a4aec..cfaf26f66c 100644 --- a/runtime/ftplugin/c.vim +++ b/runtime/ftplugin/c.vim @@ -1,7 +1,7 @@ " Vim filetype plugin file " Language: C " Maintainer: Bram Moolenaar <Bram@vim.org> -" Last Change: 2021 Sep 21 +" Last Change: 2022 Apr 08 " Only do this when not done yet for this buffer if exists("b:did_ftplugin") @@ -31,7 +31,8 @@ if exists('&ofu') endif " Set 'comments' to format dashed lists in comments. -setlocal comments=sO:*\ -,mO:*\ \ ,exO:*/,s1:/*,mb:*,ex:*/,:// +" Also include ///, used for Doxygen. + setlocal comments=sO:*\ -,mO:*\ \ ,exO:*/,s1:/*,mb:*,ex:*/,:///,:// " When the matchit plugin is loaded, this makes the % command skip parens and " braces in comments properly. diff --git a/runtime/ftplugin/checkhealth.vim b/runtime/ftplugin/checkhealth.vim new file mode 100644 index 0000000000..3d8e9ace1a --- /dev/null +++ b/runtime/ftplugin/checkhealth.vim @@ -0,0 +1,20 @@ +" Vim filetype plugin +" Language: Neovim checkhealth buffer +" Last Change: 2021 Dec 15 + +if exists("b:did_ftplugin") + finish +endif + +runtime! ftplugin/markdown.vim ftplugin/markdown_*.vim ftplugin/markdown/*.vim + +setlocal wrap breakindent linebreak +setlocal conceallevel=2 concealcursor=nc +setlocal keywordprg=:help +let &l:iskeyword='!-~,^*,^|,^",192-255' + +if exists("b:undo_ftplugin") + let b:undo_ftplugin .= "|setl wrap< bri< lbr< cole< cocu< kp< isk<" +else + let b:undo_ftplugin = "setl wrap< bri< lbr< cole< cocu< kp< isk<" +endif diff --git a/runtime/ftplugin/clojure.vim b/runtime/ftplugin/clojure.vim index 81d53b1227..c922d75699 100644 --- a/runtime/ftplugin/clojure.vim +++ b/runtime/ftplugin/clojure.vim @@ -5,7 +5,7 @@ " Meikel Brandmeyer <mb@kotka.de> " URL: https://github.com/clojure-vim/clojure.vim " License: Vim (see :h license) -" Last Change: 2021-10-26 +" Last Change: 2022-03-24 if exists("b:did_ftplugin") finish @@ -43,7 +43,7 @@ setlocal commentstring=;\ %s " specially and hence are not indented specially. " " -*- LISPWORDS -*- -" Generated from https://github.com/clojure-vim/clojure.vim/blob/62b215f079ce0f3834fd295c7a7f6bd8cc54bcc3/clj/src/vim_clojure_static/generate.clj +" Generated from https://github.com/clojure-vim/clojure.vim/blob/fd280e33e84c88e97860930557dba3ff80b1a82d/clj/src/vim_clojure_static/generate.clj setlocal lispwords=as->,binding,bound-fn,case,catch,cond->,cond->>,condp,def,definline,definterface,defmacro,defmethod,defmulti,defn,defn-,defonce,defprotocol,defrecord,defstruct,deftest,deftest-,deftype,doseq,dotimes,doto,extend,extend-protocol,extend-type,fn,for,if,if-let,if-not,if-some,let,letfn,locking,loop,ns,proxy,reify,set-test,testing,when,when-first,when-let,when-not,when-some,while,with-bindings,with-in-str,with-local-vars,with-open,with-precision,with-redefs,with-redefs-fn,with-test " Provide insert mode completions for special forms and clojure.core. As @@ -66,10 +66,10 @@ endif " Filter files in the browse dialog if (has("gui_win32") || has("gui_gtk")) && !exists("b:browsefilter") - let b:browsefilter = "Clojure Source Files (*.clj)\t*.clj\n" . - \ "ClojureScript Source Files (*.cljs)\t*.cljs\n" . - \ "Java Source Files (*.java)\t*.java\n" . - \ "All Files (*.*)\t*.*\n" + let b:browsefilter = "All Files\t*\n" . + \ "Clojure Files\t*.clj;*.cljc;*.cljs;*.cljx\n" . + \ "EDN Files\t*.edn\n" . + \ "Java Files\t*.java\n" let b:undo_ftplugin .= ' | unlet! b:browsefilter' endif diff --git a/runtime/ftplugin/config.vim b/runtime/ftplugin/config.vim index 7fde42ebf5..73136cbc66 100644 --- a/runtime/ftplugin/config.vim +++ b/runtime/ftplugin/config.vim @@ -1,8 +1,10 @@ " Vim filetype plugin file " Language: config -" Maintainer: Dan Sharp <dwsharp at users dot sourceforge dot net> +" +" This runtime file is looking for a new maintainer. +" +" Former maintainer: Dan Sharp " Last Changed: 20 Jan 2009 -" URL: http://dwsharp.users.sourceforge.net/vim/ftplugin if exists("b:did_ftplugin") | finish | endif diff --git a/runtime/ftplugin/cpp.vim b/runtime/ftplugin/cpp.vim index f9d31cbec3..58c4e4b24a 100644 --- a/runtime/ftplugin/cpp.vim +++ b/runtime/ftplugin/cpp.vim @@ -10,6 +10,7 @@ endif " Behaves mostly just like C runtime! ftplugin/c.vim ftplugin/c_*.vim ftplugin/c/*.vim +runtime! ftplugin/c.lua ftplugin/c_*.lua ftplugin/c/*.lua " C++ uses templates with <things> " Disabled, because it gives an error for typing an unmatched ">". diff --git a/runtime/ftplugin/csc.vim b/runtime/ftplugin/csc.vim index 3a09c3bf8b..7b4126a503 100644 --- a/runtime/ftplugin/csc.vim +++ b/runtime/ftplugin/csc.vim @@ -1,8 +1,10 @@ " Vim filetype plugin file " Language: csc -" Maintainer: Dan Sharp <dwsharp at users dot sourceforge dot net> +" +" This runtime file is looking for a new maintainer. +" +" Former maintainer: Dan Sharp " Last Changed: 20 Jan 2009 -" URL: http://dwsharp.users.sourceforge.net/vim/ftplugin if exists("b:did_ftplugin") | finish | endif let b:did_ftplugin = 1 diff --git a/runtime/ftplugin/csh.vim b/runtime/ftplugin/csh.vim index 929823219c..ca5da5a8b9 100644 --- a/runtime/ftplugin/csh.vim +++ b/runtime/ftplugin/csh.vim @@ -1,7 +1,7 @@ " Vim filetype plugin file " Language: csh " Maintainer: Doug Kearns <dougkearns@gmail.com> -" Previous Maintainer: Dan Sharp <dwsharp at users dot sourceforge dot net> +" Previous Maintainer: Dan Sharp " Contributor: Johannes Zellner <johannes@zellner.org> " Last Change: 2021 Oct 15 diff --git a/runtime/ftplugin/dtd.vim b/runtime/ftplugin/dtd.vim index 6c08f6691d..a046118c70 100644 --- a/runtime/ftplugin/dtd.vim +++ b/runtime/ftplugin/dtd.vim @@ -1,8 +1,10 @@ " Vim filetype plugin file " Language: dtd -" Maintainer: Dan Sharp <dwsharp at users dot sourceforge dot net> +" +" This runtime file is looking for a new maintainer. +" +" Former maintainer: Dan Sharp " Last Changed: 20 Jan 2009 -" URL: http://dwsharp.users.sourceforge.net/vim/ftplugin if exists("b:did_ftplugin") | finish | endif let b:did_ftplugin = 1 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/html.vim b/runtime/ftplugin/html.vim index 7579080ea5..3179aa2e88 100644 --- a/runtime/ftplugin/html.vim +++ b/runtime/ftplugin/html.vim @@ -1,8 +1,10 @@ " Vim filetype plugin file " Language: html -" Maintainer: Dan Sharp <dwsharp at users dot sourceforge dot net> +" +" This runtime file is looking for a new maintainer. +" +" Former maintainer: Dan Sharp " Last Changed: 20 Jan 2009 -" URL: http://dwsharp.users.sourceforge.net/vim/ftplugin if exists("b:did_ftplugin") | finish | endif let b:did_ftplugin = 1 diff --git a/runtime/ftplugin/i3config.vim b/runtime/ftplugin/i3config.vim new file mode 100644 index 0000000000..4204510e64 --- /dev/null +++ b/runtime/ftplugin/i3config.vim @@ -0,0 +1,13 @@ +" Vim filetype plugin file +" Language: i3 config file +" Original Author: Mohamed Boughaba <mohamed dot bgb at gmail dot com> +" Maintainer: Quentin Hibon +" Version: 0.4 +" Last Change: 2021 Dec 14 + +if exists("b:did_ftplugin") | finish | endif +let b:did_ftplugin = 1 + +let b:undo_ftplugin = "setlocal cms<" + +setlocal commentstring=#\ %s diff --git a/runtime/ftplugin/indent.vim b/runtime/ftplugin/indent.vim index e6d928a073..64a650ad7b 100644 --- a/runtime/ftplugin/indent.vim +++ b/runtime/ftplugin/indent.vim @@ -1,7 +1,8 @@ " Vim filetype plugin file -" Language: indent(1) configuration file -" Previous Maintainer: Nikolai Weibull <now@bitwi.se> -" Latest Revision: 2008-07-09 +" Language: indent(1) configuration file +" Maintainer: Doug Kearns <dougkearns@gmail.com> +" Previous Maintainer: Nikolai Weibull <now@bitwi.se> +" Latest Revision: 2008-07-09 if exists("b:did_ftplugin") finish diff --git a/runtime/ftplugin/java.vim b/runtime/ftplugin/java.vim index 292cb6b166..74c8e8d1c1 100644 --- a/runtime/ftplugin/java.vim +++ b/runtime/ftplugin/java.vim @@ -1,8 +1,10 @@ " Vim filetype plugin file " Language: Java -" Maintainer: Dan Sharp <dwsharp at users dot sourceforge dot net> +" +" This runtime file is looking for a new maintainer. +" +" Former maintainer: Dan Sharp " Last Change: 2012 Mar 11 -" URL: http://dwsharp.users.sourceforge.net/vim/ftplugin if exists("b:did_ftplugin") | finish | endif let b:did_ftplugin = 1 diff --git a/runtime/ftplugin/jsonc.vim b/runtime/ftplugin/jsonc.vim index 90d52cd0d3..e47a75f574 100644 --- a/runtime/ftplugin/jsonc.vim +++ b/runtime/ftplugin/jsonc.vim @@ -4,7 +4,7 @@ " Acknowledgement: Based off of vim-jsonc maintained by Kevin Locke <kevin@kevinlocke.name> " https://github.com/kevinoid/vim-jsonc " License: MIT -" Last Change: 2021-07-01 +" Last Change: 2021 Nov 22 runtime! ftplugin/json.vim @@ -14,14 +14,8 @@ else let b:did_ftplugin_jsonc = 1 endif -" A list of commands that undo buffer local changes made below. -let s:undo_ftplugin = [] - " Set comment (formatting) related options. {{{1 setlocal commentstring=//%s comments=sO:*\ -,mO:*\ \ ,exO:*/,s1:/*,mb:*,ex:*/,:// -call add(s:undo_ftplugin, 'commentstring< comments<') " Let Vim know how to disable the plug-in. -call map(s:undo_ftplugin, "'execute ' . string(v:val)") -let b:undo_ftplugin = join(s:undo_ftplugin, ' | ') -unlet s:undo_ftplugin +let b:undo_ftplugin = 'setlocal commentstring< comments<' diff --git a/runtime/ftplugin/jsp.vim b/runtime/ftplugin/jsp.vim index fbba863b32..18136ccc24 100644 --- a/runtime/ftplugin/jsp.vim +++ b/runtime/ftplugin/jsp.vim @@ -1,8 +1,10 @@ " Vim filetype plugin file " Language: jsp -" Maintainer: Dan Sharp <dwsharp at users dot sourceforge dot net> +" +" This runtime file is looking for a new maintainer. +" +" Former maintainer: Dan Sharp " Last Changed: 20 Jan 2009 -" URL: http://dwsharp.users.sourceforge.net/vim/ftplugin if exists("b:did_ftplugin") | finish | endif diff --git a/runtime/ftplugin/liquid.vim b/runtime/ftplugin/liquid.vim index b211a884c6..f24ec4cbb2 100644 --- a/runtime/ftplugin/liquid.vim +++ b/runtime/ftplugin/liquid.vim @@ -1,7 +1,7 @@ " Vim filetype plugin " Language: Liquid " Maintainer: Tim Pope <vimNOSPAM@tpope.org> -" Last Change: 2010 May 21 +" Last Change: 2022 Mar 15 if exists('b:did_ftplugin') finish @@ -53,7 +53,7 @@ if has('gui_win32') endif if exists('loaded_matchit') - let b:match_words .= '\<\%(if\w*\|unless\|case\)\>:\<\%(elsif\|else\|when\)\>:\<end\%(if\w*\|unless\|case\)\>,\<\%(for\|tablerow\)\>:\%({%\s*\)\@<=empty\>:\<end\%(for\|tablerow\)\>,<\(capture\|comment\|highlight\)\>:\<end\1\>' + let b:match_words .= '\<\%(if\w*\|unless\|case\)\>:\<\%(elsif\|else\|when\)\>:\<end\%(if\w*\|unless\|case\)\>,\<\%(for\|tablerow\)\>:\%({%\s*\)\@<=empty\>:\<end\%(for\|tablerow\)\>,\<\(capture\|comment\|highlight\)\>:\<end\1\>' endif setlocal commentstring={%\ comment\ %}%s{%\ endcomment\ %} diff --git a/runtime/ftplugin/pascal.vim b/runtime/ftplugin/pascal.vim index 2de92563ae..aba1e54f27 100644 --- a/runtime/ftplugin/pascal.vim +++ b/runtime/ftplugin/pascal.vim @@ -1,7 +1,7 @@ " Vim filetype plugin file " Language: Pascal " Maintainer: Doug Kearns <dougkearns@gmail.com> -" Previous Maintainer: Dan Sharp <dwsharp at users dot sourceforge dot net> +" Previous Maintainer: Dan Sharp " Last Change: 2021 Apr 23 if exists("b:did_ftplugin") | finish | endif diff --git a/runtime/ftplugin/php.vim b/runtime/ftplugin/php.vim index a2f8b4d8d3..2824a5853b 100644 --- a/runtime/ftplugin/php.vim +++ b/runtime/ftplugin/php.vim @@ -1,8 +1,10 @@ " Vim filetype plugin file " Language: php -" Maintainer: Dan Sharp <dwsharp at users dot sourceforge dot net> +" +" This runtime file is looking for a new maintainer. +" +" Former maintainer: Dan Sharp " Last Changed: 20 Jan 2009 -" URL: http://dwsharp.users.sourceforge.net/vim/ftplugin if exists("b:did_ftplugin") | finish | endif @@ -71,10 +73,11 @@ exe 'nno <buffer> <silent> ]] /' . escape(s:section, '|') . '/<CR>:nohls<CR>' exe 'ono <buffer> <silent> [[ ?' . escape(s:section, '|') . '?<CR>:nohls<CR>' exe 'ono <buffer> <silent> ]] /' . escape(s:section, '|') . '/<CR>:nohls<CR>' +setlocal suffixesadd=.php setlocal commentstring=/*%s*/ " Undo the stuff we changed. -let b:undo_ftplugin = "setlocal commentstring< include< omnifunc<" . +let b:undo_ftplugin = "setlocal suffixesadd< commentstring< include< omnifunc<" . \ " | unlet! b:browsefilter b:match_words | " . \ s:undo_ftplugin 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/ftplugin/query.lua b/runtime/ftplugin/query.lua new file mode 100644 index 0000000000..c1694961af --- /dev/null +++ b/runtime/ftplugin/query.lua @@ -0,0 +1,6 @@ +-- Neovim filetype plugin file +-- Language: Tree-sitter query +-- Last Change: 2022 Mar 29 + +-- it's a lisp! +vim.cmd [[ runtime! ftplugin/lisp.vim ]] diff --git a/runtime/ftplugin/ruby.vim b/runtime/ftplugin/ruby.vim index 4a476fd8cf..8c1f47731c 100644 --- a/runtime/ftplugin/ruby.vim +++ b/runtime/ftplugin/ruby.vim @@ -3,7 +3,7 @@ " Maintainer: Tim Pope <vimNOSPAM@tpope.org> " URL: https://github.com/vim-ruby/vim-ruby " Release Coordinator: Doug Kearns <dougkearns@gmail.com> -" Last Change: 2020 Feb 13 +" Last Change: 2022 Mar 21 if (exists("b:did_ftplugin")) finish @@ -53,7 +53,7 @@ endif " TODO: "setlocal define=^\\s*def -setlocal comments=:# +setlocal comments=b:# setlocal commentstring=#\ %s if !exists('g:ruby_version_paths') @@ -87,8 +87,14 @@ endfunction function! s:build_path(path) abort let path = join(map(copy(a:path), 'v:val ==# "." ? "" : v:val'), ',') - if &g:path !~# '\v^%(\.,)=%(/%(usr|emx)/include,)=,$' - let path = substitute(&g:path,',,$',',','') . ',' . path + if &g:path =~# '\v^%(\.,)=%(/%(usr|emx)/include,)=,$' + let path = path . ',.,,' + elseif &g:path =~# ',\.,,$' + let path = &g:path[0:-4] . path . ',.,,' + elseif &g:path =~# ',,$' + let path = &g:path[0:-2] . path . ',,' + else + let path = substitute(&g:path, '[^,]\zs$', ',', '') . path endif return path endfunction @@ -164,6 +170,8 @@ let b:undo_ftplugin .= "| sil! cunmap <buffer> <Plug><ctag>| sil! cunmap <buffer if !exists("g:no_plugin_maps") && !exists("g:no_ruby_maps") nmap <buffer><script> <SID>: :<C-U> nmap <buffer><script> <SID>c: :<C-U><C-R>=v:count ? v:count : ''<CR> + cmap <buffer> <SID><cfile> <Plug><cfile> + cmap <buffer> <SID><ctag> <Plug><ctag> nnoremap <silent> <buffer> [m :<C-U>call <SID>searchsyn('\<def\>',['rubyDefine'],'b','n')<CR> nnoremap <silent> <buffer> ]m :<C-U>call <SID>searchsyn('\<def\>',['rubyDefine'],'','n')<CR> @@ -210,20 +218,20 @@ if !exists("g:no_plugin_maps") && !exists("g:no_ruby_maps") call s:map('c', '', '<C-R><C-F> <Plug><cfile>') cmap <buffer><script><expr> <SID>tagzv &foldopen =~# 'tag' ? '<Bar>norm! zv' : '' - call s:map('n', '<silent>', '<C-]> <SID>:exe v:count1."tag <Plug><ctag>"<SID>tagzv<CR>') - call s:map('n', '<silent>', 'g<C-]> <SID>:exe "tjump <Plug><ctag>"<SID>tagzv<CR>') - call s:map('n', '<silent>', 'g] <SID>:exe "tselect <Plug><ctag>"<SID>tagzv<CR>') - call s:map('n', '<silent>', '<C-W>] <SID>:exe v:count1."stag <Plug><ctag>"<SID>tagzv<CR>') - call s:map('n', '<silent>', '<C-W><C-]> <SID>:exe v:count1."stag <Plug><ctag>"<SID>tagzv<CR>') - call s:map('n', '<silent>', '<C-W>g<C-]> <SID>:exe "stjump <Plug><ctag>"<SID>tagzv<CR>') - call s:map('n', '<silent>', '<C-W>g] <SID>:exe "stselect <Plug><ctag>"<SID>tagzv<CR>') - call s:map('n', '<silent>', '<C-W>} <SID>:exe v:count1."ptag <Plug><ctag>"<CR>') - call s:map('n', '<silent>', '<C-W>g} <SID>:exe "ptjump <Plug><ctag>"<CR>') - - call s:map('n', '<silent>', 'gf <SID>c:find <Plug><cfile><CR>') - call s:map('n', '<silent>', '<C-W>f <SID>c:sfind <Plug><cfile><CR>') - call s:map('n', '<silent>', '<C-W><C-F> <SID>c:sfind <Plug><cfile><CR>') - call s:map('n', '<silent>', '<C-W>gf <SID>c:tabfind <Plug><cfile><CR>') + call s:map('n', '<script><silent>', '<C-]> <SID>:exe v:count1."tag <SID><ctag>"<SID>tagzv<CR>') + call s:map('n', '<script><silent>', 'g<C-]> <SID>:exe "tjump <SID><ctag>"<SID>tagzv<CR>') + call s:map('n', '<script><silent>', 'g] <SID>:exe "tselect <SID><ctag>"<SID>tagzv<CR>') + call s:map('n', '<script><silent>', '<C-W>] <SID>:exe v:count1."stag <SID><ctag>"<SID>tagzv<CR>') + call s:map('n', '<script><silent>', '<C-W><C-]> <SID>:exe v:count1."stag <SID><ctag>"<SID>tagzv<CR>') + call s:map('n', '<script><silent>', '<C-W>g<C-]> <SID>:exe "stjump <SID><ctag>"<SID>tagzv<CR>') + call s:map('n', '<script><silent>', '<C-W>g] <SID>:exe "stselect <SID><ctag>"<SID>tagzv<CR>') + call s:map('n', '<script><silent>', '<C-W>} <SID>:exe v:count1."ptag <SID><ctag>"<CR>') + call s:map('n', '<script><silent>', '<C-W>g} <SID>:exe "ptjump <SID><ctag>"<CR>') + + call s:map('n', '<script><silent>', 'gf <SID>c:find <SID><cfile><CR>') + call s:map('n', '<script><silent>', '<C-W>f <SID>c:sfind <SID><cfile><CR>') + call s:map('n', '<script><silent>', '<C-W><C-F> <SID>c:sfind <SID><cfile><CR>') + call s:map('n', '<script><silent>', '<C-W>gf <SID>c:tabfind <SID><cfile><CR>') endif let &cpo = s:cpo_save diff --git a/runtime/ftplugin/sgml.vim b/runtime/ftplugin/sgml.vim index bf63efbf1f..ef52125c68 100644 --- a/runtime/ftplugin/sgml.vim +++ b/runtime/ftplugin/sgml.vim @@ -1,8 +1,10 @@ " Vim filetype plugin file " Language: sgml -" Maintainer: Dan Sharp <dwsharp at users dot sourceforge dot net> +" +" This runtime file is looking for a new maintainer. +" +" Former maintainer: Dan Sharp " Last Changed: 20 Jan 2009 -" URL: http://dwsharp.users.sourceforge.net/vim/ftplugin if exists("b:did_ftplugin") | finish | endif diff --git a/runtime/ftplugin/sh.vim b/runtime/ftplugin/sh.vim index 593fcec927..93a46f63e2 100644 --- a/runtime/ftplugin/sh.vim +++ b/runtime/ftplugin/sh.vim @@ -1,8 +1,10 @@ " Vim filetype plugin file " Language: sh -" Maintainer: Dan Sharp <dwsharp at users dot sourceforge dot net> +" +" This runtime file is looking for a new maintainer. +" +" Former maintainer: Dan Sharp " Last Changed: 20 Jan 2009 -" URL: http://dwsharp.users.sourceforge.net/vim/ftplugin if exists("b:did_ftplugin") | finish | endif let b:did_ftplugin = 1 diff --git a/runtime/ftplugin/solution.vim b/runtime/ftplugin/solution.vim new file mode 100644 index 0000000000..bd30c7bb19 --- /dev/null +++ b/runtime/ftplugin/solution.vim @@ -0,0 +1,37 @@ +" Vim filetype plugin file +" Language: Microsoft Visual Studio Solution +" Maintainer: Doug Kearns <dougkearns@gmail.com> +" Last Change: 2021 Dec 15 + +if exists("b:did_ftplugin") + finish +endif +let b:did_ftplugin = 1 + +let s:cpo_save = &cpo +set cpo&vim + +setlocal comments=:# +setlocal commentstring=#\ %s + +let b:undo_ftplugin = "setl com< cms<" + +if exists("loaded_matchit") && !exists("b:match_words") + let b:match_words = + \ '\<Project\>:\<EndProject\>,' .. + \ '\<ProjectSection\>:\<EndProjectSection\>,' .. + \ '\<Global\>:\<EndGlobal\>,' .. + \ '\<GlobalSection\>:\<EndGlobalSection\>' + let b:undo_ftplugin ..= " | unlet! b:match_words" +endif + +if (has("gui_win32") || has("gui_gtk")) && !exists("b:browsefilter") + let b:browsefilter = "Microsoft Visual Studio Solution Files\t*.sln\n" .. + \ "All Files (*.*)\t*.*\n" + let b:undo_ftplugin ..= " | unlet! b:browsefilter" +endif + +let &cpo = s:cpo_save +unlet s:cpo_save + +" vim: nowrap sw=2 sts=2 ts=8 noet: diff --git a/runtime/ftplugin/svg.vim b/runtime/ftplugin/svg.vim index 8fff6ea32c..6f16b1a0f4 100644 --- a/runtime/ftplugin/svg.vim +++ b/runtime/ftplugin/svg.vim @@ -1,8 +1,10 @@ " Vim filetype plugin file " Language: svg -" Maintainer: Dan Sharp <dwsharp at users dot sourceforge dot net> +" +" This runtime file is looking for a new maintainer. +" +" Former maintainer: Dan Sharp " Last Changed: 20 Jan 2009 -" URL: http://dwsharp.users.sourceforge.net/vim/ftplugin if exists("b:did_ftplugin") | finish | endif diff --git a/runtime/ftplugin/tcsh.vim b/runtime/ftplugin/tcsh.vim index 33f1aabf68..85d3873b33 100644 --- a/runtime/ftplugin/tcsh.vim +++ b/runtime/ftplugin/tcsh.vim @@ -1,7 +1,7 @@ " Vim filetype plugin file " Language: tcsh " Maintainer: Doug Kearns <dougkearns@gmail.com> -" Previous Maintainer: Dan Sharp <dwsharp at users dot sourceforge dot net> +" Previous Maintainer: Dan Sharp " Last Change: 2021 Oct 15 if exists("b:did_ftplugin") | finish | endif diff --git a/runtime/ftplugin/vb.vim b/runtime/ftplugin/vb.vim index d70db89273..5a9548115b 100644 --- a/runtime/ftplugin/vb.vim +++ b/runtime/ftplugin/vb.vim @@ -1,44 +1,70 @@ " Vim filetype plugin file -" Language: VisualBasic (ft=vb) -" Maintainer: Johannes Zellner <johannes@zellner.org> -" Last Change: Thu, 22 Nov 2001 12:56:14 W. Europe Standard Time +" Language: Visual Basic (ft=vb) +" Maintainer: Doug Kearns <dougkearns@gmail.com> +" Previous Maintainer: Johannes Zellner <johannes@zellner.org> +" Last Change: 2021 Nov 17 -if exists("b:did_ftplugin") | finish | endif +if exists("b:did_ftplugin") + finish +endif let b:did_ftplugin = 1 -setlocal com=sr:'\ -,mb:'\ \ ,el:'\ \ ,:' +let s:cpo_save = &cpo +set cpo&vim + +setlocal comments=sr:'\ -,mb:'\ \ ,el:'\ \ ,:' +setlocal commentstring='\ %s +setlocal formatoptions-=t formatoptions+=croql + +let b:undo_ftplugin = "setlocal com< cms< fo<" " we need this wrapper, as call doesn't allow a count -fun! <SID>VbSearch(pattern, flags) +function! s:VbSearch(pattern, flags) let cnt = v:count1 while cnt > 0 call search(a:pattern, a:flags) let cnt = cnt - 1 endwhile -endfun +endfunction -let s:cpo_save = &cpo -set cpo&vim +if !exists("no_plugin_maps") && !exists("no_vb_maps") + nnoremap <buffer> <silent> [[ <Cmd>call <SID>VbSearch('^\s*\%(\%(private\<Bar>public\)\s\+\)\=\%(function\<Bar>sub\)', 'sbW')<CR> + vnoremap <buffer> <silent> [[ <Cmd>call <SID>VbSearch('^\s*\%(\%(private\<Bar>public\)\s\+\)\=\%(function\<Bar>sub\)', 'sbW')<CR> + nnoremap <buffer> <silent> ]] <Cmd>call <SID>VbSearch('^\s*\%(\%(private\<Bar>public\)\s\+\)\=\%(function\<Bar>sub\)', 'sW')<CR> + vnoremap <buffer> <silent> ]] <Cmd>call <SID>VbSearch('^\s*\%(\%(private\<Bar>public\)\s\+\)\=\%(function\<Bar>sub\)', 'sW')<CR> + nnoremap <buffer> <silent> [] <Cmd>call <SID>VbSearch('^\s*end\s\+\%(function\<Bar>sub\)', 'sbW')<CR> + vnoremap <buffer> <silent> [] <Cmd>call <SID>VbSearch('^\s*end\s\+\%(function\<Bar>sub\)', 'sbW')<CR> + nnoremap <buffer> <silent> ][ <Cmd>call <SID>VbSearch('^\s*end\s\+\%(function\<Bar>sub\)', 'sW')<CR> + vnoremap <buffer> <silent> ][ <Cmd>call <SID>VbSearch('^\s*end\s\+\%(function\<Bar>sub\)', 'sW')<CR> + let b:undo_ftplugin .= " | sil! exe 'nunmap <buffer> [[' | sil! exe 'vunmap <buffer> [['" . + \ " | sil! exe 'nunmap <buffer> ]]' | sil! exe 'vunmap <buffer> ]]'" . + \ " | sil! exe 'nunmap <buffer> []' | sil! exe 'vunmap <buffer> []'" . + \ " | sil! exe 'nunmap <buffer> ][' | sil! exe 'vunmap <buffer> ]['" +endif + +" TODO: line start anchors are almost certainly overly restrictive - allow +" after statement separators. Even in QuickBasic only block IF statements +" were required to be at the start of a line. +if exists("loaded_matchit") && !exists("b:match_words") + let b:match_ignorecase = 1 + let b:match_words = + \ '\%(^\s*\)\@<=\<if\>.*\<then\>\s*\%($\|''\):\%(^\s*\)\@<=\<else\>:\%(^\s*\)\@<=\<elseif\>:\%(^\s*\)\@<=\<end\>\s\+\<if\>,' . + \ '\%(^\s*\)\@<=\<for\>:\%(^\s*\)\@<=\<next\>,' . + \ '\%(^\s*\)\@<=\<while\>:\%(^\s*\)\@<=\<wend\>,' . + \ '\%(^\s*\)\@<=\<do\>:\%(^\s*\)\@<=\<loop\>\s\+\<while\>,' . + \ '\%(^\s*\)\@<=\<select\>\s\+\<case\>:\%(^\s*\)\@<=\<case\>:\%(^\s*\)\@<=\<end\>\s\+\<select\>,' . + \ '\%(^\s*\)\@<=\<enum\>:\%(^\s*\)\@<=\<end\>\s\<enum\>,' . + \ '\%(^\s*\)\@<=\<with\>:\%(^\s*\)\@<=\<end\>\s\<with\>,' . + \ '\%(^\s*\)\@<=\%(\<\%(private\|public\)\>\s\+\)\=\<function\>\s\+\([^ \t(]\+\):\%(^\s*\)\@<=\<\1\>\s*=:\%(^\s*\)\@<=\<end\>\s\+\<function\>,' . + \ '\%(^\s*\)\@<=\%(\<\%(private\|public\)\>\s\+\)\=\<sub\>\s\+:\%(^\s*\)\@<=\<end\>\s\+\<sub\>' + let b:undo_ftplugin .= " | unlet! b:match_words b:match_ignorecase" +endif -" NOTE the double escaping \\| -nnoremap <buffer> <silent> [[ :call <SID>VbSearch('^\s*\(\(private\|public\)\s\+\)\=\(function\\|sub\)', 'bW')<cr> -nnoremap <buffer> <silent> ]] :call <SID>VbSearch('^\s*\(\(private\|public\)\s\+\)\=\(function\\|sub\)', 'W')<cr> -nnoremap <buffer> <silent> [] :call <SID>VbSearch('^\s*\<end\>\s\+\(function\\|sub\)', 'bW')<cr> -nnoremap <buffer> <silent> ][ :call <SID>VbSearch('^\s*\<end\>\s\+\(function\\|sub\)', 'W')<cr> - -" matchit support -if exists("loaded_matchit") - let b:match_ignorecase=1 - let b:match_words= - \ '\%(^\s*\)\@<=\<if\>.*\<then\>\s*$:\%(^\s*\)\@<=\<else\>:\%(^\s*\)\@<=\<elseif\>:\%(^\s*\)\@<=\<end\>\s\+\<if\>,' . - \ '\%(^\s*\)\@<=\<for\>:\%(^\s*\)\@<=\<next\>,' . - \ '\%(^\s*\)\@<=\<while\>:\%(^\s*\)\@<=\<wend\>,' . - \ '\%(^\s*\)\@<=\<do\>:\%(^\s*\)\@<=\<loop\>\s\+\<while\>,' . - \ '\%(^\s*\)\@<=\<select\>\s\+\<case\>:\%(^\s*\)\@<=\<case\>:\%(^\s*\)\@<=\<end\>\s\+\<select\>,' . - \ '\%(^\s*\)\@<=\<enum\>:\%(^\s*\)\@<=\<end\>\s\<enum\>,' . - \ '\%(^\s*\)\@<=\<with\>:\%(^\s*\)\@<=\<end\>\s\<with\>,' . - \ '\%(^\s*\)\@<=\%(\<\%(private\|public\)\>\s\+\)\=\<function\>\s\+\([^ \t(]\+\):\%(^\s*\)\@<=\<\1\>\s*=:\%(^\s*\)\@<=\<end\>\s\+\<function\>,' . - \ '\%(^\s*\)\@<=\%(\<\%(private\|public\)\>\s\+\)\=\<sub\>\s\+:\%(^\s*\)\@<=\<end\>\s\+\<sub\>' +if (has("gui_win32") || has("gui_gtk")) && !exists("b:browsefilter") + let b:browsefilter = "Visual Basic Source Files (*.bas)\t*.bas\n" . + \ "Visual Basic Form Files (*.frm)\t*.frm\n" . + \ "All Files (*.*)\t*.*\n" + let b:undo_ftplugin .= " | unlet! b:browsefilter" endif let &cpo = s:cpo_save diff --git a/runtime/ftplugin/xhtml.vim b/runtime/ftplugin/xhtml.vim index 21ed3e1100..d2a1c0b566 100644 --- a/runtime/ftplugin/xhtml.vim +++ b/runtime/ftplugin/xhtml.vim @@ -1,8 +1,10 @@ " Vim filetype plugin file " Language: xhtml -" Maintainer: Dan Sharp <dwsharp at users dot sourceforge dot net> +" +" This runtime file is looking for a new maintainer. +" +" Former maintainer: Dan Sharp " Last Changed: 20 Jan 2009 -" URL: http://dwsharp.users.sourceforge.net/vim/ftplugin if exists("b:did_ftplugin") | finish | endif diff --git a/runtime/ftplugin/xml.vim b/runtime/ftplugin/xml.vim index 1d43521155..9aa188cecc 100644 --- a/runtime/ftplugin/xml.vim +++ b/runtime/ftplugin/xml.vim @@ -3,7 +3,7 @@ " Maintainer: Christian Brabandt <cb@256bit.org> " Last Changed: Dec 07th, 2018 " Repository: https://github.com/chrisbra/vim-xml-ftplugin -" Previous Maintainer: Dan Sharp <dwsharp at users dot sourceforge dot net> +" Previous Maintainer: Dan Sharp " URL: http://dwsharp.users.sourceforge.net/vim/ftplugin if exists("b:did_ftplugin") | finish | endif diff --git a/runtime/ftplugin/xsd.vim b/runtime/ftplugin/xsd.vim index 6a4a193656..7d3efbb390 100644 --- a/runtime/ftplugin/xsd.vim +++ b/runtime/ftplugin/xsd.vim @@ -1,8 +1,10 @@ " Vim filetype plugin file " Language: xsd -" Maintainer: Dan Sharp <dwsharp at users dot sourceforge dot net> +" +" This runtime file is looking for a new maintainer. +" +" Former maintainer: Dan Sharp " Last Changed: 20 Jan 2009 -" URL: http://dwsharp.users.sourceforge.net/vim/ftplugin if exists("b:did_ftplugin") | finish | endif diff --git a/runtime/ftplugin/xslt.vim b/runtime/ftplugin/xslt.vim index 1a5ee62865..9d2def107b 100644 --- a/runtime/ftplugin/xslt.vim +++ b/runtime/ftplugin/xslt.vim @@ -1,8 +1,10 @@ " Vim filetype plugin file " Language: xslt -" Maintainer: Dan Sharp <dwsharp at users dot sourceforge dot net> +" +" This runtime file is looking for a new maintainer. +" +" Former maintainer: Dan Sharp " Last Changed: 20 Jan 2009 -" URL: http://dwsharp.users.sourceforge.net/vim/ftplugin if exists("b:did_ftplugin") | finish | endif diff --git a/runtime/ftplugin/zsh.vim b/runtime/ftplugin/zsh.vim index 53ce1417dd..34410f1c62 100644 --- a/runtime/ftplugin/zsh.vim +++ b/runtime/ftplugin/zsh.vim @@ -18,13 +18,13 @@ setlocal comments=:# commentstring=#\ %s formatoptions-=t formatoptions+=croql let b:undo_ftplugin = "setl com< cms< fo< " -if executable('zsh') +if executable('zsh') && &shell !~# '/\%(nologin\|false\)$' if !has('gui_running') && executable('less') - command! -buffer -nargs=1 RunHelp silent exe '!MANPAGER= zsh -ic "autoload -Uz run-help; run-help <args> 2>/dev/null | LESS= less"' | redraw! + command! -buffer -nargs=1 RunHelp silent exe '!MANPAGER= zsh -c "autoload -Uz run-help; run-help <args> 2>/dev/null | LESS= less"' | redraw! elseif has('terminal') - command! -buffer -nargs=1 RunHelp silent exe ':term zsh -ic "autoload -Uz run-help; run-help <args>"' + command! -buffer -nargs=1 RunHelp silent exe ':term zsh -c "autoload -Uz run-help; run-help <args>"' else - command! -buffer -nargs=1 RunHelp echo system('zsh -ic "autoload -Uz run-help; run-help <args> 2>/dev/null"') + command! -buffer -nargs=1 RunHelp echo system('zsh -c "autoload -Uz run-help; run-help <args> 2>/dev/null"') endif if !exists('current_compiler') compiler zsh diff --git a/runtime/indent/ada.vim b/runtime/indent/ada.vim index 6c8ab05267..582d033b23 100644 --- a/runtime/indent/ada.vim +++ b/runtime/indent/ada.vim @@ -16,6 +16,7 @@ " 15.10.2006 MK Bram's suggestion for runtime integration " 05.11.2006 MK Bram suggested to save on spaces " 19.09.2007 NO g: missing before ada#Comment +" 2022 April: b:undo_indent added by Doug Kearns " Help Page: ft-vim-indent "------------------------------------------------------------------------------ " ToDo: @@ -35,6 +36,8 @@ setlocal indentexpr=GetAdaIndent() setlocal indentkeys-=0{,0} setlocal indentkeys+=0=~then,0=~end,0=~elsif,0=~when,0=~exception,0=~begin,0=~is,0=~record +let b:undo_indent = "setl inde< indk<" + " Only define the functions once. if exists("*GetAdaIndent") finish diff --git a/runtime/indent/awk.vim b/runtime/indent/awk.vim index e65331977c..cf8132241c 100644 --- a/runtime/indent/awk.vim +++ b/runtime/indent/awk.vim @@ -24,6 +24,7 @@ " 29-04-2002 Fixed problems in function headers and max line width " Added support for two-line if's without curly braces " Fixed hang: 2011 Aug 31 +" 2022 April: b:undo_indent added by Doug Kearns " Only load this indent file when no other was loaded. if exists("b:did_indent") @@ -36,6 +37,8 @@ setlocal indentexpr=GetAwkIndent() " Mmm, copied from the tcl indent program. Is this okay? setlocal indentkeys-=:,0# +let b:undo_indent = "setl inde< indk<" + " Only define the function once. if exists("*GetAwkIndent") finish 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/bst.vim b/runtime/indent/bst.vim index 47e3058810..3dd8d711a8 100644 --- a/runtime/indent/bst.vim +++ b/runtime/indent/bst.vim @@ -1,20 +1,18 @@ " Vim indent file " Language: bst " Author: Tim Pope <vimNOSPAM@tpope.info> -" $Id: bst.vim,v 1.1 2007/05/05 18:11:12 vimboss Exp $ +" Last Change: 2022 Mar 15 if exists("b:did_indent") finish endif let b:did_indent = 1 -setlocal expandtab setlocal indentexpr=GetBstIndent(v:lnum) -"setlocal smartindent setlocal cinkeys& setlocal cinkeys-=0# setlocal indentkeys& -"setlocal indentkeys+=0% +let b:undo_indent = 'setlocal indentexpr< cinkeys< indentkeys<' " Only define the function once. if exists("*GetBstIndent") diff --git a/runtime/indent/cdl.vim b/runtime/indent/cdl.vim index 0e3c6152b0..2c0fc7988e 100644 --- a/runtime/indent/cdl.vim +++ b/runtime/indent/cdl.vim @@ -1,7 +1,7 @@ " Description: Comshare Dimension Definition Language (CDL) " Maintainer: Raul Segura Acevedo <raulseguraaceved@netscape.net> (Invalid email address) " Doug Kearns <dougkearns@gmail.com> -" Last Change: Fri Nov 30 13:35:48 2001 CST +" Last Change: 2022 Apr 06 if exists("b:did_indent") "finish @@ -12,6 +12,8 @@ setlocal indentexpr=CdlGetIndent(v:lnum) setlocal indentkeys& setlocal indentkeys+==~else,=~endif,=~then,;,),= +let b:undo_indent = "setl inde< indk<" + " Only define the function once. if exists("*CdlGetIndent") "finish diff --git a/runtime/indent/chaiscript.vim b/runtime/indent/chaiscript.vim index 445281cc46..b7a3fe5896 100644 --- a/runtime/indent/chaiscript.vim +++ b/runtime/indent/chaiscript.vim @@ -1,6 +1,7 @@ " Vim indent file " Language: ChaiScript " Maintainer: Jason Turner <lefticus 'at' gmail com> +" Last Change: 2022 Apr 06 " Only load this indent file when no other was loaded. if exists("b:did_indent") @@ -11,6 +12,8 @@ let b:did_indent = 1 setlocal indentexpr=GetChaiScriptIndent() setlocal autoindent +let b:undo_indent = "setl ai< inde<" + " Only define the function once. if exists("*GetChaiScriptIndent") finish diff --git a/runtime/indent/clojure.vim b/runtime/indent/clojure.vim index fadcaf4b4a..5bfbfbb197 100644 --- a/runtime/indent/clojure.vim +++ b/runtime/indent/clojure.vim @@ -5,7 +5,7 @@ " Meikel Brandmeyer <mb@kotka.de> " URL: https://github.com/clojure-vim/clojure.vim " License: Vim (see :h license) -" Last Change: 2021-10-26 +" Last Change: 2022-03-24 if exists("b:did_indent") finish diff --git a/runtime/indent/cmake.vim b/runtime/indent/cmake.vim index 845bdd7655..af27c0d49b 100644 --- a/runtime/indent/cmake.vim +++ b/runtime/indent/cmake.vim @@ -3,7 +3,7 @@ " Author: Andy Cedilnik <andy.cedilnik@kitware.com> " Maintainer: Dimitri Merejkowsky <d.merej@gmail.com> " Former Maintainer: Karthik Krishnan <karthik.krishnan@kitware.com> -" Last Change: 2017 Sep 24 +" Last Change: 2022 Apr 06 " " Licence: The CMake license applies to this file. See " https://cmake.org/licensing @@ -17,6 +17,8 @@ let b:did_indent = 1 setlocal indentexpr=CMakeGetIndent(v:lnum) setlocal indentkeys+==ENDIF(,ENDFOREACH(,ENDMACRO(,ELSE(,ELSEIF(,ENDWHILE( +let b:undo_indent = "setl inde< indk<" + " Only define the function once. if exists("*CMakeGetIndent") finish diff --git a/runtime/indent/d.vim b/runtime/indent/d.vim index 57f9125890..80c9a2f559 100644 --- a/runtime/indent/d.vim +++ b/runtime/indent/d.vim @@ -2,7 +2,7 @@ " Language: D " Maintainer: Jason Mills <jmills@cs.mun.ca> (Invalid email address) " Doug Kearns <dougkearns@gmail.com> -" Last Change: 2005 Nov 22 +" Last Change: 2022 Apr 06 " Version: 0.1 " " Please email me with bugs, comments, and suggestion. Put vim in the subject @@ -19,4 +19,6 @@ let b:did_indent = 1 " D indenting is a lot like the built-in C indenting. setlocal cindent +let b:undo_indent = "setl cin<" + " vim: ts=8 noet diff --git a/runtime/indent/dictconf.vim b/runtime/indent/dictconf.vim index 2e15c76146..fa40585a92 100644 --- a/runtime/indent/dictconf.vim +++ b/runtime/indent/dictconf.vim @@ -1,7 +1,7 @@ " Vim indent file " Language: dict(1) configuration file " Previous Maintainer: Nikolai Weibull <now@bitwi.se> -" Latest Revision: 2006-12-20 +" Last Change: 2022 Apr 06 if exists("b:did_indent") finish @@ -11,3 +11,5 @@ let b:did_indent = 1 setlocal indentkeys=0{,0},!^F,o,O cinwords= autoindent smartindent setlocal nosmartindent inoremap <buffer> # X# + +let b:undo_indent = "setl ai< cinw< indk< si< | silent! iunmap <buffer> #" diff --git a/runtime/indent/dictdconf.vim b/runtime/indent/dictdconf.vim index 5c4fbdafb5..5c0e7c566c 100644 --- a/runtime/indent/dictdconf.vim +++ b/runtime/indent/dictdconf.vim @@ -11,3 +11,5 @@ let b:did_indent = 1 setlocal indentkeys=0{,0},!^F,o,O cinwords= autoindent smartindent setlocal nosmartindent inoremap <buffer> # X# + +let b:undo_indent = "setl ai< cinw< indk< si< | silent! iunmap <buffer> #" diff --git a/runtime/indent/dylan.vim b/runtime/indent/dylan.vim index 55255ddfa9..e2a6d1039c 100644 --- a/runtime/indent/dylan.vim +++ b/runtime/indent/dylan.vim @@ -3,7 +3,7 @@ " Maintainer: Brent A. Fulgham <bfulgham@debian.org> (Invalid email address) " Doug Kearns <dougkearns@gmail.com> " Version: 0.01 -" Last Change: 2017 Jun 13 +" Last Change: 2022 Apr 06 " Only load this indent file when no other was loaded. if exists("b:did_indent") @@ -15,6 +15,9 @@ setlocal indentkeys+==~begin,=~block,=~case,=~cleanup,=~define,=~end,=~else,=~el " Define the appropriate indent function but only once setlocal indentexpr=DylanGetIndent() + +let b:undo_indent = "setl inde< indk<" + if exists("*DylanGetIndent") finish endif diff --git a/runtime/indent/falcon.vim b/runtime/indent/falcon.vim index 664ad61aa5..a58ccad870 100644 --- a/runtime/indent/falcon.vim +++ b/runtime/indent/falcon.vim @@ -3,6 +3,7 @@ " Maintainer: Steven Oliver <oliver.steven@gmail.com> " Website: https://steveno@github.com/steveno/falconpl-vim.git " Credits: This is, to a great extent, a copy n' paste of ruby.vim. +" 2022 April: b:undo_indent added by Doug Kearns " 1. Setup {{{1 " ============ @@ -20,6 +21,8 @@ setlocal indentexpr=FalconGetIndent(v:lnum) setlocal indentkeys=0{,0},0),0],!^F,o,O,e setlocal indentkeys+==~case,=~catch,=~default,=~elif,=~else,=~end,=~\" +let b:undo_indent = "setl inde< indk< si<" + " Define the appropriate indent function but only once if exists("*FalconGetIndent") finish 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/gitolite.vim b/runtime/indent/gitolite.vim index b36f30a494..22be6872cb 100644 --- a/runtime/indent/gitolite.vim +++ b/runtime/indent/gitolite.vim @@ -4,7 +4,7 @@ " (https://raw.githubusercontent.com/sitaramc/gitolite/master/contrib/vim/indent/gitolite.vim) " Maintainer: Sitaram Chamarty <sitaramc@gmail.com> " (former Maintainer: Teemu Matilainen <teemu.matilainen@iki.fi>) -" Last Change: 2017 Oct 05 +" Last Change: 2022 Apr 06 if exists("b:did_indent") finish @@ -15,6 +15,8 @@ setlocal autoindent setlocal indentexpr=GetGitoliteIndent() setlocal indentkeys=o,O,*<Return>,!^F,=repo,\",= +let b:undo_indent = "setl ai< inde< indk<" + " Only define the function once. if exists("*GetGitoliteIndent") finish diff --git a/runtime/indent/haml.vim b/runtime/indent/haml.vim index baca1d49d9..acd99d9c7d 100644 --- a/runtime/indent/haml.vim +++ b/runtime/indent/haml.vim @@ -1,7 +1,7 @@ " Vim indent file " Language: Haml " Maintainer: Tim Pope <vimNOSPAM@tpope.org> -" Last Change: 2019 Dec 05 +" Last Change: 2022 Mar 15 if exists("b:did_indent") finish @@ -14,6 +14,8 @@ setlocal autoindent setlocal indentexpr=GetHamlIndent() setlocal indentkeys=o,O,*<Return>,},],0),!^F,=end,=else,=elsif,=rescue,=ensure,=when +let b:undo_indent = "setl ai< inde< indk<" + " Only define the function once. if exists("*GetHamlIndent") finish 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/idlang.vim b/runtime/indent/idlang.vim index e6a1d73775..1519865ab5 100644 --- a/runtime/indent/idlang.vim +++ b/runtime/indent/idlang.vim @@ -2,7 +2,7 @@ " Language: IDL (ft=idlang) " Maintainer: Aleksandar Jelenak <ajelenak AT yahoo.com> (Invalid email address) " Doug Kearns <dougkearns@gmail.com> -" Last change: 2017 Jun 13 +" Last change: 2022 Apr 06 " Only load this indent file when no other was loaded. if exists("b:did_indent") @@ -14,6 +14,8 @@ setlocal indentkeys=o,O,0=endif,0=ENDIF,0=endelse,0=ENDELSE,0=endwhile,0=ENDWHIL setlocal indentexpr=GetIdlangIndent(v:lnum) +let b:undo_indent = "setl inde< indk<" + " Only define the function once. if exists("*GetIdlangIndent") finish diff --git a/runtime/indent/liquid.vim b/runtime/indent/liquid.vim index 7beb0388d1..6fc933797e 100644 --- a/runtime/indent/liquid.vim +++ b/runtime/indent/liquid.vim @@ -1,7 +1,7 @@ " Vim indent file " Language: Liquid " Maintainer: Tim Pope <vimNOSPAM@tpope.org> -" Last Change: 2017 Jun 13 +" Last Change: 2022 Mar 15 if exists('b:did_indent') finish @@ -29,17 +29,19 @@ let b:did_indent = 1 setlocal indentexpr=GetLiquidIndent() setlocal indentkeys=o,O,*<Return>,<>>,{,},0),0],o,O,!^F,=end,=endif,=endunless,=endifchanged,=endcase,=endfor,=endtablerow,=endcapture,=else,=elsif,=when,=empty +let b:undo_indent = "setl inde< indk<" + " Only define the function once. if exists('*GetLiquidIndent') finish endif -function! s:count(string,pattern) +function! s:count(string, pattern) abort let string = substitute(a:string,'\C'.a:pattern,"\n",'g') return strlen(substitute(string,"[^\n]",'','g')) endfunction -function! GetLiquidIndent(...) +function! GetLiquidIndent(...) abort if a:0 && a:1 == '.' let v:lnum = line('.') elseif a:0 && a:1 =~ '^\d' @@ -51,13 +53,14 @@ function! GetLiquidIndent(...) let lnum = prevnonblank(v:lnum-1) let line = getline(lnum) let cline = getline(v:lnum) - let line = substitute(line,'\C^\%(\s*{%\s*end\w*\s*%}\)\+','','') - let line .= matchstr(cline,'\C^\%(\s*{%\s*end\w*\s*%}\)\+') - let cline = substitute(cline,'\C^\%(\s*{%\s*end\w*\s*%}\)\+','','') + let line = substitute(line,'\C^\%(\s*{%-\=\s*end\w*\s*-\=%}\)\+','','') + let line = substitute(line,'\C\%(\s*{%-\=\s*if.\+-\=%}.\+{%-\=\s*endif\s*-\=%}\)\+','','g') + let line .= matchstr(cline,'\C^\%(\s*{%-\=\s*end\w*\s*-\=%}\)\+') + let cline = substitute(cline,'\C^\%(\s*{%-\=\s*end\w*\s*-\=%}\)\+','','') let sw = shiftwidth() - let ind += sw * s:count(line,'{%\s*\%(if\|elsif\|else\|unless\|ifchanged\|case\|when\|for\|empty\|tablerow\|capture\)\>') - let ind -= sw * s:count(line,'{%\s*end\%(if\|unless\|ifchanged\|case\|for\|tablerow\|capture\)\>') - let ind -= sw * s:count(cline,'{%\s*\%(elsif\|else\|when\|empty\)\>') - let ind -= sw * s:count(cline,'{%\s*end\w*$') + let ind += sw * s:count(line,'{%-\=\s*\%(if\|elsif\|else\|unless\|ifchanged\|case\|when\|for\|empty\|tablerow\|capture\)\>') + let ind -= sw * s:count(line,'{%-\=\s*end\%(if\|unless\|ifchanged\|case\|for\|tablerow\|capture\)\>') + let ind -= sw * s:count(cline,'{%-\=\s*\%(elsif\|else\|when\|empty\)\>') + let ind -= sw * s:count(cline,'{%-\=\s*end\w*$') return ind endfunction diff --git a/runtime/indent/make.vim b/runtime/indent/make.vim index 76c8f83399..4d1838b3aa 100644 --- a/runtime/indent/make.vim +++ b/runtime/indent/make.vim @@ -2,7 +2,7 @@ " Language: Makefile " Maintainer: Doug Kearns <dougkearns@gmail.com> " Previous Maintainer: Nikolai Weibull <now@bitwi.se> -" Last Change: 24 Sep 2021 +" Last Change: 2022 Apr 06 if exists("b:did_indent") finish @@ -13,7 +13,7 @@ setlocal indentexpr=GetMakeIndent() setlocal indentkeys=!^F,o,O,<:>,=else,=endif setlocal nosmartindent -let b:undo_indent = "setl ai< inde< indk<" +let b:undo_indent = "setl inde< indk< si<" if exists("*GetMakeIndent") finish diff --git a/runtime/indent/mma.vim b/runtime/indent/mma.vim index ebf98b9a38..9dbfd74d66 100644 --- a/runtime/indent/mma.vim +++ b/runtime/indent/mma.vim @@ -3,6 +3,7 @@ " Maintainer: Steve Layland <layland@wolfram.com> (Invalid email address) " Doug Kearns <dougkearns@gmail.com> " Last Change: Sat May 10 18:56:22 CDT 2005 +" 2022 April: b:undo_indent added by Doug Kearns " Source: http://vim.sourceforge.net/scripts/script.php?script_id=1274 " http://members.wolfram.com/layland/vim/indent/mma.vim " @@ -26,6 +27,8 @@ setlocal indentexpr=GetMmaIndent() setlocal indentkeys+=0[,0],0(,0) setlocal nosi "turn off smart indent so we don't over analyze } blocks +let b:undo_indent = "setl inde< indk< si<" + if exists("*GetMmaIndent") finish endif diff --git a/runtime/indent/nginx.vim b/runtime/indent/nginx.vim index d4afec1c11..8cef7662e0 100644 --- a/runtime/indent/nginx.vim +++ b/runtime/indent/nginx.vim @@ -1,7 +1,7 @@ " Vim indent file " Language: nginx.conf " Maintainer: Chris Aumann <me@chr4.org> -" Last Change: Apr 15, 2017 +" Last Change: 2022 Apr 06 if exists("b:did_indent") finish @@ -15,3 +15,5 @@ setlocal cindent " Just make sure that the comments are not reset as defs would be. setlocal cinkeys-=0# + +let b:undo_indent = "setl inde< cin< cink<" diff --git a/runtime/indent/objc.vim b/runtime/indent/objc.vim index a5451a5a11..1d107050dd 100644 --- a/runtime/indent/objc.vim +++ b/runtime/indent/objc.vim @@ -1,9 +1,7 @@ " Vim indent file " Language: Objective-C " Maintainer: Kazunobu Kuriyama <kazunobu.kuriyama@nifty.com> -" Last Change: 2004 May 16 -" - +" Last Change: 2022 Apr 06 " Only load this indent file when no other was loaded. if exists("b:did_indent") @@ -19,6 +17,8 @@ setlocal indentexpr=GetObjCIndent() setlocal indentkeys-=: setlocal indentkeys+=<:> +let b:undo_indent = "setl cin< inde< indk<" + " Only define the function once. if exists("*GetObjCIndent") finish diff --git a/runtime/indent/occam.vim b/runtime/indent/occam.vim index 2979ac16ac..673940a7ec 100644 --- a/runtime/indent/occam.vim +++ b/runtime/indent/occam.vim @@ -2,7 +2,7 @@ " Language: occam " Maintainer: Mario Schweigler <ms44@kent.ac.uk> (Invalid email address) " Doug Kearns <dougkearns@gmail.com> -" Last Change: 23 April 2003 +" Last Change: 2022 Apr 06 " Only load this indent file when no other was loaded. if exists("b:did_indent") @@ -17,6 +17,8 @@ setlocal indentexpr=GetOccamIndent() setlocal indentkeys=o,O,0=: "}}} +let b:undo_indent = "setl inde< indk<" + " Only define the function once if exists("*GetOccamIndent") finish diff --git a/runtime/indent/postscr.vim b/runtime/indent/postscr.vim index 0691cd237c..66094e3ed0 100644 --- a/runtime/indent/postscr.vim +++ b/runtime/indent/postscr.vim @@ -2,8 +2,8 @@ " Language: PostScript " Maintainer: Mike Williams <mrw@netcomuk.co.uk> (Invalid email address) " Doug Kearns <dougkearns@gmail.com> -" Last Change: 2nd July 2001 -" +" Last Change: 2022 Apr 06 + " Only load this indent file when no other was loaded. if exists("b:did_indent") @@ -14,6 +14,8 @@ let b:did_indent = 1 setlocal indentexpr=PostscrIndentGet(v:lnum) setlocal indentkeys+=0],0=>>,0=%%,0=end,0=restore,0=grestore indentkeys-=:,0#,e +let b:undo_indent = "setl inde< indk<" + " Catch multiple instantiations if exists("*PostscrIndentGet") finish diff --git a/runtime/indent/prolog.vim b/runtime/indent/prolog.vim index ac03c28064..0c4fd541f9 100644 --- a/runtime/indent/prolog.vim +++ b/runtime/indent/prolog.vim @@ -4,6 +4,7 @@ " Doug Kearns <dougkearns@gmail.com> " Revised on: 2002.02.18. 23:34:05 " Last change by: Takuya Fujiwara, 2018 Sep 23 +" 2022 April: b:undo_indent added by Doug Kearns " TODO: " checking with respect to syntax highlighting @@ -21,6 +22,8 @@ setlocal indentexpr=GetPrologIndent() setlocal indentkeys-=:,0# setlocal indentkeys+=0%,-,0;,>,0) +let b:undo_indent = "setl inde< indk<" + " Only define the function once. "if exists("*GetPrologIndent") " finish 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/query.lua b/runtime/indent/query.lua new file mode 100644 index 0000000000..55cb73e62b --- /dev/null +++ b/runtime/indent/query.lua @@ -0,0 +1,6 @@ +-- Neovim indent file +-- Language: Tree-sitter query +-- Last Change: 2022 Mar 29 + +-- it's a lisp! +vim.cmd [[ runtime! indent/lisp.vim ]] diff --git a/runtime/indent/ruby.vim b/runtime/indent/ruby.vim index 559d8652a6..6ce8529fd1 100644 --- a/runtime/indent/ruby.vim +++ b/runtime/indent/ruby.vim @@ -4,7 +4,7 @@ " Previous Maintainer: Nikolai Weibull <now at bitwi.se> " URL: https://github.com/vim-ruby/vim-ruby " Release Coordinator: Doug Kearns <dougkearns@gmail.com> -" Last Change: 2021 Feb 03 +" Last Change: 2022 Mar 22 " 0. Initialization {{{1 " ================= @@ -40,9 +40,11 @@ setlocal nosmartindent " Now, set up our indentation expression and keys that trigger it. setlocal indentexpr=GetRubyIndent(v:lnum) setlocal indentkeys=0{,0},0),0],!^F,o,O,e,:,. -setlocal indentkeys+==end,=else,=elsif,=when,=ensure,=rescue,==begin,==end +setlocal indentkeys+==end,=else,=elsif,=when,=in\ ,=ensure,=rescue,==begin,==end setlocal indentkeys+==private,=protected,=public +let b:undo_indent = "setlocal indentexpr< indentkeys< smartindent<" + " Only define the function once. if exists("*GetRubyIndent") finish @@ -85,14 +87,17 @@ let s:skip_expr = " Regex used for words that, at the start of a line, add a level of indent. let s:ruby_indent_keywords = \ '^\s*\zs\<\%(module\|class\|if\|for' . - \ '\|while\|until\|else\|elsif\|case\|when\|unless\|begin\|ensure\|rescue' . + \ '\|while\|until\|else\|elsif\|case\|when\|in\|unless\|begin\|ensure\|rescue' . \ '\|\%(\K\k*[!?]\?\s\+\)\=def\):\@!\>' . \ '\|\%([=,*/%+-]\|<<\|>>\|:\s\)\s*\zs' . \ '\<\%(if\|for\|while\|until\|case\|unless\|begin\):\@!\>' +" Def without an end clause: def method_call(...) = <expression> +let s:ruby_endless_def = '\<def\s\+\k\+[!?]\=\%((.*)\|\s\)\s*=' + " Regex used for words that, at the start of a line, remove a level of indent. let s:ruby_deindent_keywords = - \ '^\s*\zs\<\%(ensure\|else\|rescue\|elsif\|when\|end\):\@!\>' + \ '^\s*\zs\<\%(ensure\|else\|rescue\|elsif\|when\|in\|end\):\@!\>' " Regex that defines the start-match for the 'end' keyword. "let s:end_start_regex = '\%(^\|[^.]\)\<\%(module\|class\|def\|if\|for\|while\|until\|case\|unless\|begin\|do\)\>' @@ -104,15 +109,31 @@ let s:end_start_regex = \ '\|\%(^\|[^.:@$]\)\@<=\<do:\@!\>' " Regex that defines the middle-match for the 'end' keyword. -let s:end_middle_regex = '\<\%(ensure\|else\|\%(\%(^\|;\)\s*\)\@<=\<rescue:\@!\>\|when\|elsif\):\@!\>' +let s:end_middle_regex = '\<\%(ensure\|else\|\%(\%(^\|;\)\s*\)\@<=\<rescue:\@!\>\|when\|\%(\%(^\|;\)\s*\)\@<=\<in\|elsif\):\@!\>' " Regex that defines the end-match for the 'end' keyword. let s:end_end_regex = '\%(^\|[^.:@$]\)\@<=\<end:\@!\>' -" Expression used for searchpair() call for finding match for 'end' keyword. -let s:end_skip_expr = s:skip_expr . - \ ' || (expand("<cword>") == "do"' . - \ ' && getline(".") =~ "^\\s*\\<\\(while\\|until\\|for\\):\\@!\\>")' +" Expression used for searchpair() call for finding a match for an 'end' keyword. +function! s:EndSkipExpr() + if eval(s:skip_expr) + return 1 + elseif expand('<cword>') == 'do' + \ && getline(".") =~ '^\s*\<\(while\|until\|for\):\@!\>' + return 1 + elseif getline('.') =~ s:ruby_endless_def + return 1 + elseif getline('.') =~ '\<def\s\+\k\+[!?]\=([^)]*$' + " Then it's a `def method(` with a possible `) =` later + call search('\<def\s\+\k\+\zs(', 'W', line('.')) + normal! % + return getline('.') =~ ')\s*=' + else + return 0 + endif +endfunction + +let s:end_skip_expr = function('s:EndSkipExpr') " Regex that defines continuation lines, not including (, {, or [. let s:non_bracket_continuation_regex = @@ -572,6 +593,11 @@ function! s:AfterUnbalancedBracket(pline_info) abort call cursor(info.plnum, closing.pos + 1) normal! % + if strpart(info.pline, closing.pos) =~ '^)\s*=' + " special case: the closing `) =` of an endless def + return indent(s:GetMSL(line('.'))) + endif + if s:Match(line('.'), s:ruby_indent_keywords) return indent('.') + info.sw else @@ -610,7 +636,7 @@ function! s:AfterIndentKeyword(pline_info) abort let info = a:pline_info let col = s:Match(info.plnum, s:ruby_indent_keywords) - if col > 0 + if col > 0 && s:Match(info.plnum, s:ruby_endless_def) <= 0 call cursor(info.plnum, col) let ind = virtcol('.') - 1 + info.sw " TODO: make this better (we need to count them) (or, if a searchpair @@ -657,7 +683,7 @@ function! s:IndentingKeywordInMSL(msl_info) abort " TODO: this does not take into account contrived things such as " module Foo; class Bar; end let col = s:Match(info.plnum_msl, s:ruby_indent_keywords) - if col > 0 + if col > 0 && s:Match(info.plnum_msl, s:ruby_endless_def) <= 0 let ind = indent(info.plnum_msl) + info.sw if s:Match(info.plnum_msl, s:end_end_regex) let ind = ind - info.sw diff --git a/runtime/indent/sas.vim b/runtime/indent/sas.vim index 9cc9e025c4..bbbbbf02eb 100644 --- a/runtime/indent/sas.vim +++ b/runtime/indent/sas.vim @@ -2,7 +2,7 @@ " Language: SAS " Maintainer: Zhen-Huan Hu <wildkeny@gmail.com> " Version: 3.0.3 -" Last Change: Jun 26, 2018 +" Last Change: 2022 Apr 06 if exists("b:did_indent") finish @@ -12,6 +12,8 @@ let b:did_indent = 1 setlocal indentexpr=GetSASIndent() setlocal indentkeys+=;,=~data,=~proc,=~macro +let b:undo_indent = "setl inde< indk<" + if exists("*GetSASIndent") finish endif diff --git a/runtime/indent/sass.vim b/runtime/indent/sass.vim index d6dbf3a8bb..8c0ecd0746 100644 --- a/runtime/indent/sass.vim +++ b/runtime/indent/sass.vim @@ -1,7 +1,7 @@ " Vim indent file " Language: Sass " Maintainer: Tim Pope <vimNOSPAM@tpope.org> -" Last Change: 2017 Jun 13 +" Last Change: 2022 Mar 15 if exists("b:did_indent") finish @@ -12,6 +12,8 @@ setlocal autoindent sw=2 et setlocal indentexpr=GetSassIndent() setlocal indentkeys=o,O,*<Return>,<:>,!^F +let b:undo_indent = "setl ai< inde< indk<" + " Only define the function once. if exists("*GetSassIndent") finish diff --git a/runtime/indent/sh.vim b/runtime/indent/sh.vim index d2fb1ba452..aa47c6d1bd 100644 --- a/runtime/indent/sh.vim +++ b/runtime/indent/sh.vim @@ -109,7 +109,7 @@ function! GetShIndent() let ind += s:indent_value('continuation-line') endif elseif s:end_block(line) && !s:start_block(line) - let ind -= s:indent_value('default') + let ind = indent(lnum) elseif pnum != 0 && \ s:is_continuation_line(pline) && \ !s:end_block(curline) && diff --git a/runtime/indent/sml.vim b/runtime/indent/sml.vim index e760a8e350..a0b0c3e911 100644 --- a/runtime/indent/sml.vim +++ b/runtime/indent/sml.vim @@ -7,10 +7,11 @@ " Mike Leary <leary@nwlink.com> " Markus Mottl <markus@oefai.at> " OCaml URL: http://www.oefai.at/~markus/vim/indent/ocaml.vim -" Last Change: 2003 Jan 04 - Adapted to SML +" Last Change: 2022 Apr 06 " 2002 Nov 06 - Some fixes (JY) " 2002 Oct 28 - Fixed bug with indentation of ']' (MM) " 2002 Oct 22 - Major rewrite (JY) +" 2022 April: b:undo_indent added by Doug Kearns " Only load this indent file when no other was loaded. if exists("b:did_indent") @@ -26,6 +27,8 @@ setlocal nosmartindent setlocal textwidth=80 setlocal shiftwidth=2 +let b:undo_indent = "setl et< inde< indk< lisp< si< sw< tw<" + " Comment formatting if (has("comments")) set comments=sr:(*,mb:*,ex:*) diff --git a/runtime/indent/systemverilog.vim b/runtime/indent/systemverilog.vim index 16fb4515c5..f6114dc1fd 100644 --- a/runtime/indent/systemverilog.vim +++ b/runtime/indent/systemverilog.vim @@ -2,6 +2,7 @@ " Language: SystemVerilog " Maintainer: kocha <kocha.lsifrontend@gmail.com> " Last Change: 05-Feb-2017 by Bilal Wasim +" 2022 April: b:undo_indent added by Doug Kearns " Only load this indent file when no other was loaded. if exists("b:did_indent") @@ -16,6 +17,8 @@ setlocal indentkeys+==endclass,=endpackage,=endsequence,=endclocking setlocal indentkeys+==endinterface,=endgroup,=endprogram,=endproperty,=endchecker setlocal indentkeys+==`else,=`endif +let b:undo_indent = "setl inde< indk<" + " Only define the function once. if exists("*SystemVerilogIndent") finish 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 a98c75e541..cd2d4982d8 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 03 +" Last Change: 2022 Mar 01 " Only load this indent file when no other was loaded. if exists("b:did_indent") @@ -10,8 +10,9 @@ 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-=: let b:undo_indent = "setl indentkeys< indentexpr<" @@ -102,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][! ]\)') 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() @@ -169,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/indent/xml.vim b/runtime/indent/xml.vim index da65417939..5bf53ad1f8 100644 --- a/runtime/indent/xml.vim +++ b/runtime/indent/xml.vim @@ -39,6 +39,8 @@ setlocal indentkeys=o,O,*<Return>,<>>,<<>,/,{,},!^F " autoindent: used when the indentexpr returns -1 setlocal autoindent +let b:undo_indent = "setl ai< inde< indk<" + if !exists('b:xml_indent_open') let b:xml_indent_open = '.\{-}<[:A-Z_a-z]' " pre tag, e.g. <address> @@ -51,6 +53,10 @@ if !exists('b:xml_indent_close') " let b:xml_indent_close = '.\{-}</\(address\)\@!' endif +if !exists('b:xml_indent_continuation_filetype') + let b:xml_indent_continuation_filetype = 'xml' +endif + let &cpo = s:keepcpo unlet s:keepcpo @@ -162,7 +168,7 @@ endfun func! <SID>IsXMLContinuation(line) " Checks, whether or not the line matches a start-of-tag - return a:line !~ '^\s*<' && &ft is# 'xml' + return a:line !~ '^\s*<' && &ft =~# b:xml_indent_continuation_filetype endfunc func! <SID>HasNoTagEnd(line) diff --git a/runtime/lua/vim/F.lua b/runtime/lua/vim/F.lua index 1a258546a5..9327c652db 100644 --- a/runtime/lua/vim/F.lua +++ b/runtime/lua/vim/F.lua @@ -27,7 +27,7 @@ function F.nil_wrap(fn) end end ---- like {...} except preserve the lenght explicitly +--- like {...} except preserve the length explicitly function F.pack_len(...) return {n=select('#', ...), ...} end diff --git a/src/nvim/lua/vim.lua b/runtime/lua/vim/_editor.lua index 30c7034209..d4db4850bd 100644 --- a/src/nvim/lua/vim.lua +++ b/runtime/lua/vim/_editor.lua @@ -3,9 +3,11 @@ -- Lua code lives in one of three places: -- 1. runtime/lua/vim/ (the runtime): For "nice to have" features, e.g. the -- `inspect` and `lpeg` modules. --- 2. runtime/lua/vim/shared.lua: Code shared between Nvim and tests. --- (This will go away if we migrate to nvim as the test-runner.) --- 3. src/nvim/lua/: Compiled-into Nvim itself. +-- 2. runtime/lua/vim/shared.lua: pure lua functions which always +-- are available. Used in the test runner, as well as worker threads +-- and processes launched from Nvim. +-- 3. runtime/lua/vim/_editor.lua: Code which directly interacts with +-- the Nvim editor state. Only available in the main thread. -- -- Guideline: "If in doubt, put it in the runtime". -- @@ -34,87 +36,20 @@ -- - https://github.com/bakpakin/Fennel (pretty print, repl) -- - https://github.com/howl-editor/howl/tree/master/lib/howl/util -local vim = vim -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 - - 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) +local vim = assert(vim) -- These are for loading runtime modules lazily since they aren't available in -- the nvim binary as specified in executor.c -setmetatable(vim, { - __index = function(t, key) - if key == 'treesitter' then - t.treesitter = require('vim.treesitter') - return t.treesitter - elseif key == 'F' then - t.F = require('vim.F') - return t.F - elseif require('vim.uri')[key] ~= nil then - -- Expose all `vim.uri` functions on the `vim` module. - t[key] = require('vim.uri')[key] - return t[key] - elseif key == 'lsp' then - t.lsp = require('vim.lsp') - return t.lsp - elseif key == 'highlight' then - t.highlight = require('vim.highlight') - return t.highlight - elseif key == 'diagnostic' then - t.diagnostic = require('vim.diagnostic') - return t.diagnostic - elseif key == 'ui' then - t.ui = require('vim.ui') - return t.ui - end - end -}) +for k,v in pairs { + treesitter=true; + filetype = true; + F=true; + lsp=true; + highlight=true; + diagnostic=true; + keymap=true; + ui=true; +} do vim._submodules[k] = v end vim.log = { levels = { @@ -193,7 +128,7 @@ local function inspect(object, options) -- luacheck: no unused end do - local tdots, tick, got_line1 = 0, 0, false + local tdots, tick, got_line1, undo_started, trailing_nl = 0, 0, false, false, false --- Paste handler, invoked by |nvim_paste()| when a conforming UI --- (such as the |TUI|) pastes text into the editor. @@ -221,44 +156,80 @@ do --- - 3: ends the paste (exactly once) ---@returns false if client should cancel the paste. function vim.paste(lines, phase) - local call = vim.api.nvim_call_function local now = vim.loop.now() - local mode = call('mode', {}):sub(1,1) - if phase < 2 then -- Reset flags. - tdots, tick, got_line1 = now, 0, false - elseif mode ~= 'c' then + local is_first_chunk = phase < 2 + local is_last_chunk = phase == -1 or phase == 3 + if is_first_chunk then -- Reset flags. + tdots, tick, got_line1, undo_started, trailing_nl = now, 0, false, false, false + end + if #lines == 0 then + lines = {''} + end + if #lines == 1 and lines[1] == '' and not is_last_chunk then + -- An empty chunk can cause some edge cases in streamed pasting, + -- so don't do anything unless it is the last chunk. + return true + end + -- Note: mode doesn't always start with "c" in cmdline mode, so use getcmdtype() instead. + if vim.fn.getcmdtype() ~= '' then -- cmdline-mode: paste only 1 line. + if not got_line1 then + got_line1 = (#lines > 1) + vim.api.nvim_set_option('paste', true) -- For nvim_input(). + -- Escape "<" and control characters + local line1 = lines[1]:gsub('<', '<lt>'):gsub('(%c)', '\022%1') + vim.api.nvim_input(line1) + vim.api.nvim_set_option('paste', false) + end + return true + end + local mode = vim.api.nvim_get_mode().mode + if undo_started then vim.api.nvim_command('undojoin') end - if mode == 'c' and not got_line1 then -- cmdline-mode: paste only 1 line. - got_line1 = (#lines > 1) - vim.api.nvim_set_option('paste', true) -- For nvim_input(). - local line1 = lines[1]:gsub('<', '<lt>'):gsub('[\r\n\012\027]', ' ') -- Scrub. - vim.api.nvim_input(line1) - vim.api.nvim_set_option('paste', false) - elseif mode ~= 'c' then - if phase < 2 and mode:find('^[vV\22sS\19]') then - vim.api.nvim_command([[exe "normal! \<Del>"]]) - vim.api.nvim_put(lines, 'c', false, true) - elseif phase < 2 and not mode:find('^[iRt]') then - vim.api.nvim_put(lines, 'c', true, true) - -- XXX: Normal-mode: workaround bad cursor-placement after first chunk. - vim.api.nvim_command('normal! a') - elseif phase < 2 and mode == 'R' then - local nchars = 0 - for _, line in ipairs(lines) do - nchars = nchars + line:len() + if mode:find('^i') or mode:find('^n?t') then -- Insert mode or Terminal buffer + vim.api.nvim_put(lines, 'c', false, true) + elseif phase < 2 and mode:find('^R') and not mode:find('^Rv') then -- Replace mode + -- TODO: implement Replace mode streamed pasting + -- TODO: support Virtual Replace mode + local nchars = 0 + for _, line in ipairs(lines) do + nchars = nchars + line:len() + end + local row, col = unpack(vim.api.nvim_win_get_cursor(0)) + local bufline = vim.api.nvim_buf_get_lines(0, row-1, row, true)[1] + local firstline = lines[1] + firstline = bufline:sub(1, col)..firstline + lines[1] = firstline + lines[#lines] = lines[#lines]..bufline:sub(col + nchars + 1, bufline:len()) + vim.api.nvim_buf_set_lines(0, row-1, row, false, lines) + elseif mode:find('^[nvV\22sS\19]') then -- Normal or Visual or Select mode + if mode:find('^n') then -- Normal mode + -- When there was a trailing new line in the previous chunk, + -- the cursor is on the first character of the next line, + -- so paste before the cursor instead of after it. + vim.api.nvim_put(lines, 'c', not trailing_nl, false) + else -- Visual or Select mode + vim.api.nvim_command([[exe "silent normal! \<Del>"]]) + local del_start = vim.fn.getpos("'[") + local cursor_pos = vim.fn.getpos('.') + if mode:find('^[VS]') then -- linewise + if cursor_pos[2] < del_start[2] then -- replacing lines at eof + -- create a new line + vim.api.nvim_put({''}, 'l', true, true) + end + vim.api.nvim_put(lines, 'c', false, false) + else + -- paste after cursor when replacing text at eol, otherwise paste before cursor + vim.api.nvim_put(lines, 'c', cursor_pos[3] < del_start[3], false) end - local row, col = unpack(vim.api.nvim_win_get_cursor(0)) - local bufline = vim.api.nvim_buf_get_lines(0, row-1, row, true)[1] - local firstline = lines[1] - firstline = bufline:sub(1, col)..firstline - lines[1] = firstline - lines[#lines] = lines[#lines]..bufline:sub(col + nchars + 1, bufline:len()) - vim.api.nvim_buf_set_lines(0, row-1, row, false, lines) - else - vim.api.nvim_put(lines, 'c', false, true) end + -- put cursor at the end of the text instead of one character after it + vim.fn.setpos('.', vim.fn.getpos("']")) + trailing_nl = lines[#lines] == '' + else -- Don't know what to do in other modes + return false end + undo_started = true if phase ~= -1 and (now - tdots >= 100) then local dots = ('.'):rep(tick % 4) tdots = now @@ -267,7 +238,7 @@ do -- message when there are zero dots. vim.api.nvim_command(('echo "%s"'):format(dots)) end - if phase == -1 or phase == 3 then + if is_last_chunk then vim.api.nvim_command('redraw'..(tick > 1 and '|echo ""' or '')) end return true -- Paste will not continue if not returning `true`. @@ -286,12 +257,6 @@ function vim.schedule_wrap(cb) end) end ---- <Docs described in |vim.empty_dict()| > ----@private -function vim.empty_dict() - return setmetatable({}, vim._empty_dict_mt) -end - -- vim.fn.{func}(...) vim.fn = setmetatable({}, { __index = function(t, key) @@ -323,6 +288,7 @@ end do local validate = vim.validate + --@private local function make_dict_accessor(scope, handle) validate { scope = {scope, 's'}; @@ -418,24 +384,43 @@ function vim.defer_fn(fn, timeout) end ---- Notification provider +--- Display a notification to the user. +--- +--- 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|. --- ---- Without a runtime, writes to :Messages ----@see :help nvim_notify ----@param msg Content of the notification to show to the user ----@param log_level Optional log level ----@param opts Dictionary with optional options (timeout, etc) -function vim.notify(msg, log_level, _opts) - - if log_level == vim.log.levels.ERROR then +---@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() @@ -454,7 +439,7 @@ local on_key_cbs = {} --- On each key press, Nvim passes the key char to fn(). |i_CTRL-V| --- If {fn} is nil, it removes the callback for the associated {ns_id} ---@param ns_id number? Namespace ID. If nil or 0, generates and returns a new ---- |nvim_create_namesapce()| id. +--- |nvim_create_namespace()| id. --- ---@return number Namespace id associated with {fn}. Or count of all callbacks ---if on_key() is called without arguments. @@ -570,6 +555,8 @@ function vim._expand_pat(pat, env) local mt = getmetatable(final_env) if mt and type(mt.__index) == "table" then field = rawget(mt.__index, key) + elseif final_env == vim and vim._submodules[key] then + field = vim[key] end end final_env = field @@ -580,6 +567,7 @@ function vim._expand_pat(pat, env) end local keys = {} + ---@private local function insert_keys(obj) for k,_ in pairs(obj) do if type(k) == "string" and string.sub(k,1,string.len(match_part)) == match_part then @@ -595,6 +583,9 @@ function vim._expand_pat(pat, env) if mt and type(mt.__index) == "table" then insert_keys(mt.__index) end + if final_env == vim then + insert_keys(vim._submodules) + end table.sort(keys) @@ -662,4 +653,88 @@ vim._expand_pat_get_parts = function(lua_string) return parts, search_index end -return module +---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 + +function vim._cs_remote(rcid, server_addr, connect_error, args) + local function connection_failure_errmsg(consequence) + local explanation + if server_addr == '' then + explanation = "No server specified with --server" + else + explanation = "Failed to connect to '" .. server_addr .. "'" + if connect_error ~= "" then + explanation = explanation .. ": " .. connect_error + end + end + return "E247: " .. explanation .. ". " .. consequence + end + + local f_silent = false + local f_tab = false + + local subcmd = string.sub(args[1],10) + if subcmd == 'tab' then + f_tab = true + elseif subcmd == 'silent' then + f_silent = true + elseif subcmd == 'wait' or subcmd == 'wait-silent' or subcmd == 'tab-wait' or subcmd == 'tab-wait-silent' then + return { errmsg = 'E5600: Wait commands not yet implemented in nvim' } + elseif subcmd == 'tab-silent' then + f_tab = true + f_silent = true + elseif subcmd == 'send' then + if rcid == 0 then + return { errmsg = connection_failure_errmsg('Send failed.') } + end + vim.fn.rpcrequest(rcid, 'nvim_input', args[2]) + return { should_exit = true, tabbed = false } + elseif subcmd == 'expr' then + if rcid == 0 then + return { errmsg = connection_failure_errmsg('Send expression failed.') } + end + print(vim.fn.rpcrequest(rcid, 'nvim_eval', args[2])) + return { should_exit = true, tabbed = false } + elseif subcmd ~= '' then + return { errmsg='Unknown option argument: ' .. args[1] } + end + + if rcid == 0 then + if not f_silent then + vim.notify(connection_failure_errmsg("Editing locally"), vim.log.levels.WARN) + end + else + local command = {} + if f_tab then table.insert(command, 'tab') end + table.insert(command, 'drop') + for i = 2, #args do + table.insert(command, vim.fn.fnameescape(args[i])) + end + vim.fn.rpcrequest(rcid, 'nvim_command', table.concat(command, ' ')) + end + + return { + should_exit = rcid ~= 0, + tabbed = f_tab, + } +end + +require('vim._meta') + +return vim diff --git a/runtime/lua/vim/_init_packages.lua b/runtime/lua/vim/_init_packages.lua new file mode 100644 index 0000000000..7d27741f1b --- /dev/null +++ b/runtime/lua/vim/_init_packages.lua @@ -0,0 +1,83 @@ +-- 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) + +-- builtin functions which always should be available +require'vim.shared' + +vim._submodules = {inspect=true} + +-- These are for loading runtime modules in the vim namespace lazily. +setmetatable(vim, { + __index = function(t, key) + if vim._submodules[key] then + t[key] = require('vim.'..key) + return t[key] + elseif vim.startswith(key, 'uri_') then + local val = require('vim.uri')[key] + if val ~= nil then + -- Expose all `vim.uri` functions on the `vim` module. + t[key] = val + return t[key] + end + end + end +}) + +--- <Docs described in |vim.empty_dict()| > +---@private +--- TODO: should be in vim.shared when vim.shared always uses nvim-lua +function vim.empty_dict() + return setmetatable({}, vim._empty_dict_mt) +end + +-- only on main thread: functions for interacting with editor state +if not vim.is_thread() then + require'vim._editor' +end diff --git a/runtime/lua/vim/_meta.lua b/runtime/lua/vim/_meta.lua index f7d47c1030..522e26caa7 100644 --- a/runtime/lua/vim/_meta.lua +++ b/runtime/lua/vim/_meta.lua @@ -16,10 +16,6 @@ for _, v in pairs(a.nvim_get_all_options_info()) do if v.shortname ~= "" then options_info[v.shortname] = v end end -local is_global_option = function(info) return info.scope == "global" end -local is_buffer_option = function(info) return info.scope == "buf" end -local is_window_option = function(info) return info.scope == "win" end - local get_scoped_options = function(scope) local result = {} for name, option_info in pairs(options_info) do @@ -133,107 +129,18 @@ do -- window option accessor vim.wo = new_win_opt_accessor(nil) end ---[[ -Local window setter - -buffer options: does not get copied when split - nvim_set_option(buf_opt, value) -> sets the default for NEW buffers - this sets the hidden global default for buffer options - - nvim_buf_set_option(...) -> sets the local value for the buffer - - set opt=value, does BOTH global default AND buffer local value - setlocal opt=value, does ONLY buffer local value - -window options: gets copied - does not need to call nvim_set_option because nobody knows what the heck this does⸮ - We call it anyway for more readable code. - - - Command global value local value - :set option=value set set - :setlocal option=value - set -:setglobal option=value set - ---]] -local function set_scoped_option(k, v, set_type) - local info = options_info[k] - - -- Don't let people do setlocal with global options. - -- That is a feature that doesn't make sense. - if set_type == SET_TYPES.LOCAL and is_global_option(info) then - error(string.format("Unable to setlocal option: '%s', which is a global option.", k)) - end - - -- Only `setlocal` skips setting the default/global value - -- This will more-or-less noop for window options, but that's OK - if set_type ~= SET_TYPES.LOCAL then - a.nvim_set_option(k, v) - end - - if is_window_option(info) then - if set_type ~= SET_TYPES.GLOBAL then - a.nvim_win_set_option(0, k, v) - end - elseif is_buffer_option(info) then - if set_type == SET_TYPES.LOCAL - or (set_type == SET_TYPES.SET and not info.global_local) then - a.nvim_buf_set_option(0, k, v) - end - end -end - ---[[ -Local window getter - - Command global value local value - :set option? - display - :setlocal option? - display -:setglobal option? display - ---]] -local function get_scoped_option(k, set_type) - local info = assert(options_info[k], "Must be a valid option: " .. tostring(k)) - - if set_type == SET_TYPES.GLOBAL or is_global_option(info) then - return a.nvim_get_option(k) - end - - if is_buffer_option(info) then - local was_set, value = pcall(a.nvim_buf_get_option, 0, k) - if was_set then return value end - - if info.global_local then - return a.nvim_get_option(k) - end - - error("buf_get: This should not be able to happen, given my understanding of options // " .. k) - end - - if is_window_option(info) then - local ok, value = pcall(a.nvim_win_get_option, 0, k) - if ok then - return value - end - - local global_ok, global_val = pcall(a.nvim_get_option, k) - if global_ok then - return global_val - end - - error("win_get: This should never happen. File an issue and tag @tjdevries") - end - - error("This fallback case should not be possible. " .. k) -end - -- vim global option -- this ONLY sets the global option. like `setglobal` -vim.go = make_meta_accessor(a.nvim_get_option, a.nvim_set_option) +vim.go = make_meta_accessor( + function(k) return a.nvim_get_option_value(k, {scope = "global"}) end, + function(k, v) return a.nvim_set_option_value(k, v, {scope = "global"}) end +) -- vim `set` style options. -- it has no additional metamethod magic. vim.o = make_meta_accessor( - function(k) return get_scoped_option(k, SET_TYPES.SET) end, - function(k, v) return set_scoped_option(k, v, SET_TYPES.SET) end + function(k) return a.nvim_get_option_value(k, {}) end, + function(k, v) return a.nvim_set_option_value(k, v, {}) end ) ---@brief [[ @@ -389,6 +296,10 @@ local convert_value_to_vim = (function() } return function(name, info, value) + if value == nil then + return vim.NIL + end + local option_type = get_option_type(name, info) assert_valid_value(name, value, valid_types[option_type]) @@ -398,7 +309,7 @@ end)() --- Converts a vimoption_T style value to a Lua value local convert_value_to_lua = (function() - -- Map of OptionType to functions that take vimoption_T values and conver to lua values. + -- Map of OptionType to functions that take vimoption_T values and convert to lua values. -- Each function takes (info, vim_value) -> lua_value local to_lua_value = { [OptionTypes.BOOLEAN] = function(_, value) return value end, @@ -671,15 +582,19 @@ local create_option_metatable = function(set_type) }, option_mt) end - -- TODO(tjdevries): consider supporting `nil` for set to remove the local option. - -- vim.cmd [[set option<]] + local scope + if set_type == SET_TYPES.GLOBAL then + scope = "global" + elseif set_type == SET_TYPES.LOCAL then + scope = "local" + end option_mt = { -- To set a value, instead use: -- opt[my_option] = value _set = function(self) local value = convert_value_to_vim(self._name, self._info, self._value) - set_scoped_option(self._name, value, set_type) + a.nvim_set_option_value(self._name, value, {scope = scope}) return self end, @@ -716,7 +631,7 @@ local create_option_metatable = function(set_type) set_mt = { __index = function(_, k) - return make_option(k, get_scoped_option(k, set_type)) + return make_option(k, a.nvim_get_option_value(k, {scope = scope})) end, __newindex = function(_, k, v) diff --git a/runtime/lua/vim/diagnostic.lua b/runtime/lua/vim/diagnostic.lua index 0bc7a305f0..1ec66d7c55 100644 --- a/runtime/lua/vim/diagnostic.lua +++ b/runtime/lua/vim/diagnostic.lua @@ -30,13 +30,40 @@ 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, }) --- Local functions {{{ +-- Metatable that automatically creates an empty table when assigning to a missing key +local bufnr_and_namespace_cacher_mt = { + __index = function(t, bufnr) + assert(bufnr > 0, "Invalid buffer number") + t[bufnr] = {} + return t[bufnr] + end, +} + +local diagnostic_cache = setmetatable({}, { + __index = function(t, bufnr) + assert(bufnr > 0, "Invalid buffer number") + vim.api.nvim_buf_attach(bufnr, false, { + on_detach = function() + rawset(t, bufnr, nil) -- clear cache + end + }) + t[bufnr] = {} + return t[bufnr] + end, +}) + +local diagnostic_cache_extmarks = setmetatable({}, bufnr_and_namespace_cacher_mt) +local diagnostic_attached_buffers = {} +local diagnostic_disabled = {} +local bufs_waiting_to_update = setmetatable({}, bufnr_and_namespace_cacher_mt) + +local all_namespaces = {} ---@private local function to_severity(severity) @@ -64,23 +91,22 @@ local function filter_by_severity(severity, diagnostics) end ---@private -local function prefix_source(source, diagnostics) - vim.validate { source = {source, function(v) - return v == "always" or v == "if_many" - end, "'always' or 'if_many'" } } - - if source == "if_many" then - local sources = {} - for _, d in pairs(diagnostics) do - if d.source then - sources[d.source] = true +local function count_sources(bufnr) + local seen = {} + local count = 0 + for _, namespace_diagnostics in pairs(diagnostic_cache[bufnr]) do + for _, diagnostic in ipairs(namespace_diagnostics) do + if diagnostic.source and not seen[diagnostic.source] then + seen[diagnostic.source] = true + count = count + 1 end end - if #vim.tbl_keys(sources) <= 1 then - return diagnostics - end end + return count +end +---@private +local function prefix_source(diagnostics) return vim.tbl_map(function(d) if not d.source then return d @@ -106,8 +132,6 @@ local function reformat_diagnostics(format, diagnostics) return formatted end -local all_namespaces = {} - ---@private local function enabled_value(option, namespace) local ns = namespace and M.get_namespace(namespace) or {} @@ -213,36 +237,6 @@ local function get_bufnr(bufnr) return bufnr end --- Metatable that automatically creates an empty table when assigning to a missing key -local bufnr_and_namespace_cacher_mt = { - __index = function(t, bufnr) - if not bufnr or bufnr == 0 then - bufnr = vim.api.nvim_get_current_buf() - end - - if rawget(t, bufnr) == nil then - rawset(t, bufnr, {}) - end - - return rawget(t, bufnr) - end, - - __newindex = function(t, bufnr, v) - if not bufnr or bufnr == 0 then - bufnr = vim.api.nvim_get_current_buf() - end - - rawset(t, bufnr, v) - end, -} - -local diagnostic_cleanup = setmetatable({}, bufnr_and_namespace_cacher_mt) -local diagnostic_cache = setmetatable({}, bufnr_and_namespace_cacher_mt) -local diagnostic_cache_extmarks = setmetatable({}, bufnr_and_namespace_cacher_mt) -local diagnostic_attached_buffers = {} -local diagnostic_disabled = {} -local bufs_waiting_to_update = setmetatable({}, bufnr_and_namespace_cacher_mt) - ---@private local function is_disabled(namespace, bufnr) local ns = M.get_namespace(namespace) @@ -277,6 +271,8 @@ end ---@private local function set_diagnostic_cache(namespace, bufnr, diagnostics) for _, diagnostic in ipairs(diagnostics) do + assert(diagnostic.lnum, "Diagnostic line number is required") + assert(diagnostic.col, "Diagnostic column is required") diagnostic.severity = diagnostic.severity and to_severity(diagnostic.severity) or M.severity.ERROR diagnostic.end_lnum = diagnostic.end_lnum or diagnostic.lnum diagnostic.end_col = diagnostic.end_col or diagnostic.col @@ -287,11 +283,6 @@ local function set_diagnostic_cache(namespace, bufnr, diagnostics) end ---@private -local function clear_diagnostic_cache(namespace, bufnr) - diagnostic_cache[bufnr][namespace] = nil -end - ----@private local function restore_extmarks(bufnr, last) for ns, extmarks in pairs(diagnostic_cache_extmarks[bufnr]) do local extmarks_current = vim.api.nvim_buf_get_extmarks(bufnr, ns, 0, -1, {details = true}) @@ -307,11 +298,6 @@ local function restore_extmarks(bufnr, last) if not found[extmark[1]] then local opts = extmark[4] opts.id = extmark[1] - -- HACK: end_row should be end_line - if opts.end_row then - opts.end_line = opts.end_row - opts.end_row = nil - end pcall(vim.api.nvim_buf_set_extmark, bufnr, ns, extmark[2], extmark[3], opts) end end @@ -377,6 +363,71 @@ local function clear_scheduled_display(namespace, bufnr) end ---@private +local function get_diagnostics(bufnr, opts, clamp) + opts = opts or {} + + local namespace = opts.namespace + local diagnostics = {} + + -- Memoized results of buf_line_count per bufnr + local buf_line_count = setmetatable({}, { + __index = function(t, k) + t[k] = vim.api.nvim_buf_line_count(k) + return rawget(t, k) + end, + }) + + ---@private + local function add(b, d) + if not opts.lnum or d.lnum == opts.lnum then + if clamp and vim.api.nvim_buf_is_loaded(b) then + local line_count = buf_line_count[b] - 1 + if (d.lnum > line_count or d.end_lnum > line_count or d.lnum < 0 or d.end_lnum < 0) then + d = vim.deepcopy(d) + d.lnum = math.max(math.min(d.lnum, line_count), 0) + d.end_lnum = math.max(math.min(d.end_lnum, line_count), 0) + end + end + table.insert(diagnostics, d) + end + end + + if namespace == nil and bufnr == nil then + for b, t in pairs(diagnostic_cache) do + for _, v in pairs(t) do + for _, diagnostic in pairs(v) do + add(b, diagnostic) + end + end + end + elseif namespace == nil then + bufnr = get_bufnr(bufnr) + for iter_namespace in pairs(diagnostic_cache[bufnr]) do + for _, diagnostic in pairs(diagnostic_cache[bufnr][iter_namespace]) do + add(bufnr, diagnostic) + end + end + elseif bufnr == nil then + for b, t in pairs(diagnostic_cache) do + for _, diagnostic in pairs(t[namespace] or {}) do + add(b, diagnostic) + end + end + else + bufnr = get_bufnr(bufnr) + for _, diagnostic in pairs(diagnostic_cache[bufnr][namespace] or {}) do + add(bufnr, diagnostic) + end + end + + if opts.severity then + diagnostics = filter_by_severity(opts.severity, diagnostics) + end + + return diagnostics +end + +---@private local function set_list(loclist, opts) opts = opts or {} local open = vim.F.if_nil(opts.open, true) @@ -386,7 +437,9 @@ local function set_list(loclist, opts) if loclist then bufnr = vim.api.nvim_win_get_buf(winnr) end - local diagnostics = M.get(bufnr, opts) + -- Don't clamp line numbers since the quickfix list can already handle line + -- numbers beyond the end of the buffer + local diagnostics = get_diagnostics(bufnr, opts, false) local items = M.toqflist(diagnostics) if loclist then vim.fn.setloclist(winnr, {}, ' ', { title = title, items = items }) @@ -394,21 +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") - end -end - ----@private ---- To (slightly) improve performance, modifies diagnostics in place. -local function clamp_line_numbers(bufnr, diagnostics) - local buf_line_count = vim.api.nvim_buf_line_count(bufnr) - if buf_line_count == 0 then - return - end - - for _, diagnostic in ipairs(diagnostics) do - diagnostic.lnum = math.max(math.min(diagnostic.lnum, buf_line_count - 1), 0) - diagnostic.end_lnum = math.max(math.min(diagnostic.end_lnum, buf_line_count - 1), 0) + vim.api.nvim_command(loclist and "lopen" or "botright copen") end end @@ -418,8 +457,7 @@ local function next_diagnostic(position, search_forward, bufnr, opts, namespace) bufnr = get_bufnr(bufnr) local wrap = vim.F.if_nil(opts.wrap, true) local line_count = vim.api.nvim_buf_line_count(bufnr) - local diagnostics = M.get(bufnr, vim.tbl_extend("keep", opts, {namespace = namespace})) - clamp_line_numbers(bufnr, diagnostics) + local diagnostics = get_diagnostics(bufnr, vim.tbl_extend("keep", opts, {namespace = namespace}), true) local line_diagnostics = diagnostic_lines(diagnostics) for i = 0, line_count do local offset = i * (search_forward and 1 or -1) @@ -431,13 +469,14 @@ local function next_diagnostic(position, search_forward, bufnr, opts, namespace) lnum = (lnum + line_count) % line_count end if line_diagnostics[lnum] and not vim.tbl_isempty(line_diagnostics[lnum]) then + local line_length = #vim.api.nvim_buf_get_lines(bufnr, lnum, lnum + 1, true)[1] local sort_diagnostics, is_next if search_forward then sort_diagnostics = function(a, b) return a.col < b.col end - is_next = function(diagnostic) return diagnostic.col > position[2] end + is_next = function(d) return math.min(d.col, line_length - 1) > position[2] end else sort_diagnostics = function(a, b) return a.col > b.col end - is_next = function(diagnostic) return diagnostic.col < position[2] end + is_next = function(d) return math.min(d.col, line_length - 1) < position[2] end end table.sort(line_diagnostics[lnum], sort_diagnostics) if i == 0 then @@ -465,26 +504,28 @@ local function diagnostic_move_pos(opts, pos) return end - -- Save position in the window's jumplist - vim.api.nvim_win_call(win_id, function() vim.cmd("normal! m'") end) - - vim.api.nvim_win_set_cursor(win_id, {pos[1] + 1, pos[2]}) + vim.api.nvim_win_call(win_id, function() + -- Save position in the window's jumplist + vim.cmd("normal! m'") + vim.api.nvim_win_set_cursor(win_id, {pos[1] + 1, pos[2]}) + -- Open folds under the cursor + vim.cmd("normal! zv") + end) if float then local float_opts = type(float) == "table" and float or {} vim.schedule(function() M.open_float( - vim.api.nvim_win_get_buf(win_id), - vim.tbl_extend("keep", float_opts, {scope="cursor"}) + vim.tbl_extend("keep", float_opts, { + bufnr = vim.api.nvim_win_get_buf(win_id), + scope = "cursor", + focus = false, + }) ) end) end end --- }}} - --- Public API {{{ - --- Configure diagnostic options globally or for a specific diagnostic --- namespace. --- @@ -511,15 +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: (string) Include the diagnostic source in virtual ---- text. One of "always" or "if_many". +--- * 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: @@ -537,26 +587,7 @@ end --- * priority: (number, default 10) Base priority to use for signs. When --- {severity_sort} is used, the priority of a sign is adjusted based on --- its severity. Otherwise, all signs use the same priority. ---- - float: Options for floating windows: ---- * severity: See |diagnostic-severity|. ---- * header: (string or table) String to use as the header for the floating ---- window. If a table, it is interpreted as a [text, hl_group] tuple. ---- Defaults to "Diagnostics:". ---- * source: (string) Include the diagnostic source in ---- the message. One of "always" or "if_many". ---- * 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. ---- * prefix: (function, string, or table) Prefix each diagnostic in the floating ---- window. If a function, it must have the signature (diagnostic, i, ---- total) -> (string, string), where {i} is the index of the diagnostic ---- being evaluated and {total} is the total number of diagnostics ---- displayed in the window. The function should return a string which ---- is prepended to each diagnostic in the window as well as an ---- (optional) highlight group which will be used to highlight the ---- prefix. If {prefix} is a table, it is interpreted as a [text, ---- hl_group] tuple as in |nvim_echo()|; otherwise, if {prefix} is a ---- string, it is prepended to each diagnostic in the window with no ---- highlight. +--- - float: Options for floating windows. See |vim.diagnostic.open_float()|. --- - update_in_insert: (default false) Update diagnostics in Insert mode (if false, --- diagnostics are updated on InsertLeave) --- - severity_sort: (default false) Sort diagnostics by severity. This affects the order in @@ -564,11 +595,12 @@ end --- are displayed before lower severities (e.g. ERROR is displayed before WARN). --- Options: --- * reverse: (boolean) Reverse sort order +--- ---@param namespace number|nil Update the options for the given namespace. When omitted, update the --- global diagnostic options. function M.config(opts, namespace) vim.validate { - opts = { opts, 't' }, + opts = { opts, 't', true }, namespace = { namespace, 'n', true }, } @@ -580,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 @@ -613,37 +648,39 @@ 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}, } + bufnr = get_bufnr(bufnr) + if vim.tbl_isempty(diagnostics) then - clear_diagnostic_cache(namespace, bufnr) + diagnostic_cache[bufnr][namespace] = nil else - if not diagnostic_cleanup[bufnr][namespace] then - diagnostic_cleanup[bufnr][namespace] = true - - -- Clean up our data when the buffer unloads. - vim.api.nvim_buf_attach(bufnr, false, { - on_detach = function(_, b) - clear_diagnostic_cache(namespace, b) - diagnostic_cleanup[b][namespace] = nil - end - }) - end set_diagnostic_cache(namespace, bufnr, diagnostics) end if vim.api.nvim_buf_is_loaded(bufnr) then - M.show(namespace, bufnr, diagnostics, opts) + M.show(namespace, bufnr, nil, opts) end - vim.api.nvim_command("doautocmd <nomodeline> User DiagnosticsChanged") + 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)) + ) + ) + end) end --- Get namespace metadata. --- ----@param ns number Diagnostic namespace +---@param namespace number Diagnostic namespace ---@return table Namespace metadata function M.get_namespace(namespace) vim.validate { namespace = { namespace, 'n' } } @@ -689,49 +726,7 @@ function M.get(bufnr, opts) opts = { opts, 't', true }, } - opts = opts or {} - - local namespace = opts.namespace - local diagnostics = {} - - ---@private - local function add(d) - if not opts.lnum or d.lnum == opts.lnum then - table.insert(diagnostics, d) - end - end - - if namespace == nil and bufnr == nil then - for _, t in pairs(diagnostic_cache) do - for _, v in pairs(t) do - for _, diagnostic in pairs(v) do - add(diagnostic) - end - end - end - elseif namespace == nil then - for iter_namespace in pairs(diagnostic_cache[bufnr]) do - for _, diagnostic in pairs(diagnostic_cache[bufnr][iter_namespace]) do - add(diagnostic) - end - end - elseif bufnr == nil then - for _, t in pairs(diagnostic_cache) do - for _, diagnostic in pairs(t[namespace] or {}) do - add(diagnostic) - end - end - else - for _, diagnostic in pairs(diagnostic_cache[bufnr][namespace] or {}) do - add(diagnostic) - end - end - - if opts.severity then - diagnostics = filter_by_severity(opts.severity, diagnostics) - end - - return diagnostics + return get_diagnostics(bufnr, opts, false) end --- Get the previous diagnostic closest to the cursor position. @@ -807,7 +802,9 @@ end --- - severity: See |diagnostic-severity|. --- - float: (boolean or table, default true) If "true", call |vim.diagnostic.open_float()| --- after moving. If a table, pass the table as the {opts} parameter to ---- |vim.diagnostic.open_float()|. +--- |vim.diagnostic.open_float()|. Unless overridden, the float will show +--- diagnostics at the new cursor position (as if "cursor" were passed to +--- the "scope" option). --- - win_id: (number, default 0) Window ID function M.goto_next(opts) return diagnostic_move_pos( @@ -821,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) @@ -884,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) @@ -913,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) @@ -932,19 +940,27 @@ 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 if opts.virtual_text.format then diagnostics = reformat_diagnostics(opts.virtual_text.format, diagnostics) end - if opts.virtual_text.source then - diagnostics = prefix_source(opts.virtual_text.source, diagnostics) + if + opts.virtual_text.source + and (opts.virtual_text.source ~= "if_many" or count_sources(bufnr) > 1) + then + diagnostics = prefix_source(diagnostics) end if opts.virtual_text.severity then severity = opts.virtual_text.severity @@ -1089,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 }, } @@ -1115,7 +1135,7 @@ function M.show(namespace, bufnr, diagnostics, opts) M.hide(namespace, bufnr) - diagnostics = diagnostics or M.get(bufnr, {namespace=namespace}) + diagnostics = diagnostics or get_diagnostics(bufnr, {namespace=namespace}, true) if not diagnostics or vim.tbl_isempty(diagnostics) then return @@ -1141,8 +1161,6 @@ function M.show(namespace, bufnr, diagnostics, opts) end end - clamp_line_numbers(bufnr, diagnostics) - for handler_name, handler in pairs(M.handlers) do if handler.show and opts[handler_name] then handler.show(namespace, bufnr, diagnostics, opts) @@ -1152,12 +1170,15 @@ end --- Show diagnostics in a floating window. --- ----@param bufnr number|nil Buffer number. Defaults to the current buffer. ---@param opts table|nil Configuration table with the same keys as --- |vim.lsp.util.open_floating_preview()| in addition to the following: +--- - bufnr: (number) Buffer number to show diagnostics from. +--- Defaults to the current buffer. --- - namespace: (number) Limit diagnostics to the given namespace ---- - scope: (string, default "buffer") Show diagnostics from the whole buffer ("buffer"), +--- - scope: (string, default "line") Show diagnostics from the whole buffer ("buffer"), --- the current cursor line ("line"), or the current cursor position ("cursor"). +--- Shorthand versions are also accepted ("c" for "cursor", "l" for "line", "b" +--- for "buffer"). --- - pos: (number or table) If {scope} is "line" or "cursor", use this position rather --- than the cursor position. If a number, interpreted as a line number; --- otherwise, a (row, col) tuple. @@ -1168,23 +1189,54 @@ end --- - header: (string or table) String to use as the header for the floating window. If a --- table, it is interpreted as a [text, hl_group] tuple. Overrides the setting --- from |vim.diagnostic.config()|. ---- - source: (string) Include the diagnostic source in the message. One of "always" or ---- "if_many". Overrides the setting from |vim.diagnostic.config()|. +--- - source: (boolean or string) Include the diagnostic source in the message. +--- Use "if_many" to only show sources if there is more than one source of +--- diagnostics in the buffer. Otherwise, any truthy value means to always show +--- the diagnostic source. Overrides the setting from +--- |vim.diagnostic.config()|. --- - 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. --- Overrides the setting from |vim.diagnostic.config()|. ---- - prefix: (function, string, or table) Prefix each diagnostic in the floating window. +--- - prefix: (function, string, or table) Prefix each diagnostic in the floating +--- window. If a function, it must have the signature (diagnostic, i, +--- total) -> (string, string), where {i} is the index of the diagnostic +--- being evaluated and {total} is the total number of diagnostics +--- displayed in the window. The function should return a string which +--- is prepended to each diagnostic in the window as well as an +--- (optional) highlight group which will be used to highlight the +--- prefix. If {prefix} is a table, it is interpreted as a [text, +--- hl_group] tuple as in |nvim_echo()|; otherwise, if {prefix} is a +--- string, it is prepended to each diagnostic in the window with no +--- highlight. --- Overrides the setting from |vim.diagnostic.config()|. ---@return tuple ({float_bufnr}, {win_id}) -function M.open_float(bufnr, opts) - vim.validate { - bufnr = { bufnr, 'n', true }, - opts = { opts, 't', true }, - } +function M.open_float(opts, ...) + -- Support old (bufnr, opts) signature + local bufnr + if opts == nil or type(opts) == "number" then + bufnr = opts + opts = ... + else + vim.validate { + opts = { opts, 't', true }, + } + end opts = opts or {} - bufnr = get_bufnr(bufnr) - local scope = opts.scope or "buffer" + bufnr = get_bufnr(bufnr or opts.bufnr) + + do + -- Resolve options with user settings from vim.diagnostic.config + -- Unlike the other decoration functions (e.g. set_virtual_text, set_signs, etc.) `open_float` + -- does not have a dedicated table for configuration options; instead, the options are mixed in + -- with its `opts` table which also includes "keyword" parameters. So we create a dedicated + -- options table that inherits missing keys from the global configuration before resolving. + local t = global_diagnostic_options.float + local float_opts = vim.tbl_extend("keep", opts, type(t) == "table" and t or {}) + opts = get_resolved_options({ float = float_opts }, nil, bufnr).float + end + + local scope = ({l = "line", c = "cursor", b = "buffer"})[opts.scope] or opts.scope or "line" local lnum, col if scope == "line" or scope == "cursor" then if not opts.pos then @@ -1202,19 +1254,7 @@ function M.open_float(bufnr, opts) error("Invalid value for option 'scope'") end - do - -- Resolve options with user settings from vim.diagnostic.config - -- Unlike the other decoration functions (e.g. set_virtual_text, set_signs, etc.) `open_float` - -- does not have a dedicated table for configuration options; instead, the options are mixed in - -- with its `opts` table which also includes "keyword" parameters. So we create a dedicated - -- options table that inherits missing keys from the global configuration before resolving. - local t = global_diagnostic_options.float - local float_opts = vim.tbl_extend("keep", opts, type(t) == "table" and t or {}) - opts = get_resolved_options({ float = float_opts }, nil, bufnr).float - end - - local diagnostics = M.get(bufnr, opts) - clamp_line_numbers(bufnr, diagnostics) + local diagnostics = get_diagnostics(bufnr, opts, true) if scope == "line" then diagnostics = vim.tbl_filter(function(d) @@ -1266,8 +1306,8 @@ function M.open_float(bufnr, opts) diagnostics = reformat_diagnostics(opts.format, diagnostics) end - if opts.source then - diagnostics = prefix_source(opts.source, diagnostics) + if opts.source and (opts.source ~= "if_many" or count_sources(bufnr) > 1) then + diagnostics = prefix_source(diagnostics) end local prefix_opt = if_nil(opts.prefix, (scope == "cursor" and #diagnostics <= 1) and "" or function(_, i) @@ -1334,16 +1374,23 @@ function M.reset(namespace, bufnr) bufnr = {bufnr, 'n', true}, } - local buffers = bufnr and {bufnr} or vim.tbl_keys(diagnostic_cache) + local buffers = bufnr and {get_bufnr(bufnr)} or vim.tbl_keys(diagnostic_cache) for _, iter_bufnr in ipairs(buffers) do local namespaces = namespace and {namespace} or vim.tbl_keys(diagnostic_cache[iter_bufnr]) for _, iter_namespace in ipairs(namespaces) do - clear_diagnostic_cache(iter_namespace, iter_bufnr) + diagnostic_cache[iter_bufnr][iter_namespace] = nil M.hide(iter_namespace, iter_bufnr) end - end - vim.api.nvim_command("doautocmd <nomodeline> User DiagnosticsChanged") + 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 --- Add all diagnostics to the quickfix list. @@ -1508,7 +1555,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 @@ -1539,7 +1592,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 @@ -1563,6 +1622,4 @@ function M.fromqflist(list) return diagnostics end --- }}} - return M diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua new file mode 100644 index 0000000000..603f9f854a --- /dev/null +++ b/runtime/lua/vim/filetype.lua @@ -0,0 +1,1641 @@ +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] or "" +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", + 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", + cook = "cook", + 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", + 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", + elv = "elvish", + 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", + gdb = "gdb", + 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", + 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", + org = "org", + org_archive = "org", + 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", + phpt = "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", + 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", + quark = "supercollider", + 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, + sc = function() vim.fn["dist#ft#FTsc"]() end, + scd = function() vim.fn["dist#ft#FTscd"]() 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", + [".gdbearlyinit"] = "gdb", + gdbearlyinit = "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", + Vagrantfile = "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, + [".*%.[Cc][Ff][Gg]"] = function() vim.fn["dist#ft#FTcfg"]() end, + [".*%.[Dd][Aa][Tt]"] = function() vim.fn["dist#ft#FTdat"]() end, + [".*%.[Mm][Oo][Dd]"] = function() vim.fn["dist#ft#FTmod"]() end, + [".*%.[Ss][Rr][Cc]"] = function() vim.fn["dist#ft#FTsrc"]() end, + [".*%.[Ss][Uu][Bb]"] = "krl", + [".*%.[Pp][Rr][Gg]"] = function() vim.fn["dist#ft#FTprg"]() end, + [".*%.[Ss][Yy][Ss]"] = function() vim.fn["dist#ft#FTsys"]() end, + -- Neovim only + [".*/queries/.*%.scm"] = "query", -- tree-sitter queries + -- 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..0986d21196 --- /dev/null +++ b/runtime/lua/vim/keymap.lua @@ -0,0 +1,145 @@ +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. +--- If a Lua function and `opts.expr == true`, returning `nil` is +--- equivalent to an empty string. +-- +---@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 then + local user_rhs = rhs + rhs = function () + local res = user_rhs() + if res == nil then + -- TODO(lewis6991): Handle this in C? + return '' + elseif opts.replace_keycodes ~= false then + return vim.api.nvim_replace_termcodes(res, true, true, true) + else + return res + end + 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 88dcb38dd1..7bbe3637db 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -115,6 +115,13 @@ local format_line_ending = { ["mac"] = '\r', } +---@private +---@param bufnr (number) +---@returns (string) +local function buf_get_line_ending(bufnr) + return format_line_ending[nvim_buf_get_option(bufnr, 'fileformat')] or '\n' +end + local client_index = 0 ---@private --- Returns a new, unused client id. @@ -130,12 +137,6 @@ local all_buffer_active_clients = {} local uninitialized_clients = {} ---@private ---- Invokes a function for each LSP client attached to the buffer {bufnr}. ---- ----@param bufnr (Number) of buffer ----@param fn (function({client}, {client_id}, {bufnr}) Function to run on ----each client attached to that buffer. ----@param restrict_client_ids table list of client ids on which to restrict function application. local function for_each_buffer_client(bufnr, fn, restrict_client_ids) validate { fn = { fn, 'f' }; @@ -236,7 +237,6 @@ local function validate_client_config(config) config = { config, 't' }; } validate { - root_dir = { config.root_dir, optional_validator(is_dir), "directory" }; handlers = { config.handlers, "t", true }; capabilities = { config.capabilities, "t", true }; cmd_cwd = { config.cmd_cwd, optional_validator(is_dir), "directory" }; @@ -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) @@ -278,9 +278,10 @@ end ---@param bufnr (number) Buffer handle, or 0 for current. ---@returns Buffer text as string. local function buf_get_full_text(bufnr) - local text = table.concat(nvim_buf_get_lines(bufnr, 0, -1, true), '\n') + local line_ending = buf_get_line_ending(bufnr) + local text = table.concat(nvim_buf_get_lines(bufnr, 0, -1, true), line_ending) if nvim_buf_get_option(bufnr, 'eol') then - text = text .. '\n' + text = text .. line_ending end return text end @@ -289,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 @@ -305,67 +306,138 @@ local function once(fn) end end - local changetracking = {} do --@private --- client_id → state --- --- state - --- pending_change?: function that the timer starts to trigger didChange - --- pending_changes: list of tables with the pending changesets; for incremental_sync only --- use_incremental_sync: bool - --- buffers?: table (bufnr → lines); for incremental sync only + --- 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 + --- --- 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, bufnr) local state = state_by_client[client.id] - if state then - changetracking._reset_timer(state) - if state.buffers then - state.buffers[bufnr] = nil + 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 - function changetracking.prepare(bufnr, firstline, lastline, new_lastline, changedtick) - 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 line_ending = format_line_ending[vim.api.nvim_buf_get_option(0, 'fileformat')] + ---@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, 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 or '\n') - 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() @@ -379,65 +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 = changedtick; - }; - 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 - table.insert(state.pending_changes, 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 - local contentChanges - if state.use_incremental_sync then - contentChanges = state.pending_changes - state.pending_changes = {} - else - 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 = changedtick; - }; - contentChanges = contentChanges + 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. - function changetracking.flush(client) + ---@private + 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 @@ -475,7 +550,8 @@ local function text_document_did_open_handler(bufnr, client) -- Protect against a race where the buffer disappears -- between `did_open_handler` and the scheduled function firing. if vim.api.nvim_buf_is_valid(bufnr) then - vim.lsp.diagnostic.redraw(bufnr, client.id) + local namespace = vim.lsp.diagnostic.get_namespace(client.id) + vim.diagnostic.show(namespace, bufnr) end end) end @@ -545,6 +621,12 @@ end --- --- - {handlers} (table): The handlers used by the client as described in |lsp-handler|. --- +--- - {requests} (table): The current pending requests in flight +--- to the server. Entries are key-value pairs with the key +--- being the request ID while the value is a table with `type`, +--- `bufnr`, and `method` key-value pairs. `type` is either "pending" +--- for an active request, or "cancel" for a cancel request. +--- --- - {config} (table): copy of the table that was passed by the user --- to |vim.lsp.start_client()|. --- @@ -566,12 +648,10 @@ end -- --- Starts and initializes a client with the given configuration. --- ---- Parameters `cmd` and `root_dir` are required. +--- Parameter `cmd` is required. --- --- The following parameters describe fields in the {config} table. --- ----@param root_dir: (string) Directory where the LSP server will base ---- its rootUri on initialization. --- ---@param cmd: (required, string or list treated like |jobstart()|) Base command --- that initiates the LSP client. @@ -587,6 +667,11 @@ end --- { "PRODUCTION=true"; "TEST=123"; PORT = 8080; HOST = "0.0.0.0"; } --- </pre> --- +---@param workspace_folders (table) List of workspace folders passed to the +--- language server. For backwards compatibility rootUri and rootPath will be +--- derived from the first workspace folder in this list. See `workspaceFolders` in +--- the LSP spec. +--- ---@param capabilities Map overriding the default capabilities defined by --- |vim.lsp.protocol.make_client_capabilities()|, passed to the language --- server on initialization. Hint: use make_client_capabilities() and modify @@ -603,17 +688,13 @@ end --- ---@param commands table Table that maps string of clientside commands to user-defined functions. --- Commands passed to start_client take precedence over the global command registry. Each key ---- must be a unique comand name, and the value is a function which is called if any LSP action +--- must be a unique command name, and the value is a function which is called if any LSP action --- (code action, code lenses, ...) triggers the command. --- ---@param init_options Values to pass in the initialization request --- as `initializationOptions`. See `initialize` in the LSP spec. --- ---@param name (string, default=client-id) Name in log messages. --- ----@param workspace_folders (table) List of workspace folders passed to the ---- language server. Defaults to root_dir if not set. See `workspaceFolders` in ---- the LSP spec --- ---@param get_language_id function(bufnr, filetype) -> language ID as string. --- Defaults to the filetype. @@ -625,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 @@ -661,8 +742,13 @@ end --- notifications to the server by the given number in milliseconds. No debounce --- occurs if nil --- - exit_timeout (number, default 500): Milliseconds to wait for server to --- exit cleanly after sending the 'shutdown' request before sending kill -15. --- If set to false, nvim exits immediately after sending the 'shutdown' request to the server. +--- exit cleanly after sending the 'shutdown' request before sending kill -15. +--- If set to false, nvim exits immediately after sending the 'shutdown' request to the server. +--- +---@param root_dir string Directory where the LSP +--- server will base its workspaceFolders, rootUri, and rootPath +--- on initialization. +--- ---@returns Client id. |vim.lsp.get_client_by_id()| Note: client may not be --- fully initialized. Use `on_init` to do any actions once --- the client has been initialized. @@ -732,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)) @@ -752,6 +838,10 @@ function lsp.start_client(config) ---@param code (number) exit code of the process ---@param signal (number) the signal used to terminate (if any) function dispatch.on_exit(code, signal) + if config.on_exit then + pcall(config.on_exit, code, signal, client_id) + end + active_clients[client_id] = nil uninitialized_clients[client_id] = nil @@ -767,10 +857,6 @@ function lsp.start_client(config) vim.notify(msg, vim.log.levels.WARN) end) end - - if config.on_exit then - pcall(config.on_exit, code, signal, client_id) - end end -- Start the RPC client. @@ -779,6 +865,9 @@ function lsp.start_client(config) env = config.cmd_env; }) + -- Return nil if client fails to start + if not rpc then return end + local client = { id = client_id; name = name; @@ -805,11 +894,24 @@ function lsp.start_client(config) } local version = vim.version() - if config.root_dir and not config.workspace_folders then - config.workspace_folders = {{ - uri = vim.uri_from_fname(config.root_dir); - name = string.format("%s", config.root_dir); - }}; + local workspace_folders + local root_uri + local root_path + if config.workspace_folders or config.root_dir then + if config.root_dir and not config.workspace_folders then + workspace_folders = {{ + uri = vim.uri_from_fname(config.root_dir); + name = string.format("%s", config.root_dir); + }}; + else + workspace_folders = config.workspace_folders + end + root_uri = workspace_folders[1].uri + root_path = vim.uri_to_fname(root_uri) + else + workspace_folders = nil + root_uri = nil + root_path = nil end local initialize_params = { @@ -827,10 +929,15 @@ function lsp.start_client(config) -- The rootPath of the workspace. Is null if no folder is open. -- -- @deprecated in favour of rootUri. - rootPath = config.root_dir; + rootPath = root_path or vim.NIL; -- The rootUri of the workspace. Is null if no folder is open. If both -- `rootPath` and `rootUri` are set `rootUri` wins. - rootUri = config.root_dir and vim.uri_from_fname(config.root_dir); + rootUri = root_uri or vim.NIL; + -- The workspace folders configured in the client when the server starts. + -- This property is only available if the client supports workspace folders. + -- It can be `null` if the client supports workspace folders but none are + -- configured. + workspaceFolders = workspace_folders or vim.NIL; -- User provided initialization options. initializationOptions = config.init_options; -- The capabilities provided by the client (editor or tool) @@ -838,21 +945,6 @@ function lsp.start_client(config) -- The initial trace setting. If omitted trace is disabled ("off"). -- trace = "off" | "messages" | "verbose"; trace = valid_traces[config.trace] or 'off'; - -- The workspace folders configured in the client when the server starts. - -- This property is only available if the client supports workspace folders. - -- It can be `null` if the client supports workspace folders but none are - -- configured. - -- - -- Since 3.6.0 - -- workspaceFolders?: WorkspaceFolder[] | null; - -- export interface WorkspaceFolder { - -- -- The associated URI for this workspace folder. - -- uri - -- -- The name of the workspace folder. Used to refer to this - -- -- workspace folder in the user interface. - -- name - -- } - workspaceFolders = config.workspace_folders, } if config.before_init then -- TODO(ashkan) handle errors here. @@ -865,7 +957,9 @@ function lsp.start_client(config) rpc.notify('initialized', vim.empty_dict()) client.initialized = true uninitialized_clients[client_id] = nil - client.workspaceFolders = initialize_params.workspaceFolders + client.workspace_folders = workspace_folders + -- 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 -- when to send certain events to clients. @@ -924,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) @@ -981,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 @@ -1007,7 +1103,7 @@ function lsp.start_client(config) return rpc.notify("$/cancelRequest", { id = id }) end - -- Track this so that we can escalate automatically if we've alredy tried a + -- Track this so that we can escalate automatically if we've already tried a -- graceful shutdown local graceful_shutdown_failed = false ---@private @@ -1079,12 +1175,12 @@ local text_document_did_change_handler do text_document_did_change_handler = function(_, bufnr, changedtick, firstline, lastline, new_lastline) - -- Don't do anything if there are no clients attached. + -- Detach (nvim_buf_attach) via returning True to on_lines if no clients are attached if tbl_isempty(all_buffer_active_clients[bufnr] or {}) then - return + return true end util.buf_versions[bufnr] = changedtick - local compute_change_and_notify = changetracking.prepare(bufnr, firstline, lastline, new_lastline, changedtick) + local compute_change_and_notify = changetracking.prepare(bufnr, firstline, lastline, new_lastline) for_each_buffer_client(bufnr, compute_change_and_notify) end end @@ -1098,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 = { @@ -1123,6 +1219,12 @@ function lsp.buf_attach_client(bufnr, client_id) client_id = {client_id, 'n'}; } bufnr = resolve_bufnr(bufnr) + if not vim.api.nvim_buf_is_loaded(bufnr) then + local _ = log.warn() and log.warn( + string.format("buf_attach_client called on unloaded buffer (id: %d): ", bufnr) + ) + return false + end local buffer_client_ids = all_buffer_active_clients[bufnr] -- This is our first time attaching to this buffer. if not buffer_client_ids then @@ -1143,6 +1245,7 @@ function lsp.buf_attach_client(bufnr, client_id) on_reload = function() local params = { textDocument = { uri = uri; } } for_each_buffer_client(bufnr, function(client, _) + changetracking.reset_buf(client, bufnr) if client.resolved_capabilities.text_document_open_close then client.notify('textDocument/didClose', params) end @@ -1152,10 +1255,10 @@ function lsp.buf_attach_client(bufnr, client_id) on_detach = function() local params = { textDocument = { uri = uri; } } for_each_buffer_client(bufnr, function(client, _) + changetracking.reset_buf(client, bufnr) if client.resolved_capabilities.text_document_open_close then client.notify('textDocument/didClose', params) end - changetracking.reset_buf(client, bufnr) end) util.buf_versions[bufnr] = nil all_buffer_active_clients[bufnr] = nil @@ -1180,6 +1283,50 @@ function lsp.buf_attach_client(bufnr, client_id) return true end +--- Detaches client from the specified buffer. +--- Note: While the server is notified that the text document (buffer) +--- was closed, it is still able to send notifications should it ignore this notification. +--- +---@param bufnr number Buffer handle, or 0 for current +---@param client_id number Client id +function lsp.buf_detach_client(bufnr, client_id) + validate { + bufnr = {bufnr, 'n', true}; + client_id = {client_id, 'n'}; + } + bufnr = resolve_bufnr(bufnr) + + local client = lsp.get_client_by_id(client_id) + if not client or not client.attached_buffers[bufnr] then + vim.notify( + string.format('Buffer (id: %d) is not attached to client (id: %d). Cannot detach.', client_id, bufnr) + ) + return + end + + changetracking.reset_buf(client, bufnr) + + if client.resolved_capabilities.text_document_open_close then + local uri = vim.uri_from_bufnr(bufnr) + local params = { textDocument = { uri = uri; } } + client.notify('textDocument/didClose', params) + end + + client.attached_buffers[bufnr] = nil + util.buf_versions[bufnr] = nil + + all_buffer_active_clients[bufnr][client_id] = nil + if #vim.tbl_keys(all_buffer_active_clients[bufnr]) == 0 then + all_buffer_active_clients[bufnr] = nil + end + + local namespace = vim.lsp.diagnostic.get_namespace(client_id) + vim.diagnostic.reset(namespace, bufnr) + + vim.notify(string.format('Detached buffer (id: %d) from client (id: %d)', bufnr, client_id)) + +end + --- Checks if a buffer is attached for a particular client. --- ---@param bufnr (number) Buffer handle, or 0 for current @@ -1190,7 +1337,7 @@ end --- Gets a client by id, or nil if the id is invalid. --- The returned client may not yet be fully initialized. --- +--- ---@param client_id number client id --- ---@returns |vim.lsp.client| object, or nil @@ -1199,7 +1346,7 @@ function lsp.get_client_by_id(client_id) end --- Returns list of buffers attached to client_id. --- +--- ---@param client_id number client id ---@returns list of buffer ids function lsp.get_buffers_by_client_id(client_id) @@ -1269,6 +1416,7 @@ function lsp._vim_exit_handler() local poll_time = 50 + ---@private local function check_clients_closed() for client_id, timeout in pairs(timeouts) do timeouts[client_id] = timeout - poll_time @@ -1303,8 +1451,8 @@ nvim_command("autocmd VimLeavePre * lua vim.lsp._vim_exit_handler()") ---@param method (string) LSP method name ---@param params (optional, table) Parameters to send to the server ---@param handler (optional, function) See |lsp-handler| --- If nil, follows resolution strategy defined in |lsp-handler-configuration| --- +--- If nil, follows resolution strategy defined in |lsp-handler-configuration| +--- ---@returns 2-tuple: --- - Map of client-id:request-id pairs for all successful requests. --- - Function which can be used to cancel all the requests. You could instead @@ -1450,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 @@ -1458,11 +1606,7 @@ local function adjust_start_col(lnum, line, items, encoding) end end if min_start_char then - if encoding == 'utf-8' then - return min_start_char - else - return vim.str_byteindex(line, min_start_char, encoding == 'utf-16') - end + return util._str_byteindex_enc(line, min_start_char, encoding) else return nil end @@ -1545,7 +1689,7 @@ end --- --- Currently only supports a single client. This can be set via --- `setlocal formatexpr=v:lua.vim.lsp.formatexpr()` but will typically or in `on_attach` ---- via `vim.api.nvim_buf_set_option(bufnr, 'formatexpr', 'v:lua.vim.lsp.formatexpr(#{timeout_ms:250})')`. +--- via ``vim.api.nvim_buf_set_option(bufnr, 'formatexpr', 'v:lua.vim.lsp.formatexpr(#{timeout_ms:250})')``. --- ---@param opts table options for customizing the formatting expression which takes the --- following optional keys: @@ -1575,9 +1719,9 @@ function lsp.formatexpr(opts) local client_results = vim.lsp.buf_request_sync(0, "textDocument/rangeFormatting", params, timeout_ms) -- Apply the text edits from one and only one of the clients. - for _, response in pairs(client_results) do + for client_id, response in pairs(client_results) do if response.result then - vim.lsp.util.apply_text_edits(response.result, 0) + vim.lsp.util.apply_text_edits(response.result, 0, vim.lsp.get_client_by_id(client_id).offset_encoding) return 0 end end @@ -1627,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. --- @@ -1655,7 +1799,17 @@ function lsp.get_log_path() return log.get_filename() end ---- Call {fn} for every client attached to {bufnr} +--- Invokes a function for each LSP client attached to a buffer. +--- +---@param bufnr number Buffer number +---@param fn function Function to run on each client attached to buffer +--- {bufnr}. The function takes the client, client ID, and +--- buffer number as arguments. Example: +--- <pre> +--- vim.lsp.for_each_buffer_client(0, function(client, client_id, bufnr) +--- print(vim.inspect(client)) +--- end) +--- </pre> function lsp.for_each_buffer_client(bufnr, fn) return for_each_buffer_client(bufnr, fn) end @@ -1714,11 +1868,11 @@ end --- using `workspace/executeCommand`. --- --- The first argument to the function will be the `Command`: --- Command --- title: String --- command: String --- arguments?: any[] --- +--- Command +--- title: String +--- command: String +--- arguments?: any[] +--- --- The second argument is the `ctx` of |lsp-handler| lsp.commands = setmetatable({}, { __newindex = function(tbl, key, value) diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua index 747d761730..eb7ec579f1 100644 --- a/runtime/lua/vim/lsp/buf.lua +++ b/runtime/lua/vim/lsp/buf.lua @@ -116,31 +116,30 @@ end --- asks the user to select one. -- ---@returns The client that the user selected or nil -local function select_client(method) - local clients = vim.tbl_values(vim.lsp.buf_get_clients()); - clients = vim.tbl_filter(function (client) +local function select_client(method, on_choice) + validate { + on_choice = { on_choice, 'function', false }, + } + local clients = vim.tbl_values(vim.lsp.buf_get_clients()) + clients = vim.tbl_filter(function(client) return client.supports_method(method) end, clients) -- better UX when choices are always in the same order (between restarts) - table.sort(clients, function (a, b) return a.name < b.name end) + table.sort(clients, function(a, b) + return a.name < b.name + end) if #clients > 1 then - local choices = {} - for k,v in pairs(clients) do - table.insert(choices, string.format("%d %s", k, v.name)) - end - local user_choice = vim.fn.confirm( - "Select a language server:", - table.concat(choices, "\n"), - 0, - "Question" - ) - if user_choice == 0 then return nil end - return clients[user_choice] + vim.ui.select(clients, { + prompt = 'Select a language server:', + format_item = function(client) + return client.name + end, + }, on_choice) elseif #clients < 1 then - return nil + on_choice(nil) else - return clients[1] + on_choice(clients[1]) end end @@ -152,11 +151,15 @@ end -- ---@see https://microsoft.github.io/language-server-protocol/specification#textDocument_formatting function M.formatting(options) - local client = select_client("textDocument/formatting") - if client == nil then return end - local params = util.make_formatting_params(options) - return client.request("textDocument/formatting", params, nil, vim.api.nvim_get_current_buf()) + local bufnr = vim.api.nvim_get_current_buf() + select_client('textDocument/formatting', function(client) + if client == nil then + return + end + + return client.request('textDocument/formatting', params, nil, bufnr) + end) end --- Performs |vim.lsp.buf.formatting()| synchronously. @@ -165,23 +168,27 @@ end --- saved. {timeout_ms} is passed on to |vim.lsp.buf_request_sync()|. Example: --- --- <pre> ---- vim.api.nvim_command[[autocmd BufWritePre <buffer> lua vim.lsp.buf.formatting_sync()]] +--- autocmd BufWritePre <buffer> lua vim.lsp.buf.formatting_sync() --- </pre> --- ---@param options Table with valid `FormattingOptions` entries ---@param timeout_ms (number) Request timeout ---@see |vim.lsp.buf.formatting_seq_sync| function M.formatting_sync(options, timeout_ms) - local client = select_client("textDocument/formatting") - if client == nil then return end - 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) - elseif err then - vim.notify("vim.lsp.buf.formatting_sync: " .. err, vim.log.levels.WARN) - end + local bufnr = vim.api.nvim_get_current_buf() + select_client('textDocument/formatting', function(client) + if client == nil then + return + end + + 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, client.offset_encoding) + elseif err then + vim.notify('vim.lsp.buf.formatting_sync: ' .. err, vim.log.levels.WARN) + end + end) end --- Formats the current buffer by sequentially requesting formatting from attached clients. @@ -202,6 +209,7 @@ end ---the remaining clients in the order as they occur in the `order` list. function M.formatting_seq_sync(options, timeout_ms, order) local clients = vim.tbl_values(vim.lsp.buf_get_clients()); + local bufnr = vim.api.nvim_get_current_buf() -- sort the clients according to `order` for _, client_name in pairs(order or {}) do @@ -220,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) + 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 @@ -236,12 +244,15 @@ end ---@param end_pos ({number, number}, optional) mark-indexed position. ---Defaults to the end of the last visual selection. function M.range_formatting(options, start_pos, end_pos) - local client = select_client("textDocument/rangeFormatting") - if client == nil then return end - local params = util.make_given_range_params(start_pos, end_pos) params.options = util.make_formatting_params(options).options - return client.request("textDocument/rangeFormatting", params) + select_client('textDocument/rangeFormatting', function(client) + if client == nil then + return + end + + return client.request('textDocument/rangeFormatting', params) + end) end --- Renames all references to the symbol under the cursor. @@ -261,6 +272,7 @@ function M.rename(new_name) request('textDocument/rename', params) end + ---@private local function prepare_rename(err, result) if err == nil and result == nil then vim.notify('nothing to rename', vim.log.levels.INFO) @@ -369,7 +381,7 @@ end function M.list_workspace_folders() local workspace_folders = {} for _, client in pairs(vim.lsp.buf_get_clients()) do - for _, folder in pairs(client.workspaceFolders or {}) do + for _, folder in pairs(client.workspace_folders or {}) do table.insert(workspace_folders, folder.name) end end @@ -389,7 +401,7 @@ function M.add_workspace_folder(workspace_folder) local params = util.make_workspace_params({{uri = vim.uri_from_fname(workspace_folder); name = workspace_folder}}, {{}}) for _, client in pairs(vim.lsp.buf_get_clients()) do local found = false - for _, folder in pairs(client.workspaceFolders or {}) do + for _, folder in pairs(client.workspace_folders or {}) do if folder.name == workspace_folder then found = true print(workspace_folder, "is already part of this workspace") @@ -398,10 +410,10 @@ function M.add_workspace_folder(workspace_folder) end if not found then vim.lsp.buf_notify(0, 'workspace/didChangeWorkspaceFolders', params) - if not client.workspaceFolders then - client.workspaceFolders = {} + if not client.workspace_folders then + client.workspace_folders = {} end - table.insert(client.workspaceFolders, params.event.added[1]) + table.insert(client.workspace_folders, params.event.added[1]) end end end @@ -415,10 +427,10 @@ function M.remove_workspace_folder(workspace_folder) if not (workspace_folder and #workspace_folder > 0) then return end local params = util.make_workspace_params({{}}, {{uri = vim.uri_from_fname(workspace_folder); name = workspace_folder}}) for _, client in pairs(vim.lsp.buf_get_clients()) do - for idx, folder in pairs(client.workspaceFolders) do + for idx, folder in pairs(client.workspace_folders) do if folder.name == workspace_folder then vim.lsp.buf_notify(0, 'workspace/didChangeWorkspaceFolders', params) - client.workspaceFolders[idx] = nil + client.workspace_folders[idx] = nil return end end @@ -435,18 +447,21 @@ 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> ---- vim.api.nvim_command [[autocmd CursorHold <buffer> lua vim.lsp.buf.document_highlight()]] ---- vim.api.nvim_command [[autocmd CursorHoldI <buffer> lua vim.lsp.buf.document_highlight()]] ---- vim.api.nvim_command [[autocmd CursorMoved <buffer> lua vim.lsp.buf.clear_references()]] +--- autocmd CursorHold <buffer> lua vim.lsp.buf.document_highlight() +--- autocmd CursorHoldI <buffer> lua vim.lsp.buf.document_highlight() +--- autocmd CursorMoved <buffer> lua vim.lsp.buf.clear_references() --- </pre> --- --- Note: Usage of |vim.lsp.buf.document_highlight()| requires the following highlight groups @@ -491,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 @@ -615,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 1e6f83c1ba..614d83f565 100644 --- a/runtime/lua/vim/lsp/diagnostic.lua +++ b/runtime/lua/vim/lsp/diagnostic.lua @@ -104,8 +104,10 @@ local function diagnostic_lsp_to_vim(diagnostics, bufnr, client_id) severity = severity_lsp_to_vim(diagnostic.severity), message = diagnostic.message, source = diagnostic.source, + code = diagnostic.code, user_data = { lsp = { + -- usage of user_data.lsp.code is deprecated in favor of the top-level code field code = diagnostic.code, codeDescription = diagnostic.codeDescription, tags = diagnostic.tags, @@ -120,7 +122,8 @@ end ---@private local function diagnostic_vim_to_lsp(diagnostics) return vim.tbl_map(function(diagnostic) - return vim.tbl_extend("error", { + return vim.tbl_extend("keep", { + -- "keep" the below fields over any duplicate fields in diagnostic.user_data.lsp range = { start = { line = diagnostic.lnum, @@ -134,6 +137,7 @@ local function diagnostic_vim_to_lsp(diagnostics) severity = severity_vim_to_lsp(diagnostic.severity), message = diagnostic.message, source = diagnostic.source, + code = diagnostic.code, }, diagnostic.user_data and (diagnostic.user_data.lsp or {}) or {}) end, diagnostics) end @@ -153,19 +157,6 @@ function M.get_namespace(client_id) return _client_namespaces[client_id] end ---- Save diagnostics to the current buffer. ---- ---- Handles saving diagnostics from multiple clients in the same buffer. ----@param diagnostics Diagnostic[] ----@param bufnr number ----@param client_id number ----@private -function M.save(diagnostics, bufnr, client_id) - local namespace = M.get_namespace(client_id) - vim.diagnostic.set(namespace, bufnr, diagnostic_lsp_to_vim(diagnostics, bufnr, client_id)) -end --- }}} - --- |lsp-handler| for the method "textDocument/publishDiagnostics" --- --- See |vim.diagnostic.config()| for configuration options. Handler-specific @@ -181,8 +172,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, @@ -220,12 +211,9 @@ function M.on_publish_diagnostics(_, result, ctx, config) end vim.diagnostic.set(namespace, bufnr, diagnostic_lsp_to_vim(diagnostics, bufnr, client_id)) - - -- Keep old autocmd for back compat. This should eventually be removed. - vim.api.nvim_command("doautocmd <nomodeline> User LspDiagnosticsChanged") end ---- Clear diagnotics and diagnostic cache. +--- Clear diagnostics and diagnostic cache. --- --- Diagnostic producers should prefer |vim.diagnostic.reset()|. However, --- this method signature is still used internally in some parts of the LSP @@ -248,6 +236,23 @@ end -- Deprecated Functions {{{ + +--- Save diagnostics to the current buffer. +--- +---@deprecated Prefer |vim.diagnostic.set()| +--- +--- Handles saving diagnostics from multiple clients in the same buffer. +---@param diagnostics Diagnostic[] +---@param bufnr number +---@param client_id number +---@private +function M.save(diagnostics, bufnr, client_id) + 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 +-- }}} + --- Get all diagnostics for clients --- ---@deprecated Prefer |vim.diagnostic.get()| @@ -256,6 +261,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.notify_once('vim.lsp.diagnostic.get_all is deprecated. See :h deprecated', vim.log.levels.WARN) local result = {} local namespace if client_id then @@ -277,6 +283,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.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 = {} @@ -338,6 +345,7 @@ end ---@param severity DiagnosticSeverity ---@param client_id number the client id function M.get_count(bufnr, severity, client_id) + 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 @@ -354,6 +362,7 @@ end ---@param opts table See |vim.lsp.diagnostic.goto_next()| ---@return table Previous diagnostic function M.get_prev(opts) + 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) @@ -371,6 +380,7 @@ end ---@param opts table See |vim.lsp.diagnostic.goto_next()| ---@return table Previous diagnostic position function M.get_prev_pos(opts) + 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) @@ -387,6 +397,7 @@ end --- ---@param opts table See |vim.lsp.diagnostic.goto_next()| function M.goto_prev(opts) + 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) @@ -404,6 +415,7 @@ end ---@param opts table See |vim.lsp.diagnostic.goto_next()| ---@return table Next diagnostic function M.get_next(opts) + 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) @@ -421,6 +433,7 @@ end ---@param opts table See |vim.lsp.diagnostic.goto_next()| ---@return table Next diagnostic position function M.get_next_pos(opts) + 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) @@ -434,25 +447,8 @@ end --- Move to the next diagnostic --- ---@deprecated Prefer |vim.diagnostic.goto_next()| ---- ----@param opts table|nil Configuration table. Keys: ---- - {client_id}: (number) ---- - If nil, will consider all clients attached to buffer. ---- - {cursor_position}: (Position, default current position) ---- - See |nvim_win_get_cursor()| ---- - {wrap}: (boolean, default true) ---- - Whether to loop around file or not. Similar to 'wrapscan' ---- - {severity}: (DiagnosticSeverity) ---- - Exclusive severity to consider. Overrides {severity_limit} ---- - {severity_limit}: (DiagnosticSeverity) ---- - Limit severity of diagnostics found. E.g. "Warning" means { "Error", "Warning" } will be valid. ---- - {enable_popup}: (boolean, default true) ---- - Call |vim.lsp.diagnostic.show_line_diagnostics()| on jump ---- - {popup_opts}: (table) ---- - Table to pass as {opts} parameter to |vim.lsp.diagnostic.show_line_diagnostics()| ---- - {win_id}: (number, default 0) ---- - Window ID function M.goto_next(opts) + 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) @@ -476,6 +472,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.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)} @@ -496,6 +493,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.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)} @@ -517,6 +515,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.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)} @@ -535,6 +534,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.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 @@ -552,6 +552,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.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 @@ -565,7 +566,7 @@ end --- Open a floating window with the diagnostics from {line_nr} --- ----@deprecated Prefer |vim.diagnostic.show_line_diagnostics()| +---@deprecated Prefer |vim.diagnostic.open_float()| --- ---@param opts table Configuration table --- - all opts for |vim.lsp.diagnostic.get_line_diagnostics()| and @@ -575,6 +576,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.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 @@ -586,7 +588,7 @@ end --- Redraw diagnostics for the given buffer and client --- ----@deprecated Prefer |vim.diagnostic.redraw()| +---@deprecated Prefer |vim.diagnostic.show()| --- --- This calls the "textDocument/publishDiagnostics" handler manually using --- the cached diagnostics already received from the server. This can be useful @@ -598,6 +600,7 @@ end --- client. The default is to redraw diagnostics for all attached --- clients. function M.redraw(bufnr, client_id) + 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) @@ -625,6 +628,7 @@ end --- - {workspace}: (boolean, default true) --- - Set the list with workspace diagnostics function M.set_qflist(opts) + 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,6 +660,7 @@ end --- - {workspace}: (boolean, default false) --- - Set the list with workspace diagnostics function M.set_loclist(opts) + 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) @@ -683,6 +688,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.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) @@ -703,6 +709,7 @@ end --- client. The default is to enable diagnostics for all attached --- clients. function M.enable(bufnr, client_id) + 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) @@ -717,5 +724,3 @@ end -- }}} return M - --- vim: fdm=marker diff --git a/runtime/lua/vim/lsp/handlers.lua b/runtime/lua/vim/lsp/handlers.lua index fa4f2b22a5..f5aefd4402 100644 --- a/runtime/lua/vim/lsp/handlers.lua +++ b/runtime/lua/vim/lsp/handlers.lua @@ -28,7 +28,7 @@ local function progress_handler(_, result, ctx, _) local client_name = client and client.name or string.format("id=%d", client_id) if not client then err_message("LSP[", client_name, "] client has shut down after sending the message") - return + return vim.NIL end local val = result.value -- unspecified yet local token = result.token -- string or number @@ -70,6 +70,7 @@ M['window/workDoneProgress/create'] = function(_, result, ctx) local client_name = client and client.name or string.format("id=%d", client_id) if not client then err_message("LSP[", client_name, "] client has shut down after sending the message") + return vim.NIL end client.messages.progress[token] = {} return vim.NIL @@ -110,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; @@ -149,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 @@ -158,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 @@ -168,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 @@ -193,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 @@ -245,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 @@ -276,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 - util.set_qflist(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 @@ -378,8 +429,8 @@ local make_call_hierarchy_handler = function(direction) }) end end - util.set_qflist(items) - api.nvim_command("copen") + vim.fn.setqflist({}, ' ', {title = 'LSP call hierarchy', items = items}) + api.nvim_command("botright copen") end end @@ -438,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 4597f1919a..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. @@ -101,6 +101,7 @@ function log.set_level(level) end --- Gets the current log level. +---@return string current log level function log.get_level() return current_log_level end diff --git a/runtime/lua/vim/lsp/protocol.lua b/runtime/lua/vim/lsp/protocol.lua index b3aa8b934f..86c9e2fd58 100644 --- a/runtime/lua/vim/lsp/protocol.lua +++ b/runtime/lua/vim/lsp/protocol.lua @@ -776,149 +776,9 @@ function protocol.make_client_capabilities() } end ---[=[ -export interface DocumentFilter { - --A language id, like `typescript`. - language?: string; - --A Uri [scheme](#Uri.scheme), like `file` or `untitled`. - scheme?: string; - --A glob pattern, like `*.{ts,js}`. - -- - --Glob patterns can have the following syntax: - --- `*` to match one or more characters in a path segment - --- `?` to match on one character in a path segment - --- `**` to match any number of path segments, including none - --- `{}` to group conditions (e.g. `**/*.{ts,js}` matches all TypeScript and JavaScript files) - --- `[]` to declare a range of characters to match in a path segment (e.g., `example.[0-9]` to match on `example.0`, `example.1`, …) - --- `[!...]` to negate a range of characters to match in a path segment (e.g., `example.[!0-9]` to match on `example.a`, `example.b`, but not `example.0`) - pattern?: string; -} ---]=] - ---[[ ---Static registration options to be returned in the initialize request. -interface StaticRegistrationOptions { - --The id used to register the request. The id can be used to deregister - --the request again. See also Registration#id. - id?: string; -} - -export interface DocumentFilter { - --A language id, like `typescript`. - language?: string; - --A Uri [scheme](#Uri.scheme), like `file` or `untitled`. - scheme?: string; - --A glob pattern, like `*.{ts,js}`. - -- - --Glob patterns can have the following syntax: - --- `*` to match one or more characters in a path segment - --- `?` to match on one character in a path segment - --- `**` to match any number of path segments, including none - --- `{}` to group conditions (e.g. `**/*.{ts,js}` matches all TypeScript and JavaScript files) - --- `[]` to declare a range of characters to match in a path segment (e.g., `example.[0-9]` to match on `example.0`, `example.1`, …) - --- `[!...]` to negate a range of characters to match in a path segment (e.g., `example.[!0-9]` to match on `example.a`, `example.b`, but not `example.0`) - pattern?: string; -} -export type DocumentSelector = DocumentFilter[]; -export interface TextDocumentRegistrationOptions { - --A document selector to identify the scope of the registration. If set to null - --the document selector provided on the client side will be used. - documentSelector: DocumentSelector | null; -} - ---Code Action options. -export interface CodeActionOptions { - --CodeActionKinds that this server may return. - -- - --The list of kinds may be generic, such as `CodeActionKind.Refactor`, or the server - --may list out every specific kind they provide. - codeActionKinds?: CodeActionKind[]; -} - -interface ServerCapabilities { - --Defines how text documents are synced. Is either a detailed structure defining each notification or - --for backwards compatibility the TextDocumentSyncKind number. If omitted it defaults to `TextDocumentSyncKind.None`. - textDocumentSync?: TextDocumentSyncOptions | number; - --The server provides hover support. - hoverProvider?: boolean; - --The server provides completion support. - completionProvider?: CompletionOptions; - --The server provides signature help support. - signatureHelpProvider?: SignatureHelpOptions; - --The server provides goto definition support. - definitionProvider?: boolean; - --The server provides Goto Type Definition support. - -- - --Since 3.6.0 - typeDefinitionProvider?: boolean | (TextDocumentRegistrationOptions & StaticRegistrationOptions); - --The server provides Goto Implementation support. - -- - --Since 3.6.0 - implementationProvider?: boolean | (TextDocumentRegistrationOptions & StaticRegistrationOptions); - --The server provides find references support. - referencesProvider?: boolean; - --The server provides document highlight support. - documentHighlightProvider?: boolean; - --The server provides document symbol support. - documentSymbolProvider?: boolean; - --The server provides workspace symbol support. - workspaceSymbolProvider?: boolean; - --The server provides code actions. The `CodeActionOptions` return type is only - --valid if the client signals code action literal support via the property - --`textDocument.codeAction.codeActionLiteralSupport`. - codeActionProvider?: boolean | CodeActionOptions; - --The server provides code lens. - codeLensProvider?: CodeLensOptions; - --The server provides document formatting. - documentFormattingProvider?: boolean; - --The server provides document range formatting. - documentRangeFormattingProvider?: boolean; - --The server provides document formatting on typing. - documentOnTypeFormattingProvider?: DocumentOnTypeFormattingOptions; - --The server provides rename support. RenameOptions may only be - --specified if the client states that it supports - --`prepareSupport` in its initial `initialize` request. - renameProvider?: boolean | RenameOptions; - --The server provides document link support. - documentLinkProvider?: DocumentLinkOptions; - --The server provides color provider support. - -- - --Since 3.6.0 - colorProvider?: boolean | ColorProviderOptions | (ColorProviderOptions & TextDocumentRegistrationOptions & StaticRegistrationOptions); - --The server provides folding provider support. - -- - --Since 3.10.0 - foldingRangeProvider?: boolean | FoldingRangeProviderOptions | (FoldingRangeProviderOptions & TextDocumentRegistrationOptions & StaticRegistrationOptions); - --The server provides go to declaration support. - -- - --Since 3.14.0 - declarationProvider?: boolean | (TextDocumentRegistrationOptions & StaticRegistrationOptions); - --The server provides execute command support. - executeCommandProvider?: ExecuteCommandOptions; - --Workspace specific server capabilities - workspace?: { - --The server supports workspace folder. - -- - --Since 3.6.0 - workspaceFolders?: { - * The server has support for workspace folders - supported?: boolean; - * Whether the server wants to receive workspace folder - * change notifications. - * - * If a strings is provided the string is treated as a ID - * under which the notification is registered on the client - * side. The ID can be used to unregister for these events - * using the `client/unregisterCapability` request. - changeNotifications?: string | boolean; - } - } - --Experimental server capabilities. - experimental?: any; -} ---]] - --- Creates a normalized object describing LSP server capabilities. +---@param server_capabilities table Table of capabilities supported by the server +---@return table Normalized table of capabilities function protocol.resolve_capabilities(server_capabilities) local general_properties = {} local text_document_sync_properties diff --git a/runtime/lua/vim/lsp/rpc.lua b/runtime/lua/vim/lsp/rpc.lua index bce1e9f35d..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 @@ -230,7 +233,7 @@ function default_dispatchers.on_error(code, err) end --- Starts an LSP server process and create an LSP RPC client object to ---- interact with it. +--- interact with it. Communication with the server is currently limited to stdio. --- ---@param cmd (string) Command to start the LSP server. ---@param cmd_args (table) List of additional string arguments to pass to {cmd}. @@ -264,8 +267,6 @@ local function start(cmd, cmd_args, dispatchers, extra_spawn_params) if extra_spawn_params and extra_spawn_params.cwd then assert(is_dir(extra_spawn_params.cwd), "cwd must be a directory") - elseif not (vim.fn.executable(cmd) == 1) then - error(string.format("The given command %q is not executable.", cmd)) end if dispatchers then local user_dispatchers = dispatchers @@ -325,7 +326,14 @@ local function start(cmd, cmd_args, dispatchers, extra_spawn_params) end handle, pid = uv.spawn(cmd, spawn_params, onexit) if handle == nil then - error(string.format("start `%s` failed: %s", cmd, pid)) + local msg = string.format("Spawning language server with cmd: `%s` failed", cmd) + if string.match(pid, "ENOENT") then + msg = msg .. ". The language server is either not installed, missing from PATH, or not executable." + else + msg = msg .. string.format(" with error message: %s", pid) + end + vim.notify(msg, vim.log.levels.WARN) + return end end diff --git a/runtime/lua/vim/lsp/sync.lua b/runtime/lua/vim/lsp/sync.lua index f185e3973f..9955fff3e2 100644 --- a/runtime/lua/vim/lsp/sync.lua +++ b/runtime/lua/vim/lsp/sync.lua @@ -74,6 +74,7 @@ local function byte_to_utf(line, byte, offset_encoding) return utf_idx + 1 end +---@private local function compute_line_length(line, offset_encoding) local length local _ @@ -104,15 +105,16 @@ local function align_end_position(line, byte, offset_encoding) char = compute_line_length(line, offset_encoding) + 1 else -- Modifying line, find the nearest utf codepoint - local offset = str_utf_end(line, byte) + local offset = str_utf_start(line, byte) -- If the byte does not fall on the start of the character, then -- align to the start of the next character. - if offset > 0 then - char = byte_to_utf(line, byte, offset_encoding) + 1 - byte = byte + offset - else + if offset < 0 then + byte = byte + str_utf_end(line, byte) + 1 + end + if byte <= #line then char = byte_to_utf(line, byte, offset_encoding) - byte = byte + offset + else + char = compute_line_length(line, offset_encoding) + 1 end -- Extending line, find the nearest utf codepoint for the last valid character end @@ -129,13 +131,30 @@ end ---@param offset_encoding string utf-8|utf-16|utf-32|nil (fallback to utf-8) ---@returns table<int, int> line_idx, byte_idx, and char_idx of first change position local function compute_start_range(prev_lines, curr_lines, firstline, lastline, new_lastline, offset_encoding) + local char_idx + local byte_idx -- If firstline == lastline, no existing text is changed. All edit operations -- occur on a new line pointed to by lastline. This occurs during insertion of -- new lines(O), the new newline is inserted at the line indicated by -- new_lastline. - -- If firstline == new_lastline, the first change occured on a line that was deleted. + if firstline == lastline then + local line_idx + local line = prev_lines[firstline - 1] + if line then + line_idx = firstline - 1 + byte_idx = #line + 1 + char_idx = compute_line_length(line, offset_encoding) + 1 + else + line_idx = firstline + byte_idx = 1 + char_idx = 1 + end + return { line_idx = line_idx, byte_idx = byte_idx, char_idx = char_idx } + end + + -- If firstline == new_lastline, the first change occurred on a line that was deleted. -- In this case, the first byte change is also at the first byte of firstline - if firstline == new_lastline or firstline == lastline then + if firstline == new_lastline then return { line_idx = firstline, byte_idx = 1, char_idx = 1 } end @@ -156,8 +175,6 @@ local function compute_start_range(prev_lines, curr_lines, firstline, lastline, end -- Convert byte to codepoint if applicable - local char_idx - local byte_idx if start_byte_idx == 1 or (#prev_line == 0 and start_byte_idx == 1)then byte_idx = start_byte_idx char_idx = 1 @@ -166,7 +183,7 @@ local function compute_start_range(prev_lines, curr_lines, firstline, lastline, char_idx = compute_line_length(prev_line, offset_encoding) + 1 else byte_idx = start_byte_idx + str_utf_start(prev_line, start_byte_idx) - char_idx = byte_to_utf(prev_line, start_byte_idx, offset_encoding) + char_idx = byte_to_utf(prev_line, byte_idx, offset_encoding) end -- Return the start difference (shared for new and prev lines) @@ -187,7 +204,7 @@ end ---@param offset_encoding string ---@returns (int, int) end_line_idx and end_col_idx of range local function compute_end_range(prev_lines, curr_lines, start_range, firstline, lastline, new_lastline, offset_encoding) - -- If firstline == new_lastline, the first change occured on a line that was deleted. + -- If firstline == new_lastline, the first change occurred on a line that was deleted. -- In this case, the last_byte... if firstline == new_lastline then return { line_idx = (lastline - new_lastline + firstline), byte_idx = 1, char_idx = 1 }, { line_idx = firstline, byte_idx = 1, char_idx = 1 } @@ -296,7 +313,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 ba5c20ef9f..401dac9acd 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 = { @@ -90,6 +82,49 @@ local function split_lines(value) return split(value, '\n', true) end +--- Convert byte index to `encoding` index. +--- Convenience wrapper around vim.str_utfindex +---@param line string line to be indexed +---@param index number byte index (utf-8), or `nil` for length +---@param encoding string utf-8|utf-16|utf-32|nil defaults to utf-16 +---@return number `encoding` index of `index` in `line` +function M._str_utfindex_enc(line, index, encoding) + if not encoding then encoding = 'utf-16' end + if encoding == 'utf-8' then + if index then return index else return #line end + elseif encoding == 'utf-16' then + local _, col16 = vim.str_utfindex(line, index) + return col16 + elseif encoding == 'utf-32' then + local col32, _ = vim.str_utfindex(line, index) + return col32 + else + error("Invalid encoding: " .. vim.inspect(encoding)) + end +end + +--- Convert UTF index to `encoding` index. +--- Convenience wrapper around vim.str_byteindex +---Alternative to vim.str_byteindex that takes an encoding. +---@param line string line to be indexed +---@param index number UTF index +---@param encoding string utf-8|utf-16|utf-32|nil defaults to utf-16 +---@return number byte (utf-8) index of `encoding` index `index` in `line` +function M._str_byteindex_enc(line, index, encoding) + if not encoding then encoding = 'utf-16' end + if encoding == 'utf-8' then + if index then return index else return #line end + elseif encoding == 'utf-16' then + return vim.str_byteindex(line, index, true) + elseif encoding == 'utf-32' then + return vim.str_byteindex(line, index) + else + error("Invalid encoding: " .. vim.inspect(encoding)) + end +end + +local _str_utfindex_enc = M._str_utfindex_enc +local _str_byteindex_enc = M._str_byteindex_enc --- Replaces text in a range with new text. --- --- CAUTION: Changes in-place! @@ -148,8 +183,101 @@ local function sort_by_key(fn) end ---@private +--- Gets the zero-indexed lines from the given buffer. +--- Works on unloaded buffers by reading the file using libuv to bypass buf reading events. +--- Falls back to loading the buffer and nvim_buf_get_lines for buffers with non-file URI. +--- +---@param bufnr number bufnr to get the lines from +---@param rows number[] zero-indexed line numbers +---@return table<number string> a table mapping rows to lines +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 = {} + for _, row in pairs(rows) do + lines[row] = (vim.api.nvim_buf_get_lines(bufnr, row, row + 1, false) or { "" })[1] + end + return lines + end + + local uri = vim.uri_from_bufnr(bufnr) + + -- load the buffer if this is not a file uri + -- Custom language server protocol extensions can result in servers sending URIs with custom schemes. Plugins are able to load these via `BufReadCmd` autocmds. + if uri:sub(1, 4) ~= "file" then + vim.fn.bufload(bufnr) + return buf_lines() + end + + -- use loaded buffers if available + if vim.fn.bufloaded(bufnr) == 1 then + return buf_lines() + end + + local filename = api.nvim_buf_get_name(bufnr) + + -- get the data from the file + local fd = uv.fs_open(filename, "r", 438) + if not fd then return "" end + local stat = uv.fs_fstat(fd) + local data = uv.fs_read(fd, stat.size, 0) + uv.fs_close(fd) + + local lines = {} -- rows we need to retrieve + local need = 0 -- keep track of how many unique rows we need + for _, row in pairs(rows) do + if not lines[row] then + need = need + 1 + end + lines[row] = true + end + + local found = 0 + local lnum = 0 + + for line in string.gmatch(data, "([^\n]*)\n?") do + if lines[lnum] == true then + lines[lnum] = line + found = found + 1 + if found == need then break end + end + lnum = lnum + 1 + end + + -- change any lines we didn't find to the empty string + for i, line in pairs(lines) do + if line == true then + lines[i] = "" + end + end + return lines +end + + +---@private +--- Gets the zero-indexed line from the given buffer. +--- Works on unloaded buffers by reading the file using libuv to bypass buf reading events. +--- Falls back to loading the buffer and nvim_buf_get_lines for buffers with non-file URI. +--- +---@param bufnr number +---@param row number zero-indexed line number +---@return string the line at row in filename +local function get_line(bufnr, row) + return get_lines(bufnr, { row })[row] +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 --- 1-indexed local function get_line_byte_from_position(bufnr, position, offset_encoding) -- LSP's line and characters are 0-indexed @@ -158,31 +286,19 @@ 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 - if not api.nvim_buf_is_loaded(bufnr) then - vim.fn.bufload(bufnr) - end - - local line = position.line - local lines = api.nvim_buf_get_lines(bufnr, line, line + 1, false) - if #lines > 0 then - local ok, result - - if offset_encoding == "utf-16" or not offset_encoding then - ok, result = pcall(vim.str_byteindex, lines[1], col, true) - elseif offset_encoding == "utf-32" then - ok, result = pcall(vim.str_byteindex, lines[1], col, false) - end - - if ok then - return result - end - return math.min(#lines[1], col) + local line = get_line(bufnr, position.line) or '' + local ok, result + ok, result = pcall(_str_byteindex_enc, line, col, offset_encoding) + if ok then + return result end + return math.min(#line, col) end return col end --- Process and return progress reports from lsp server +---@private function M.get_progress_messages() local new_messages = {} @@ -244,8 +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 ---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textEdit -function M.apply_text_edits(text_edits, bufnr) +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', false }; + } if not next(text_edits) then return end if not api.nvim_buf_is_loaded(bufnr) then vim.fn.bufload(bufnr) @@ -279,26 +401,6 @@ function M.apply_text_edits(text_edits, bufnr) 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) - -- TODO handle offset_encoding - local _, len = vim.str_utfindex(vim.api.nvim_buf_get_lines(bufnr, -2, -1, false)[1] or '') - 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 @@ -318,16 +420,38 @@ function M.apply_text_edits(text_edits, bufnr) -- 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) @@ -342,10 +466,13 @@ function M.apply_text_edits(text_edits, bufnr) 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 @@ -353,8 +480,8 @@ function M.apply_text_edits(text_edits, bufnr) -- 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 (api.nvim_buf_get_option(bufnr, 'eol') or (api.nvim_buf_get_option(bufnr, 'fixeol') and not api.nvim_buf_get_option('binary'))) + 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 @@ -392,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. @@ -413,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. @@ -474,7 +604,7 @@ local function remove_unmatch_completion_items(items, prefix) end, items) end ---- Acording to LSP spec, if the client set `completionItemKind.valueSet`, +--- According to LSP spec, if the client set `completionItemKind.valueSet`, --- the client must handle it properly even if it receives a value outside the --- specification. --- @@ -574,7 +704,7 @@ function M.rename(old_fname, new_fname, opts) api.nvim_buf_delete(oldbuf, { force = true }) end - +---@private local function create_file(change) local opts = change.options or {} -- from spec: Overwrite wins over `ignoreIfExists` @@ -586,7 +716,7 @@ local function create_file(change) vim.fn.bufadd(fname) end - +---@private local function delete_file(change) local opts = change.options or {} local fname = vim.uri_to_fname(change.uri) @@ -610,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 @@ -628,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 @@ -641,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 @@ -717,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] @@ -858,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'" @@ -875,11 +1014,13 @@ 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") return true end @@ -997,7 +1138,7 @@ function M.stylize_markdown(bufnr, contents, opts) block = {nil, "```+([a-zA-Z0-9_]*)", "```+"}, pre = {"", "<pre>", "</pre>"}, code = {"", "<code>", "</code>"}, - text = {"plaintex", "<text>", "</text>"}, + text = {"text", "<text>", "</text>"}, } local match_begin = function(line) @@ -1054,7 +1195,7 @@ function M.stylize_markdown(bufnr, contents, opts) markdown_lines[#stripped] = true end else - -- strip any emty lines or separators prior to this separator in actual markdown + -- strip any empty lines or separators prior to this separator in actual markdown if line:match("^---+$") then while markdown_lines[#stripped] and (stripped[#stripped]:match("^%s*$") or stripped[#stripped]:match("^---+$")) do markdown_lines[#stripped] = false @@ -1087,7 +1228,7 @@ function M.stylize_markdown(bufnr, contents, opts) local idx = 1 ---@private - -- keep track of syntaxes we already inlcuded. + -- keep track of syntaxes we already included. -- no need to include the same syntax more than once local langs = {} local fences = get_markdown_fences() @@ -1133,17 +1274,57 @@ function M.stylize_markdown(bufnr, contents, opts) return stripped end +---@private --- Creates autocommands to close a preview window when events happen. --- ----@param events (table) list of events ----@param winnr (number) window id of preview window +---@param events table list of events +---@param winnr number window id of preview window +---@param bufnrs table list of buffers where the preview window will remain visible ---@see |autocmd-events| -function M.close_preview_autocmd(events, winnr) +local function close_preview_autocmd(events, winnr, bufnrs) + local augroup = 'preview_window_'..winnr + + -- close the preview window when entered a buffer that is not + -- the floating window buffer or the buffer that spawned it + vim.cmd(string.format([[ + augroup %s + autocmd! + autocmd BufEnter * lua vim.lsp.util._close_preview_window(%d, {%s}) + augroup end + ]], augroup, winnr, table.concat(bufnrs, ','))) + if #events > 0 then - api.nvim_command("autocmd "..table.concat(events, ',').." <buffer> ++once lua pcall(vim.api.nvim_win_close, "..winnr..", true)") + vim.cmd(string.format([[ + augroup %s + autocmd %s <buffer> lua vim.lsp.util._close_preview_window(%d) + augroup end + ]], augroup, table.concat(events, ','), winnr)) end end +---@private +--- Closes the preview window +--- +---@param winnr number window id of preview window +---@param bufnrs table|nil optional list of ignored buffers +function M._close_preview_window(winnr, bufnrs) + vim.schedule(function() + -- exit if we are in one of ignored buffers + if bufnrs and vim.tbl_contains(bufnrs, api.nvim_get_current_buf()) then + return + end + + local augroup = 'preview_window_'..winnr + vim.cmd(string.format([[ + augroup %s + autocmd! + augroup end + augroup! %s + ]], augroup, augroup)) + pcall(vim.api.nvim_win_close, winnr, true) + end) +end + ---@internal --- Computes size of float needed to show contents (with optional wrapping) --- @@ -1223,18 +1404,21 @@ end --- ---@param contents table of lines to show in window ---@param syntax string of syntax to set for opened buffer ----@param opts dictionary with optional fields ---- - height of floating window ---- - width of floating window ---- - wrap boolean enable wrapping of long lines (defaults to true) ---- - wrap_at character to wrap at for computing height when wrap is enabled ---- - max_width maximal width of floating window ---- - max_height maximal height of floating window ---- - pad_top number of lines to pad contents at top ---- - pad_bottom number of lines to pad contents at bottom ---- - focus_id if a popup with this id is opened, then focus it ---- - close_events list of events that closes the floating window ---- - focusable (boolean, default true): Make float focusable +---@param opts table with optional fields (additional keys are passed on to |vim.api.nvim_open_win()|) +--- - height: (number) height of floating window +--- - width: (number) width of floating window +--- - wrap: (boolean, default true) wrap long lines +--- - wrap_at: (string) character to wrap at for computing height when wrap is enabled +--- - max_width: (number) maximal width of floating window +--- - max_height: (number) maximal height of floating window +--- - pad_top: (number) number of lines to pad contents at top +--- - pad_bottom: (number) number of lines to pad contents at bottom +--- - focus_id: (string) if a popup with this id is opened, then focus it +--- - close_events: (table) list of events that closes the floating window +--- - focusable: (boolean, default true) Make float focusable +--- - focus: (boolean, default true) If `true`, and if {focusable} +--- is also `true`, focus an existing floating window with the same +--- {focus_id} ---@returns bufnr,winnr buffer and window number of the newly created floating ---preview window function M.open_floating_preview(contents, syntax, opts) @@ -1246,12 +1430,13 @@ function M.open_floating_preview(contents, syntax, opts) opts = opts or {} opts.wrap = opts.wrap ~= false -- wrapping by default opts.stylize_markdown = opts.stylize_markdown ~= false - opts.close_events = opts.close_events or {"CursorMoved", "CursorMovedI", "BufHidden", "InsertCharPre"} + opts.focus = opts.focus ~= false + opts.close_events = opts.close_events or {"CursorMoved", "CursorMovedI", "InsertCharPre"} local bufnr = api.nvim_get_current_buf() -- check if this popup is focusable and we need to focus - if opts.focus_id and opts.focusable ~= false then + if opts.focus_id and opts.focusable ~= false and opts.focus then -- Go back to previous window if we are in a focusable one local current_winnr = api.nvim_get_current_win() if npcall(api.nvim_win_get_var, current_winnr, opts.focus_id) then @@ -1314,8 +1499,8 @@ function M.open_floating_preview(contents, syntax, opts) api.nvim_buf_set_option(floating_bufnr, 'modifiable', false) api.nvim_buf_set_option(floating_bufnr, 'bufhidden', 'wipe') - api.nvim_buf_set_keymap(floating_bufnr, "n", "q", "<cmd>bdelete<cr>", {silent = true, noremap = true}) - M.close_preview_autocmd(opts.close_events, floating_winnr) + api.nvim_buf_set_keymap(floating_bufnr, "n", "q", "<cmd>bdelete<cr>", {silent = true, noremap = true, nowait = true}) + close_preview_autocmd(opts.close_events, floating_winnr, {floating_bufnr, bufnr}) -- save focus_id if opts.focus_id then @@ -1331,21 +1516,23 @@ do --[[ References ]] --- Removes document highlights from a buffer. --- - ---@param bufnr buffer id + ---@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 buffer id - ---@param references List of `DocumentHighlight` objects to highlight - ---@param offset_encoding string utf-8|utf-16|utf-32|nil defaults to utf-16 + ---@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". ---@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 'utf-16' + 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"] @@ -1363,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 @@ -1372,97 +1560,20 @@ local position_sort = sort_by_key(function(v) return {v.start.line, v.start.character} end) ---- Gets the zero-indexed line from the given uri. ----@param uri string uri of the resource to get the line from ----@param row number zero-indexed line number ----@return string the line at row in filename --- For non-file uris, we load the buffer and get the line. --- If a loaded buffer exists, then that is used. --- Otherwise we get the line using libuv which is a lot faster than loading the buffer. -function M.get_line(uri, row) - return M.get_lines(uri, { row })[row] -end - ---- Gets the zero-indexed lines from the given uri. ----@param uri string uri of the resource to get the lines from ----@param rows number[] zero-indexed line numbers ----@return table<number string> a table mapping rows to lines --- For non-file uris, we load the buffer and get the lines. --- If a loaded buffer exists, then that is used. --- Otherwise we get the lines using libuv which is a lot faster than loading the buffer. -function M.get_lines(uri, rows) - rows = type(rows) == "table" and rows or { rows } - - local function buf_lines(bufnr) - local lines = {} - for _, row in pairs(rows) do - lines[row] = (vim.api.nvim_buf_get_lines(bufnr, row, row + 1, false) or { "" })[1] - end - return lines - end - - -- load the buffer if this is not a file uri - -- Custom language server protocol extensions can result in servers sending URIs with custom schemes. Plugins are able to load these via `BufReadCmd` autocmds. - if uri:sub(1, 4) ~= "file" then - local bufnr = vim.uri_to_bufnr(uri) - vim.fn.bufload(bufnr) - return buf_lines(bufnr) - end - - local filename = vim.uri_to_fname(uri) - - -- use loaded buffers if available - if vim.fn.bufloaded(filename) == 1 then - local bufnr = vim.fn.bufnr(filename, false) - return buf_lines(bufnr) - end - - -- get the data from the file - local fd = uv.fs_open(filename, "r", 438) - if not fd then return "" end - local stat = uv.fs_fstat(fd) - local data = uv.fs_read(fd, stat.size, 0) - uv.fs_close(fd) - - local lines = {} -- rows we need to retrieve - local need = 0 -- keep track of how many unique rows we need - for _, row in pairs(rows) do - if not lines[row] then - need = need + 1 - end - lines[row] = true - end - - local found = 0 - local lnum = 0 - - for line in string.gmatch(data, "([^\n]*)\n?") do - if lines[lnum] == true then - lines[lnum] = line - found = found + 1 - if found == need then break end - end - lnum = lnum + 1 - end - - -- change any lines we didn't find to the empty string - for i, line in pairs(lines) do - if line == true then - lines[i] = "" - end - end - return lines -end - --- Returns the items with the byte position calculated correctly and in sorted --- order, for display in quickfix and location lists. --- --- 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) @@ -1496,13 +1607,13 @@ function M.locations_to_items(locations) end -- get all the lines for this uri - local lines = M.get_lines(uri, uri_rows) + local lines = get_lines(vim.uri_to_bufnr(uri), uri_rows) for _, temp in ipairs(rows) do 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, @@ -1522,6 +1633,7 @@ end --- ---@param items (table) list of items function M.set_loclist(items, win_id) + vim.api.nvim_echo({{'vim.lsp.util.set_loclist is deprecated. See :h deprecated', 'WarningMsg'}}, true, {}) vim.fn.setloclist(win_id or 0, {}, ' ', { title = 'Language Server'; items = items; @@ -1535,13 +1647,14 @@ end --- ---@param items (table) list of items function M.set_qflist(items) + vim.api.nvim_echo({{'vim.lsp.util.set_qflist is deprecated. See :h deprecated', 'WarningMsg'}}, true, {}) vim.fn.setqflist({}, ' ', { title = 'Language Server'; items = items; }) end --- Acording to LSP spec, if the client set "symbolKind.valueSet", +-- According to LSP spec, if the client set "symbolKind.valueSet", -- the client must handle it properly even if it receives a value outside the specification. -- https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_documentSymbol function M._get_symbol_kind_name(symbol_kind) @@ -1586,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. @@ -1638,43 +1751,84 @@ function M.try_trim_markdown_code_blocks(lines) return 'markdown' end -local str_utfindex = vim.str_utfindex ---@private -local function make_position_param() - local row, col = unpack(api.nvim_win_get_cursor(0)) +---@param window (optional, number): window handle or 0 for current, defaults to current +---@param offset_encoding string utf-8|utf-16|utf-32|nil defaults to `offset_encoding` of first client of buffer of `window` +local function make_position_param(window, offset_encoding) + window = window or 0 + local buf = vim.api.nvim_win_get_buf(window) + local row, col = unpack(api.nvim_win_get_cursor(window)) + offset_encoding = offset_encoding or M._get_offset_encoding(buf) row = row - 1 - local line = api.nvim_buf_get_lines(0, row, row+1, true)[1] + local line = api.nvim_buf_get_lines(buf, row, row+1, true)[1] if not line then return { line = 0; character = 0; } end - -- TODO handle offset_encoding - local _ - _, col = str_utfindex(line, col) + + col = _str_utfindex_enc(line, col, offset_encoding) + return { line = row; character = col; } end --- Creates a `TextDocumentPositionParams` object for the current buffer and cursor position. --- +---@param window (optional, number): window handle or 0 for current, defaults to current +---@param offset_encoding string utf-8|utf-16|utf-32|nil defaults to `offset_encoding` of first client of buffer of `window` ---@returns `TextDocumentPositionParams` object ---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocumentPositionParams -function M.make_position_params() +function M.make_position_params(window, offset_encoding) + window = window or 0 + local buf = vim.api.nvim_win_get_buf(window) + offset_encoding = offset_encoding or M._get_offset_encoding(buf) return { - textDocument = M.make_text_document_params(); - position = make_position_param() + textDocument = M.make_text_document_params(buf); + position = make_position_param(window, offset_encoding) } end +--- Utility function for getting the encoding of the first LSP client on the given buffer. +---@param bufnr (number) buffer handle or 0 for current, defaults to current +---@returns (string) encoding first client if there is one, nil otherwise +function M._get_offset_encoding(bufnr) + validate { + bufnr = {bufnr, 'n', true}; + } + + local offset_encoding + + for _, client in pairs(vim.lsp.buf_get_clients(bufnr)) do + 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 + vim.notify("warning: multiple different client offset_encodings detected for buffer, this is not supported yet", vim.log.levels.WARN) + end + end + + return offset_encoding +end + --- Using the current position in the current buffer, creates an object that --- can be used as a building block for several LSP requests, such as --- `textDocument/codeAction`, `textDocument/colorPresentation`, --- `textDocument/rangeFormatting`. --- +---@param window (optional, number): window handle or 0 for current, defaults to current +---@param offset_encoding string utf-8|utf-16|utf-32|nil defaults to `offset_encoding` of first client of buffer of `window` ---@returns { textDocument = { uri = `current_file_uri` }, range = { start = ---`current_position`, end = `current_position` } } -function M.make_range_params() - local position = make_position_param() +function M.make_range_params(window, offset_encoding) + 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 { - textDocument = M.make_text_document_params(), + textDocument = M.make_text_document_params(buf), range = { start = position; ["end"] = position; } } end @@ -1686,27 +1840,29 @@ end ---Defaults to the start of the last visual selection. ---@param end_pos ({number, number}, optional) mark-indexed position. ---Defaults to the end of the last visual selection. +---@param bufnr (optional, number): buffer handle or 0 for current, defaults to current +---@param offset_encoding string utf-8|utf-16|utf-32|nil defaults to `offset_encoding` of first client of `bufnr` ---@returns { textDocument = { uri = `current_file_uri` }, range = { start = ---`start_position`, end = `end_position` } } -function M.make_given_range_params(start_pos, end_pos) +function M.make_given_range_params(start_pos, end_pos, bufnr, offset_encoding) validate { start_pos = {start_pos, 't', true}; end_pos = {end_pos, 't', true}; + offset_encoding = {offset_encoding, 's', true}; } - local A = list_extend({}, start_pos or api.nvim_buf_get_mark(0, '<')) - local B = list_extend({}, end_pos or api.nvim_buf_get_mark(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, '>')) -- convert to 0-index A[1] = A[1] - 1 B[1] = B[1] - 1 - -- account for encoding. - -- TODO handle offset_encoding + -- account for offset_encoding. if A[2] > 0 then - local _, char = M.character_offset(0, A[1], A[2]) - A = {A[1], char} + A = {A[1], M.character_offset(bufnr, A[1], A[2], offset_encoding)} end if B[2] > 0 then - local _, char = M.character_offset(0, B[1], B[2]) - B = {B[1], char} + B = {B[1], M.character_offset(bufnr, B[1], B[2], offset_encoding)} end -- we need to offset the end character position otherwise we loose the last -- character of the selection, as LSP end position is exclusive @@ -1715,7 +1871,7 @@ function M.make_given_range_params(start_pos, end_pos) B[2] = B[2] + 1 end return { - textDocument = M.make_text_document_params(), + textDocument = M.make_text_document_params(bufnr), range = { start = {line = A[1], character = A[2]}, ['end'] = {line = B[1], character = B[2]} @@ -1725,10 +1881,11 @@ end --- Creates a `TextDocumentIdentifier` object for the current buffer. --- +---@param bufnr (optional, number): Buffer handle, defaults to current ---@returns `TextDocumentIdentifier` ---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocumentIdentifier -function M.make_text_document_params() - return { uri = vim.uri_from_bufnr(0) } +function M.make_text_document_params(bufnr) + return { uri = vim.uri_from_bufnr(bufnr or 0) } end --- Create the workspace params @@ -1737,16 +1894,16 @@ end function M.make_workspace_params(added, removed) return { event = { added = added; removed = removed; } } end ---- Returns visual width of tabstop. +--- Returns indentation size. --- ----@see |softtabstop| +---@see |shiftwidth| ---@param bufnr (optional, number): Buffer handle, defaults to current ----@returns (number) tabstop visual width +---@returns (number) indentation size function M.get_effective_tabstop(bufnr) validate { bufnr = {bufnr, 'n', true} } local bo = bufnr and vim.bo[bufnr] or vim.bo - local sts = bo.softtabstop - return (sts > 0 and sts) or (sts < 0 and bo.shiftwidth) or bo.tabstop + local sw = bo.shiftwidth + return (sw == 0 and bo.tabstop) or sw end --- Creates a `DocumentFormattingParams` object for the current buffer and cursor position. @@ -1771,15 +1928,18 @@ end ---@param buf buffer id (0 for current) ---@param row 0-indexed line ---@param col 0-indexed byte offset in line ----@returns (number, number) UTF-32 and UTF-16 index of the character in line {row} column {col} in buffer {buf} -function M.character_offset(bufnr, row, col) - local uri = vim.uri_from_bufnr(bufnr) - local line = M.get_line(uri, row) +---@param offset_encoding string utf-8|utf-16|utf-32|nil defaults to `offset_encoding` of first client of `buf` +---@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) + 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(line) + return _str_utfindex_enc(line, nil, offset_encoding) end - return str_utfindex(line, col) + return _str_utfindex_enc(line, col, offset_encoding) end --- Helper function to return nested values in language server settings @@ -1798,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 6e40b6ca52..f0dc34608c 100644 --- a/runtime/lua/vim/shared.lua +++ b/runtime/lua/vim/shared.lua @@ -1,8 +1,10 @@ -- Functions shared by Nvim and its test-suite. -- --- The singular purpose of this module is to share code with the Nvim --- test-suite. If, in the future, Nvim itself is used to run the test-suite --- instead of "vanilla Lua", these functions could move to src/nvim/lua/vim.lua +-- These are "pure" lua functions not depending of the state of the editor. +-- Thus they should always be available whenever nvim-related lua code is run, +-- regardless if it is code in the editor itself, or in worker threads/processes, +-- or the test suite. (Eventually the test suite will be run in a worker process, +-- so this wouldn't be a separate case to consider) local vim = vim or {} @@ -12,7 +14,7 @@ local vim = vim or {} --- same functions as those in the input table. Userdata and threads are not --- copied and will throw an error. --- ----@param orig Table to copy +---@param orig table Table to copy ---@returns New table of copied keys and (nested) values. function vim.deepcopy(orig) end -- luacheck: no unused vim.deepcopy = (function() @@ -21,17 +23,16 @@ vim.deepcopy = (function() end local deepcopy_funcs = { - table = function(orig) + table = function(orig, cache) + if cache[orig] then return cache[orig] end local copy = {} - if vim._empty_dict_mt ~= nil and getmetatable(orig) == vim._empty_dict_mt then - copy = vim.empty_dict() - end - + cache[orig] = copy + local mt = getmetatable(orig) for k, v in pairs(orig) do - copy[vim.deepcopy(k)] = vim.deepcopy(v) + copy[vim.deepcopy(k, cache)] = vim.deepcopy(v, cache) end - return copy + return setmetatable(copy, mt) end, number = _id, string = _id, @@ -40,10 +41,10 @@ vim.deepcopy = (function() ['function'] = _id, } - return function(orig) + return function(orig, cache) local f = deepcopy_funcs[type(orig)] if f then - return f(orig) + return f(orig, cache or {}) else error("Cannot deepcopy object of type "..type(orig)) end @@ -330,7 +331,7 @@ end --- Add the reverse lookup values to an existing table. --- For example: ---- `tbl_add_reverse_lookup { A = 1 } == { [1] = 'A', A = 1 }` +--- ``tbl_add_reverse_lookup { A = 1 } == { [1] = 'A', A = 1 }`` -- --Do note that it *modifies* the input. ---@param o table The table to add the reverse to. @@ -346,6 +347,33 @@ function vim.tbl_add_reverse_lookup(o) return o end +--- Index into a table (first argument) via string keys passed as subsequent arguments. +--- Return `nil` if the key does not exist. +--_ +--- Examples: +--- <pre> +--- vim.tbl_get({ key = { nested_key = true }}, 'key', 'nested_key') == true +--- vim.tbl_get({ key = {}}, 'key', 'nested_key') == nil +--- </pre> +--- +---@param o Table to index +---@param ... Optional strings (0 or more, variadic) via which to index the table +--- +---@returns nested value indexed by key if it exists, else nil +function vim.tbl_get(o, ...) + local keys = {...} + if #keys == 0 then + return + end + for _, k in ipairs(keys) do + o = o[k] + if o == nil then + return + end + end + return o +end + --- Extends a list-like table with the values of another list-like table. --- --- NOTE: This mutates dst! @@ -527,13 +555,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 @@ -560,6 +598,7 @@ do return type(val) == t or (t == 'callable' and vim.is_callable(val)) end + ---@private local function is_valid(opt) if type(opt) ~= 'table' then return false, string.format('opt: expected table, got %s', type(opt)) @@ -571,31 +610,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 66999c5f7f..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) @@ -72,7 +73,7 @@ end --- Gets the parser for this bufnr / ft combination. --- --- If needed this will create the parser. ---- Unconditionnally attach the provided callback +--- Unconditionally attach the provided callback --- ---@param bufnr The buffer the parser should be tied to ---@param lang The filetype of this parser diff --git a/runtime/lua/vim/treesitter/highlighter.lua b/runtime/lua/vim/treesitter/highlighter.lua index 22b528838c..0ec4ab37ec 100644 --- a/runtime/lua/vim/treesitter/highlighter.lua +++ b/runtime/lua/vim/treesitter/highlighter.lua @@ -22,8 +22,25 @@ 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", + ["text.underline"] = "Underlined", + ["todo"] = "Todo", + ["debug"] = "Debug", -- Miscs ["comment"] = "Comment", @@ -35,10 +52,13 @@ TSHighlighter.hl_map = { ["constant"] = "Constant", ["constant.builtin"] = "Special", ["constant.macro"] = "Define", + ["define"] = "Define", + ["macro"] = "Macro", ["string"] = "String", ["string.regex"] = "String", ["string.escape"] = "SpecialChar", ["character"] = "Character", + ["character.special"] = "SpecialChar", ["number"] = "Number", ["boolean"] = "Boolean", ["float"] = "Float", @@ -64,9 +84,13 @@ TSHighlighter.hl_map = { ["type"] = "Type", ["type.builtin"] = "Type", + ["type.qualifier"] = "Type", + ["type.definition"] = "Typedef", + ["storageclass"] = "StorageClass", ["structure"] = "Structure", ["include"] = "Include", -} + ["preproc"] = "PreProc", +}, subcapture_fallback) ---@private local function is_highlight_name(capture_name) @@ -260,7 +284,8 @@ local function on_line_impl(self, buf, line) { end_line = end_row, end_col = end_col, hl_group = hl, ephemeral = true, - priority = tonumber(metadata.priority) or 100 -- Low but leaves room below + priority = tonumber(metadata.priority) or 100, -- Low but leaves room below + conceal = metadata.conceal, }) end if start_row > line then diff --git a/runtime/lua/vim/treesitter/language.lua b/runtime/lua/vim/treesitter/language.lua index 89ddd6cd5a..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 @@ -38,7 +38,7 @@ end --- Inspects the provided language. --- ---- Inspecting provides some useful informations on the language like node names, ... +--- Inspecting provides some useful information on the language like node names, ... --- ---@param lang The language. function M.inspect_language(lang) diff --git a/runtime/lua/vim/treesitter/languagetree.lua b/runtime/lua/vim/treesitter/languagetree.lua index 7e392f72a4..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,9 +495,9 @@ local function tree_contains(tree, range) return false end ---- Determines wether @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 childs. +--- This goes down the tree to recursively check children. --- ---@param range A range, that is a `{ start_line, start_col, end_line, end_col }` table. function LanguageTree:contains(range) @@ -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 66da179ea3..8383551b5f 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -48,7 +48,7 @@ function M.get_query_files(lang, query_name, is_included) local base_langs = {} -- Now get the base languages by looking at the first line of every file - -- The syntax is the folowing : + -- The syntax is the following : -- ;+ inherits: ({language},)*{language} -- -- {language} ::= {lang} | ({lang}) @@ -138,48 +138,77 @@ 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 --- TODO(vigoux): support multiline nodes too - --- Gets the text corresponding to a given node --- ---@param node the node ----@param bsource The buffer or string from which the node is extracted +---@param source The buffer or string from which the node is extracted function M.get_node_text(node, source) local start_row, start_col, start_byte = node:start() local end_row, end_col, end_byte = node:end_() if type(source) == "number" then - if start_row ~= end_row then + local lines + local eof_row = a.nvim_buf_line_count(source) + if start_row >= eof_row then return nil end - local line = a.nvim_buf_get_lines(source, start_row, start_row+1, true)[1] - return string.sub(line, start_col+1, end_col) + + if end_col == 0 then + lines = a.nvim_buf_get_lines(source, start_row, end_row, true) + end_col = -1 + else + lines = a.nvim_buf_get_lines(source, start_row, end_row + 1, true) + end + + 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") elseif type(source) == "string" then return source:sub(start_byte+1, end_byte) end @@ -211,11 +240,6 @@ local predicate_handlers = { ["lua-match?"] = function(match, _, source, predicate) local node = match[predicate[2]] local regex = predicate[3] - local start_row, _, end_row, _ = node:range() - if start_row ~= end_row then - return false - end - return string.find(M.get_node_text(node, source), regex) end, @@ -239,13 +263,8 @@ local predicate_handlers = { return function(match, _, source, pred) local node = match[pred[2]] - local start_row, start_col, end_row, end_col = node:range() - if start_row ~= end_row then - return false - end - local regex = compiled_vim_regexes[pred[3]] - return regex:match_line(source, start_row, start_col, end_col) + return regex:match_str(M.get_node_text(node, source)) end end)(), @@ -446,7 +465,7 @@ end --- --- {source} is needed if the query contains predicates, then the caller --- must ensure to use a freshly parsed tree consistent with the current ---- text of the buffer (if relevent). {start_row} and {end_row} can be used to limit +--- text of the buffer (if relevant). {start_row} and {end_row} can be used to limit --- matches inside a row range (this is typically used with root node --- as the node, i e to get syntax highlight matches in the current --- viewport). When omitted the start and end row values are used from the given node. @@ -466,7 +485,7 @@ end --- </pre> --- ---@param node The node under which the search will occur ----@param source The source buffer or string to exctract text from +---@param source The source buffer or string to extract text from ---@param start The starting line of the search ---@param stop The stopping line of the search (end-exclusive) --- 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/lua/vim/uri.lua b/runtime/lua/vim/uri.lua index d08d2a3ee3..11b661cd1a 100644 --- a/runtime/lua/vim/uri.lua +++ b/runtime/lua/vim/uri.lua @@ -74,8 +74,8 @@ local function uri_from_fname(path) return table.concat(uri_parts) end -local URI_SCHEME_PATTERN = '^([a-zA-Z]+[a-zA-Z0-9+-.]*):.*' -local WINDOWS_URI_SCHEME_PATTERN = '^([a-zA-Z]+[a-zA-Z0-9+-.]*):[a-zA-Z]:.*' +local URI_SCHEME_PATTERN = '^([a-zA-Z]+[a-zA-Z0-9.+-]*):.*' +local WINDOWS_URI_SCHEME_PATTERN = '^([a-zA-Z]+[a-zA-Z0-9.+-]*):[a-zA-Z]:.*' --- Get a URI from a bufnr ---@param bufnr number diff --git a/runtime/menu.vim b/runtime/menu.vim index 701dd33cdd..956b941971 100644 --- a/runtime/menu.vim +++ b/runtime/menu.vim @@ -569,9 +569,9 @@ func! s:XxdConv() %!mc vim:xxd else call s:XxdFind() - exe '%!' . g:xxdprogram + exe ':%!' . g:xxdprogram endif - if getline(1) =~ "^0000000:" " only if it worked + if getline(1) =~ "^00000000:" " only if it worked set ft=xxd endif let &mod = mod @@ -583,7 +583,7 @@ func! s:XxdBack() %!mc vim:xxd -r else call s:XxdFind() - exe '%!' . g:xxdprogram . ' -r' + exe ':%!' . g:xxdprogram . ' -r' endif set ft= doautocmd filetypedetect BufReadPost diff --git a/runtime/nvim.appdata.xml b/runtime/nvim.appdata.xml index 099c9a57c0..4ad656f1a3 100644 --- a/runtime/nvim.appdata.xml +++ b/runtime/nvim.appdata.xml @@ -26,6 +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/optwin.vim b/runtime/optwin.vim index d4c10f7afa..a13e945098 100644 --- a/runtime/optwin.vim +++ b/runtime/optwin.vim @@ -1,7 +1,7 @@ " These commands create the option window. " " Maintainer: Bram Moolenaar <Bram@vim.org> -" Last Change: 2020 Oct 27 +" Last Change: 2022 Apr 07 " If there already is an option window, jump to that one. let buf = bufnr('option-window') @@ -261,6 +261,8 @@ call <SID>OptionG("sect", §) call append("$", "path\tlist of directory names used for file searching") call append("$", "\t(global or local to buffer)") call <SID>OptionG("pa", &pa) +call <SID>AddOption("cdhome", ":cd without argument goes to the home directory") +call <SID>BinOptionG("cdh", &cdh) call append("$", "cdpath\tlist of directory names used for :cd") call <SID>OptionG("cd", &cd) if exists("+autochdir") @@ -868,6 +870,9 @@ if has("cindent") call append("$", "cinwords\tlist of words that cause more C-indent") call append("$", "\t(local to buffer)") call <SID>OptionL("cinw") + call append("$", "cinscopedecls\tlist of scope declaration names used by cino-g") + call append("$", "\t(local to buffer)") + call <SID>OptionL("cinsd") call append("$", "indentexpr\texpression used to obtain the indent of a line") call append("$", "\t(local to buffer)") call <SID>OptionL("inde") diff --git a/runtime/pack/dist/opt/matchit/autoload/matchit.vim b/runtime/pack/dist/opt/matchit/autoload/matchit.vim index 4f3dd8ff9e..eafb7c0551 100644 --- a/runtime/pack/dist/opt/matchit/autoload/matchit.vim +++ b/runtime/pack/dist/opt/matchit/autoload/matchit.vim @@ -1,6 +1,11 @@ " matchit.vim: (global plugin) Extended "%" matching " autload script of matchit plugin, see ../plugin/matchit.vim -" Last Change: Mar 01, 2020 +" Last Change: Jun 10, 2021 + +" Neovim does not support scriptversion +if has("vimscript-4") + scriptversion 4 +endif let s:last_mps = "" let s:last_words = ":" @@ -30,11 +35,11 @@ function s:RestoreOptions() " In s:CleanUp(), :execute "set" restore_options . let restore_options = "" if get(b:, 'match_ignorecase', &ic) != &ic - let restore_options .= (&ic ? " " : " no") . "ignorecase" + let restore_options ..= (&ic ? " " : " no") .. "ignorecase" let &ignorecase = b:match_ignorecase endif if &ve != '' - let restore_options = " ve=" . &ve . restore_options + let restore_options = " ve=" .. &ve .. restore_options set ve= endif return restore_options @@ -42,22 +47,23 @@ endfunction function matchit#Match_wrapper(word, forward, mode) range let restore_options = s:RestoreOptions() - " If this function was called from Visual mode, make sure that the cursor - " is at the correct end of the Visual range: - if a:mode == "v" - execute "normal! gv\<Esc>" - elseif a:mode == "o" && mode(1) !~# '[vV]' - exe "norm! v" - elseif a:mode == "n" && mode(1) =~# 'ni' - exe "norm! v" - endif " In s:CleanUp(), we may need to check whether the cursor moved forward. let startpos = [line("."), col(".")] - " Use default behavior if called with a count. + " if a count has been applied, use the default [count]% mode (see :h N%) if v:count - exe "normal! " . v:count . "%" + exe "normal! " .. v:count .. "%" return s:CleanUp(restore_options, a:mode, startpos) end + if a:mode =~# "v" && mode(1) =~# 'ni' + exe "norm! gv" + elseif a:mode == "o" && mode(1) !~# '[vV]' + exe "norm! v" + " If this function was called from Visual mode, make sure that the cursor + " is at the correct end of the Visual range: + elseif a:mode == "v" + execute "normal! gv\<Esc>" + let startpos = [line("."), col(".")] + endif " First step: if not already done, set the script variables " s:do_BR flag for whether there are backrefs @@ -78,30 +84,30 @@ function matchit#Match_wrapper(word, forward, mode) range " quote the special chars in 'matchpairs', replace [,:] with \| and then " append the builtin pairs (/*, */, #if, #ifdef, #ifndef, #else, #elif, " #endif) - let default = escape(&mps, '[$^.*~\\/?]') . (strlen(&mps) ? "," : "") . + let default = escape(&mps, '[$^.*~\\/?]') .. (strlen(&mps) ? "," : "") .. \ '\/\*:\*\/,#\s*if\%(n\=def\)\=:#\s*else\>:#\s*elif\>:#\s*endif\>' " s:all = pattern with all the keywords - let match_words = match_words . (strlen(match_words) ? "," : "") . default + let match_words = match_words .. (strlen(match_words) ? "," : "") .. default let s:last_words = match_words - if match_words !~ s:notslash . '\\\d' + if match_words !~ s:notslash .. '\\\d' let s:do_BR = 0 let s:pat = match_words else let s:do_BR = 1 let s:pat = s:ParseWords(match_words) endif - let s:all = substitute(s:pat, s:notslash . '\zs[,:]\+', '\\|', 'g') + let s:all = substitute(s:pat, s:notslash .. '\zs[,:]\+', '\\|', 'g') " Just in case there are too many '\(...)' groups inside the pattern, make " sure to use \%(...) groups, so that error E872 can be avoided let s:all = substitute(s:all, '\\(', '\\%(', 'g') - let s:all = '\%(' . s:all . '\)' + let s:all = '\%(' .. s:all .. '\)' if exists("b:match_debug") let b:match_pat = s:pat endif " Reconstruct the version with unresolved backrefs. - let s:patBR = substitute(match_words.',', - \ s:notslash.'\zs[,:]*,[,:]*', ',', 'g') - let s:patBR = substitute(s:patBR, s:notslash.'\zs:\{2,}', ':', 'g') + let s:patBR = substitute(match_words .. ',', + \ s:notslash .. '\zs[,:]*,[,:]*', ',', 'g') + let s:patBR = substitute(s:patBR, s:notslash .. '\zs:\{2,}', ':', 'g') endif " Second step: set the following local variables: @@ -128,12 +134,15 @@ function matchit#Match_wrapper(word, forward, mode) range let curcol = match(matchline, regexp) " If there is no match, give up. if curcol == -1 + " Make sure macros abort properly + "exe "norm! \<esc>" + call feedkeys("\e", 'tni') return s:CleanUp(restore_options, a:mode, startpos) endif let endcol = matchend(matchline, regexp) let suf = strlen(matchline) - endcol - let prefix = (curcol ? '^.*\%' . (curcol + 1) . 'c\%(' : '^\%(') - let suffix = (suf ? '\)\%' . (endcol + 1) . 'c.*$' : '\)$') + let prefix = (curcol ? '^.*\%' .. (curcol + 1) .. 'c\%(' : '^\%(') + let suffix = (suf ? '\)\%' .. (endcol + 1) .. 'c.*$' : '\)$') endif if exists("b:match_debug") let b:match_match = matchstr(matchline, regexp) @@ -150,7 +159,7 @@ function matchit#Match_wrapper(word, forward, mode) range " 'while:endwhile' or whatever. A bit of a kluge: s:Choose() returns " group . "," . groupBR, and we pick it apart. let group = s:Choose(s:pat, matchline, ",", ":", prefix, suffix, s:patBR) - let i = matchend(group, s:notslash . ",") + let i = matchend(group, s:notslash .. ",") let groupBR = strpart(group, i) let group = strpart(group, 0, i-1) " Now, matchline =~ prefix . substitute(group,':','\|','g') . suffix @@ -159,32 +168,32 @@ function matchit#Match_wrapper(word, forward, mode) range endif if exists("b:match_debug") let b:match_wholeBR = groupBR - let i = matchend(groupBR, s:notslash . ":") + let i = matchend(groupBR, s:notslash .. ":") let b:match_iniBR = strpart(groupBR, 0, i-1) endif " Fourth step: Set the arguments for searchpair(). - let i = matchend(group, s:notslash . ":") - let j = matchend(group, '.*' . s:notslash . ":") + let i = matchend(group, s:notslash .. ":") + let j = matchend(group, '.*' .. s:notslash .. ":") let ini = strpart(group, 0, i-1) - let mid = substitute(strpart(group, i,j-i-1), s:notslash.'\zs:', '\\|', 'g') + let mid = substitute(strpart(group, i,j-i-1), s:notslash .. '\zs:', '\\|', 'g') let fin = strpart(group, j) "Un-escape the remaining , and : characters. - let ini = substitute(ini, s:notslash . '\zs\\\(:\|,\)', '\1', 'g') - let mid = substitute(mid, s:notslash . '\zs\\\(:\|,\)', '\1', 'g') - let fin = substitute(fin, s:notslash . '\zs\\\(:\|,\)', '\1', 'g') + let ini = substitute(ini, s:notslash .. '\zs\\\(:\|,\)', '\1', 'g') + let mid = substitute(mid, s:notslash .. '\zs\\\(:\|,\)', '\1', 'g') + let fin = substitute(fin, s:notslash .. '\zs\\\(:\|,\)', '\1', 'g') " searchpair() requires that these patterns avoid \(\) groups. - let ini = substitute(ini, s:notslash . '\zs\\(', '\\%(', 'g') - let mid = substitute(mid, s:notslash . '\zs\\(', '\\%(', 'g') - let fin = substitute(fin, s:notslash . '\zs\\(', '\\%(', 'g') + let ini = substitute(ini, s:notslash .. '\zs\\(', '\\%(', 'g') + let mid = substitute(mid, s:notslash .. '\zs\\(', '\\%(', 'g') + let fin = substitute(fin, s:notslash .. '\zs\\(', '\\%(', 'g') " Set mid. This is optimized for readability, not micro-efficiency! - if a:forward && matchline =~ prefix . fin . suffix - \ || !a:forward && matchline =~ prefix . ini . suffix + if a:forward && matchline =~ prefix .. fin .. suffix + \ || !a:forward && matchline =~ prefix .. ini .. suffix let mid = "" endif " Set flag. This is optimized for readability, not micro-efficiency! - if a:forward && matchline =~ prefix . fin . suffix - \ || !a:forward && matchline !~ prefix . ini . suffix + if a:forward && matchline =~ prefix .. fin .. suffix + \ || !a:forward && matchline !~ prefix .. ini .. suffix let flag = "bW" else let flag = "W" @@ -193,14 +202,14 @@ function matchit#Match_wrapper(word, forward, mode) range if exists("b:match_skip") let skip = b:match_skip elseif exists("b:match_comment") " backwards compatibility and testing! - let skip = "r:" . b:match_comment + let skip = "r:" .. b:match_comment else let skip = 's:comment\|string' endif let skip = s:ParseSkip(skip) if exists("b:match_debug") let b:match_ini = ini - let b:match_tail = (strlen(mid) ? mid.'\|' : '') . fin + let b:match_tail = (strlen(mid) ? mid .. '\|' : '') .. fin endif " Fifth step: actually start moving the cursor and call searchpair(). @@ -210,25 +219,29 @@ function matchit#Match_wrapper(word, forward, mode) range if skip =~ 'synID' && !(has("syntax") && exists("g:syntax_on")) let skip = "0" else - execute "if " . skip . "| let skip = '0' | endif" + execute "if " .. skip .. "| let skip = '0' | endif" endif let sp_return = searchpair(ini, mid, fin, flag, skip) if &selection isnot# 'inclusive' && a:mode == 'v' " move cursor one pos to the right, because selection is not inclusive - " add virtualedit=onemore, to make it work even when the match ends the " line + " add virtualedit=onemore, to make it work even when the match ends the + " line if !(col('.') < col('$')-1) - set ve=onemore + let eolmark=1 " flag to set a mark on eol (since we cannot move there) endif norm! l endif - let final_position = "call cursor(" . line(".") . "," . col(".") . ")" + let final_position = "call cursor(" .. line(".") .. "," .. col(".") .. ")" " Restore cursor position and original screen. call winrestview(view) normal! m' if sp_return > 0 execute final_position endif - return s:CleanUp(restore_options, a:mode, startpos, mid.'\|'.fin) + if exists('eolmark') && eolmark + call setpos("''", [0, line('.'), col('$'), 0]) " set mark on the eol + endif + return s:CleanUp(restore_options, a:mode, startpos, mid .. '\|' .. fin) endfun " Restore options and do some special handling for Operator-pending mode. @@ -270,16 +283,16 @@ endfun " a:matchline = "123<tag>12" or "123</tag>12" " then extract "tag" from a:matchline and return "<tag>:</tag>" . fun! s:InsertRefs(groupBR, prefix, group, suffix, matchline) - if a:matchline !~ a:prefix . - \ substitute(a:group, s:notslash . '\zs:', '\\|', 'g') . a:suffix + if a:matchline !~ a:prefix .. + \ substitute(a:group, s:notslash .. '\zs:', '\\|', 'g') .. a:suffix return a:group endif - let i = matchend(a:groupBR, s:notslash . ':') + let i = matchend(a:groupBR, s:notslash .. ':') let ini = strpart(a:groupBR, 0, i-1) let tailBR = strpart(a:groupBR, i) let word = s:Choose(a:group, a:matchline, ":", "", a:prefix, a:suffix, \ a:groupBR) - let i = matchend(word, s:notslash . ":") + let i = matchend(word, s:notslash .. ":") let wordBR = strpart(word, i) let word = strpart(word, 0, i-1) " Now, a:matchline =~ a:prefix . word . a:suffix @@ -289,10 +302,10 @@ fun! s:InsertRefs(groupBR, prefix, group, suffix, matchline) let table = "" let d = 0 while d < 10 - if tailBR =~ s:notslash . '\\' . d - let table = table . d + if tailBR =~ s:notslash .. '\\' .. d + let table = table .. d else - let table = table . "-" + let table = table .. "-" endif let d = d + 1 endwhile @@ -300,13 +313,13 @@ fun! s:InsertRefs(groupBR, prefix, group, suffix, matchline) let d = 9 while d if table[d] != "-" - let backref = substitute(a:matchline, a:prefix.word.a:suffix, - \ '\'.table[d], "") + let backref = substitute(a:matchline, a:prefix .. word .. a:suffix, + \ '\' .. table[d], "") " Are there any other characters that should be escaped? let backref = escape(backref, '*,:') execute s:Ref(ini, d, "start", "len") - let ini = strpart(ini, 0, start) . backref . strpart(ini, start+len) - let tailBR = substitute(tailBR, s:notslash . '\zs\\' . d, + let ini = strpart(ini, 0, start) .. backref .. strpart(ini, start+len) + let tailBR = substitute(tailBR, s:notslash .. '\zs\\' .. d, \ escape(backref, '\\&'), 'g') endif let d = d-1 @@ -320,7 +333,7 @@ fun! s:InsertRefs(groupBR, prefix, group, suffix, matchline) let b:match_word = "" endif endif - return ini . ":" . tailBR + return ini .. ":" .. tailBR endfun " Input a comma-separated list of groups with backrefs, such as @@ -328,25 +341,25 @@ endfun " and return a comma-separated list of groups with backrefs replaced: " return '\(foo\):end\(foo\),\(bar\):end\(bar\)' fun! s:ParseWords(groups) - let groups = substitute(a:groups.",", s:notslash.'\zs[,:]*,[,:]*', ',', 'g') - let groups = substitute(groups, s:notslash . '\zs:\{2,}', ':', 'g') + let groups = substitute(a:groups .. ",", s:notslash .. '\zs[,:]*,[,:]*', ',', 'g') + let groups = substitute(groups, s:notslash .. '\zs:\{2,}', ':', 'g') let parsed = "" while groups =~ '[^,:]' - let i = matchend(groups, s:notslash . ':') - let j = matchend(groups, s:notslash . ',') + let i = matchend(groups, s:notslash .. ':') + let j = matchend(groups, s:notslash .. ',') let ini = strpart(groups, 0, i-1) - let tail = strpart(groups, i, j-i-1) . ":" + let tail = strpart(groups, i, j-i-1) .. ":" let groups = strpart(groups, j) - let parsed = parsed . ini - let i = matchend(tail, s:notslash . ':') + let parsed = parsed .. ini + let i = matchend(tail, s:notslash .. ':') while i != -1 " In 'if:else:endif', ini='if' and word='else' and then word='endif'. let word = strpart(tail, 0, i-1) let tail = strpart(tail, i) - let i = matchend(tail, s:notslash . ':') - let parsed = parsed . ":" . s:Resolve(ini, word, "word") + let i = matchend(tail, s:notslash .. ':') + let parsed = parsed .. ":" .. s:Resolve(ini, word, "word") endwhile " Now, tail has been used up. - let parsed = parsed . "," + let parsed = parsed .. "," endwhile " groups =~ '[^,:]' let parsed = substitute(parsed, ',$', '', '') return parsed @@ -364,14 +377,14 @@ endfun " let j = matchend(getline("."), regexp) " let match = matchstr(getline("."), regexp) fun! s:Wholematch(string, pat, start) - let group = '\%(' . a:pat . '\)' - let prefix = (a:start ? '\(^.*\%<' . (a:start + 2) . 'c\)\zs' : '^') + let group = '\%(' .. a:pat .. '\)' + let prefix = (a:start ? '\(^.*\%<' .. (a:start + 2) .. 'c\)\zs' : '^') let len = strlen(a:string) - let suffix = (a:start+1 < len ? '\(\%>'.(a:start+1).'c.*$\)\@=' : '$') - if a:string !~ prefix . group . suffix + let suffix = (a:start+1 < len ? '\(\%>' .. (a:start+1) .. 'c.*$\)\@=' : '$') + if a:string !~ prefix .. group .. suffix let prefix = '' endif - return prefix . group . suffix + return prefix .. group .. suffix endfun " No extra arguments: s:Ref(string, d) will @@ -392,7 +405,7 @@ fun! s:Ref(string, d, ...) let match = a:string while cnt let cnt = cnt - 1 - let index = matchend(match, s:notslash . '\\(') + let index = matchend(match, s:notslash .. '\\(') if index == -1 return "" endif @@ -404,7 +417,7 @@ fun! s:Ref(string, d, ...) endif let cnt = 1 while cnt - let index = matchend(match, s:notslash . '\\(\|\\)') - 1 + let index = matchend(match, s:notslash .. '\\(\|\\)') - 1 if index == -2 return "" endif @@ -418,7 +431,7 @@ fun! s:Ref(string, d, ...) if a:0 == 1 return len elseif a:0 == 2 - return "let " . a:1 . "=" . start . "| let " . a:2 . "=" . len + return "let " .. a:1 .. "=" .. start .. "| let " .. a:2 .. "=" .. len else return strpart(a:string, start, len) endif @@ -431,9 +444,9 @@ endfun fun! s:Count(string, pattern, ...) let pat = escape(a:pattern, '\\') if a:0 > 1 - let foo = substitute(a:string, '[^'.a:pattern.']', "a:1", "g") + let foo = substitute(a:string, '[^' .. a:pattern .. ']', "a:1", "g") let foo = substitute(a:string, pat, a:2, "g") - let foo = substitute(foo, '[^' . a:2 . ']', "", "g") + let foo = substitute(foo, '[^' .. a:2 .. ']', "", "g") return strlen(foo) endif let result = 0 @@ -456,7 +469,7 @@ endfun " unless it is preceded by "\". fun! s:Resolve(source, target, output) let word = a:target - let i = matchend(word, s:notslash . '\\\d') - 1 + let i = matchend(word, s:notslash .. '\\\d') - 1 let table = "----------" while i != -2 " There are back references to be replaced. let d = word[i] @@ -477,28 +490,28 @@ fun! s:Resolve(source, target, output) if table[s] == "-" if w + b < 10 " let table[s] = w + b - let table = strpart(table, 0, s) . (w+b) . strpart(table, s+1) + let table = strpart(table, 0, s) .. (w+b) .. strpart(table, s+1) endif let b = b + 1 let s = s + 1 else execute s:Ref(backref, b, "start", "len") let ref = strpart(backref, start, len) - let backref = strpart(backref, 0, start) . ":". table[s] - \ . strpart(backref, start+len) + let backref = strpart(backref, 0, start) .. ":" .. table[s] + \ .. strpart(backref, start+len) let s = s + s:Count(substitute(ref, '\\\\', '', 'g'), '\(', '1') endif endwhile - let word = strpart(word, 0, i-1) . backref . strpart(word, i+1) - let i = matchend(word, s:notslash . '\\\d') - 1 + let word = strpart(word, 0, i-1) .. backref .. strpart(word, i+1) + let i = matchend(word, s:notslash .. '\\\d') - 1 endwhile - let word = substitute(word, s:notslash . '\zs:', '\\', 'g') + let word = substitute(word, s:notslash .. '\zs:', '\\', 'g') if a:output == "table" return table elseif a:output == "word" return word else - return table . word + return table .. word endif endfun @@ -508,21 +521,21 @@ endfun " If <patn> is the first pattern that matches a:string then return <patn> " if no optional arguments are given; return <patn>,<altn> if a:1 is given. fun! s:Choose(patterns, string, comma, branch, prefix, suffix, ...) - let tail = (a:patterns =~ a:comma."$" ? a:patterns : a:patterns . a:comma) - let i = matchend(tail, s:notslash . a:comma) + let tail = (a:patterns =~ a:comma .. "$" ? a:patterns : a:patterns .. a:comma) + let i = matchend(tail, s:notslash .. a:comma) if a:0 - let alttail = (a:1 =~ a:comma."$" ? a:1 : a:1 . a:comma) - let j = matchend(alttail, s:notslash . a:comma) + let alttail = (a:1 =~ a:comma .. "$" ? a:1 : a:1 .. a:comma) + let j = matchend(alttail, s:notslash .. a:comma) endif let current = strpart(tail, 0, i-1) if a:branch == "" let currpat = current else - let currpat = substitute(current, s:notslash . a:branch, '\\|', 'g') + let currpat = substitute(current, s:notslash .. a:branch, '\\|', 'g') endif - while a:string !~ a:prefix . currpat . a:suffix + while a:string !~ a:prefix .. currpat .. a:suffix let tail = strpart(tail, i) - let i = matchend(tail, s:notslash . a:comma) + let i = matchend(tail, s:notslash .. a:comma) if i == -1 return -1 endif @@ -530,15 +543,15 @@ fun! s:Choose(patterns, string, comma, branch, prefix, suffix, ...) if a:branch == "" let currpat = current else - let currpat = substitute(current, s:notslash . a:branch, '\\|', 'g') + let currpat = substitute(current, s:notslash .. a:branch, '\\|', 'g') endif if a:0 let alttail = strpart(alttail, j) - let j = matchend(alttail, s:notslash . a:comma) + let j = matchend(alttail, s:notslash .. a:comma) endif endwhile if a:0 - let current = current . a:comma . strpart(alttail, 0, j-1) + let current = current .. a:comma .. strpart(alttail, 0, j-1) endif return current endfun @@ -562,7 +575,7 @@ fun! matchit#Match_debug() " fin = 'endif' piece, with all backrefs resolved from match amenu &Matchit.&word :echo b:match_word<CR> " '\'.d in ini refers to the same thing as '\'.table[d] in word. - amenu &Matchit.t&able :echo '0:' . b:match_table . ':9'<CR> + amenu &Matchit.t&able :echo '0:' .. b:match_table .. ':9'<CR> endfun " Jump to the nearest unmatched "(" or "if" or "<tag>" if a:spflag == "bW" @@ -598,26 +611,26 @@ fun! matchit#MultiMatch(spflag, mode) endif if (match_words != s:last_words) || (&mps != s:last_mps) || \ exists("b:match_debug") - let default = escape(&mps, '[$^.*~\\/?]') . (strlen(&mps) ? "," : "") . + let default = escape(&mps, '[$^.*~\\/?]') .. (strlen(&mps) ? "," : "") .. \ '\/\*:\*\/,#\s*if\%(n\=def\)\=:#\s*else\>:#\s*elif\>:#\s*endif\>' let s:last_mps = &mps - let match_words = match_words . (strlen(match_words) ? "," : "") . default + let match_words = match_words .. (strlen(match_words) ? "," : "") .. default let s:last_words = match_words - if match_words !~ s:notslash . '\\\d' + if match_words !~ s:notslash .. '\\\d' let s:do_BR = 0 let s:pat = match_words else let s:do_BR = 1 let s:pat = s:ParseWords(match_words) endif - let s:all = '\%(' . substitute(s:pat, '[,:]\+', '\\|', 'g') . '\)' + let s:all = '\%(' .. substitute(s:pat, '[,:]\+', '\\|', 'g') .. '\)' if exists("b:match_debug") let b:match_pat = s:pat endif " Reconstruct the version with unresolved backrefs. - let s:patBR = substitute(match_words.',', - \ s:notslash.'\zs[,:]*,[,:]*', ',', 'g') - let s:patBR = substitute(s:patBR, s:notslash.'\zs:\{2,}', ':', 'g') + let s:patBR = substitute(match_words .. ',', + \ s:notslash .. '\zs[,:]*,[,:]*', ',', 'g') + let s:patBR = substitute(s:patBR, s:notslash .. '\zs:\{2,}', ':', 'g') endif " Second step: figure out the patterns for searchpair() @@ -625,23 +638,23 @@ fun! matchit#MultiMatch(spflag, mode) " - TODO: A lot of this is copied from matchit#Match_wrapper(). " - maybe even more functionality should be split off " - into separate functions! - let openlist = split(s:pat . ',', s:notslash . '\zs:.\{-}' . s:notslash . ',') - let midclolist = split(',' . s:pat, s:notslash . '\zs,.\{-}' . s:notslash . ':') - call map(midclolist, {-> split(v:val, s:notslash . ':')}) + let openlist = split(s:pat .. ',', s:notslash .. '\zs:.\{-}' .. s:notslash .. ',') + let midclolist = split(',' .. s:pat, s:notslash .. '\zs,.\{-}' .. s:notslash .. ':') + call map(midclolist, {-> split(v:val, s:notslash .. ':')}) let closelist = [] let middlelist = [] call map(midclolist, {i,v -> [extend(closelist, v[-1 : -1]), \ extend(middlelist, v[0 : -2])]}) - call map(openlist, {i,v -> v =~# s:notslash . '\\|' ? '\%(' . v . '\)' : v}) - call map(middlelist, {i,v -> v =~# s:notslash . '\\|' ? '\%(' . v . '\)' : v}) - call map(closelist, {i,v -> v =~# s:notslash . '\\|' ? '\%(' . v . '\)' : v}) + call map(openlist, {i,v -> v =~# s:notslash .. '\\|' ? '\%(' .. v .. '\)' : v}) + call map(middlelist, {i,v -> v =~# s:notslash .. '\\|' ? '\%(' .. v .. '\)' : v}) + call map(closelist, {i,v -> v =~# s:notslash .. '\\|' ? '\%(' .. v .. '\)' : v}) let open = join(openlist, ',') let middle = join(middlelist, ',') let close = join(closelist, ',') if exists("b:match_skip") let skip = b:match_skip elseif exists("b:match_comment") " backwards compatibility and testing! - let skip = "r:" . b:match_comment + let skip = "r:" .. b:match_comment else let skip = 's:comment\|string' endif @@ -650,18 +663,18 @@ fun! matchit#MultiMatch(spflag, mode) " Third step: call searchpair(). " Replace '\('--but not '\\('--with '\%(' and ',' with '\|'. - let openpat = substitute(open, '\%(' . s:notslash . '\)\@<=\\(', '\\%(', 'g') + let openpat = substitute(open, '\%(' .. s:notslash .. '\)\@<=\\(', '\\%(', 'g') let openpat = substitute(openpat, ',', '\\|', 'g') - let closepat = substitute(close, '\%(' . s:notslash . '\)\@<=\\(', '\\%(', 'g') + let closepat = substitute(close, '\%(' .. s:notslash .. '\)\@<=\\(', '\\%(', 'g') let closepat = substitute(closepat, ',', '\\|', 'g') - let middlepat = substitute(middle, '\%(' . s:notslash . '\)\@<=\\(', '\\%(', 'g') + let middlepat = substitute(middle, '\%(' .. s:notslash .. '\)\@<=\\(', '\\%(', 'g') let middlepat = substitute(middlepat, ',', '\\|', 'g') if skip =~ 'synID' && !(has("syntax") && exists("g:syntax_on")) let skip = '0' else try - execute "if " . skip . "| let skip = '0' | endif" + execute "if " .. skip .. "| let skip = '0' | endif" catch /^Vim\%((\a\+)\)\=:E363/ " We won't find anything, so skip searching, should keep Vim responsive. return {} @@ -744,15 +757,15 @@ fun! s:ParseSkip(str) let skip = a:str if skip[1] == ":" if skip[0] == "s" - let skip = "synIDattr(synID(line('.'),col('.'),1),'name') =~? '" . - \ strpart(skip,2) . "'" + let skip = "synIDattr(synID(line('.'),col('.'),1),'name') =~? '" .. + \ strpart(skip,2) .. "'" elseif skip[0] == "S" - let skip = "synIDattr(synID(line('.'),col('.'),1),'name') !~? '" . - \ strpart(skip,2) . "'" + 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/matchit/doc/matchit.txt b/runtime/pack/dist/opt/matchit/doc/matchit.txt index b719fae730..553359ffaf 100644 --- a/runtime/pack/dist/opt/matchit/doc/matchit.txt +++ b/runtime/pack/dist/opt/matchit/doc/matchit.txt @@ -4,7 +4,7 @@ For instructions on installing this file, type `:help matchit-install` inside Vim. -For Vim version 8.1. Last change: 2021 Nov 13 +For Vim version 8.2. Last change: 2021 Dec 24 VIM REFERENCE MANUAL by Benji Fisher et al @@ -150,6 +150,10 @@ To use the matchit plugin add this line to your |vimrc|: > The script should start working the next time you start Vim. +To use the matchit plugin after startup, you can use this command (note the +omitted '!'): > + packadd matchit + (Earlier versions of the script did nothing unless a |buffer-variable| named |b:match_words| was defined. Even earlier versions contained autocommands that set this variable for various file types. Now, |b:match_words| is @@ -378,8 +382,8 @@ The back reference '\'.d refers to the same thing as '\'.b:match_table[d] in 5. Known Bugs and Limitations *matchit-bugs* Repository: https://github.com/chrisbra/matchit/ -Bugs can be reported at the repository (alternatively you can send me a mail). -The latest development snapshot can also be downloaded there. +Bugs can be reported at the repository and the latest development snapshot can +also be downloaded there. Just because I know about a bug does not mean that it is on my todo list. I try to respond to reports of bugs that cause real problems. If it does not diff --git a/runtime/pack/dist/opt/matchit/plugin/matchit.vim b/runtime/pack/dist/opt/matchit/plugin/matchit.vim index b62cc3913a..51ba3a7f51 100644 --- a/runtime/pack/dist/opt/matchit/plugin/matchit.vim +++ b/runtime/pack/dist/opt/matchit/plugin/matchit.vim @@ -1,7 +1,7 @@ " matchit.vim: (global plugin) Extended "%" matching " Maintainer: Christian Brabandt -" Version: 1.17 -" Last Change: 2019 Oct 24 +" Version: 1.18 +" Last Change: 2020 Dec 23 " Repository: https://github.com/chrisbra/matchit " Previous URL:http://www.vim.org/script.php?script_id=39 " Previous Maintainer: Benji Fisher PhD <benji@member.AMS.org> @@ -48,18 +48,12 @@ set cpo&vim nnoremap <silent> <Plug>(MatchitNormalForward) :<C-U>call matchit#Match_wrapper('',1,'n')<CR> nnoremap <silent> <Plug>(MatchitNormalBackward) :<C-U>call matchit#Match_wrapper('',0,'n')<CR> -xnoremap <silent> <Plug>(MatchitVisualForward) :<C-U>call matchit#Match_wrapper('',1,'v')<CR>m'gv`` +xnoremap <silent> <Plug>(MatchitVisualForward) :<C-U>call matchit#Match_wrapper('',1,'v')<CR> + \:if col("''") != col("$") \| exe ":normal! m'" \| endif<cr>gv`` xnoremap <silent> <Plug>(MatchitVisualBackward) :<C-U>call matchit#Match_wrapper('',0,'v')<CR>m'gv`` onoremap <silent> <Plug>(MatchitOperationForward) :<C-U>call matchit#Match_wrapper('',1,'o')<CR> onoremap <silent> <Plug>(MatchitOperationBackward) :<C-U>call matchit#Match_wrapper('',0,'o')<CR> -nmap <silent> % <Plug>(MatchitNormalForward) -nmap <silent> g% <Plug>(MatchitNormalBackward) -xmap <silent> % <Plug>(MatchitVisualForward) -xmap <silent> g% <Plug>(MatchitVisualBackward) -omap <silent> % <Plug>(MatchitOperationForward) -omap <silent> g% <Plug>(MatchitOperationBackward) - " Analogues of [{ and ]} using matching patterns: nnoremap <silent> <Plug>(MatchitNormalMultiBackward) :<C-U>call matchit#MultiMatch("bW", "n")<CR> nnoremap <silent> <Plug>(MatchitNormalMultiForward) :<C-U>call matchit#MultiMatch("W", "n")<CR> @@ -68,16 +62,28 @@ xnoremap <silent> <Plug>(MatchitVisualMultiForward) :<C-U>call matchit#Multi onoremap <silent> <Plug>(MatchitOperationMultiBackward) :<C-U>call matchit#MultiMatch("bW", "o")<CR> onoremap <silent> <Plug>(MatchitOperationMultiForward) :<C-U>call matchit#MultiMatch("W", "o")<CR> -nmap <silent> [% <Plug>(MatchitNormalMultiBackward) -nmap <silent> ]% <Plug>(MatchitNormalMultiForward) -xmap <silent> [% <Plug>(MatchitVisualMultiBackward) -xmap <silent> ]% <Plug>(MatchitVisualMultiForward) -omap <silent> [% <Plug>(MatchitOperationMultiBackward) -omap <silent> ]% <Plug>(MatchitOperationMultiForward) - " text object: xmap <silent> <Plug>(MatchitVisualTextObject) <Plug>(MatchitVisualMultiBackward)o<Plug>(MatchitVisualMultiForward) -xmap a% <Plug>(MatchitVisualTextObject) + +if !exists("g:no_plugin_maps") + nmap <silent> % <Plug>(MatchitNormalForward) + nmap <silent> g% <Plug>(MatchitNormalBackward) + xmap <silent> % <Plug>(MatchitVisualForward) + xmap <silent> g% <Plug>(MatchitVisualBackward) + omap <silent> % <Plug>(MatchitOperationForward) + omap <silent> g% <Plug>(MatchitOperationBackward) + + " Analogues of [{ and ]} using matching patterns: + nmap <silent> [% <Plug>(MatchitNormalMultiBackward) + nmap <silent> ]% <Plug>(MatchitNormalMultiForward) + xmap <silent> [% <Plug>(MatchitVisualMultiBackward) + xmap <silent> ]% <Plug>(MatchitVisualMultiForward) + omap <silent> [% <Plug>(MatchitOperationMultiBackward) + omap <silent> ]% <Plug>(MatchitOperationMultiForward) + + " Text object + xmap a% <Plug>(MatchitVisualTextObject) +endif " Call this function to turn on debugging information. Every time the main " script is run, buffer variables will be saved. These can be used directly diff --git a/runtime/pack/dist/opt/termdebug/plugin/termdebug.vim b/runtime/pack/dist/opt/termdebug/plugin/termdebug.vim index ae0242a312..71456e788d 100644 --- a/runtime/pack/dist/opt/termdebug/plugin/termdebug.vim +++ b/runtime/pack/dist/opt/termdebug/plugin/termdebug.vim @@ -2,12 +2,13 @@ " " Author: Bram Moolenaar " Copyright: Vim license applies, see ":help license" -" Last Change: 2021 Nov 14 +" 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 -" MingW is too old (7.6.1). -" I used version 7.12 from http://www.equation.com/servlet/equation.cmd?fa=gdb +" WORK IN PROGRESS - The basics works stable, more to come +" Note: In general you need at least GDB 7.12 because this provides the +" frame= response in MI thread-selected events we need to sync stack to file. +" The one included with "old" MingW is too old (7.6.1), you may upgrade it or +" use a newer version from http://www.equation.com/servlet/equation.cmd?fa=gdb " " There are two ways to run gdb: " - In a terminal window; used if possible, does not work on MS-Windows @@ -102,6 +103,11 @@ endfunc call s:Highlight(1, '', &background) hi default debugBreakpoint term=reverse ctermbg=red guibg=red +hi default debugBreakpointDisabled term=reverse ctermbg=gray guibg=gray + +func s:GetCommand() + return type(g:termdebugger) == v:t_list ? copy(g:termdebugger) : [g:termdebugger] +endfunc func s:StartDebug(bang, ...) " First argument is the command to debug, second core file or process ID. @@ -118,8 +124,9 @@ func s:StartDebug_internal(dict) echoerr 'Terminal debugger already running, cannot run two' return endif - if !executable(g:termdebugger) - echoerr 'Cannot execute debugger program "' .. g:termdebugger .. '"' + let gdbcmd = s:GetCommand() + if !executable(gdbcmd[0]) + echoerr 'Cannot execute debugger program "' .. gdbcmd[0] .. '"' return endif @@ -147,7 +154,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 @@ -190,8 +197,8 @@ func s:CloseBuffers() endfunc func s:CheckGdbRunning() - if nvim_get_chan_info(s:gdb_job_id) == {} - echoerr string(g:termdebugger) . ' exited unexpectedly' + if !s:running + echoerr string(s:GetCommand()[0]) . ' exited unexpectedly' call s:CloseBuffers() return '' endif @@ -241,15 +248,29 @@ func s:StartDebug_term(dict) let comm_job_info = nvim_get_chan_info(s:comm_job_id) let commpty = comm_job_info['pty'] - " Open a terminal window to run the debugger. - " Add -quiet to avoid the intro message causing a hit-enter prompt. let gdb_args = get(a:dict, 'gdb_args', []) let proc_args = get(a:dict, 'proc_args', []) - let cmd = [g:termdebugger, '-quiet', '-tty', pty, '--eval-command', 'echo startupdone\n'] + gdb_args - "call ch_log('executing "' . join(cmd) . '"') + let gdb_cmd = s:GetCommand() + " Add -quiet to avoid the intro message causing a hit-enter prompt. + let gdb_cmd += ['-quiet'] + " Disable pagination, it causes everything to stop at the gdb + let gdb_cmd += ['-iex', 'set pagination off'] + " Interpret commands while the target is running. This should usually only + " be exec-interrupt, since many commands don't work properly while the + " target is running (so execute during startup). + let gdb_cmd += ['-iex', 'set mi-async on'] + " Open a terminal window to run the debugger. + let gdb_cmd += ['-tty', pty] + " Command executed _after_ startup is done, provides us with the necessary feedback + let gdb_cmd += ['-ex', 'echo startupdone\n'] + + " Adding arguments requested by the user + let gdb_cmd += gdb_args + execute 'new' - let s:gdb_job_id = termopen(cmd, {'on_exit': function('s:EndTermDebug')}) + " call ch_log('executing "' . join(gdb_cmd) . '"') + let s:gdb_job_id = termopen(gdb_cmd, {'on_exit': function('s:EndTermDebug')}) if s:gdb_job_id == 0 echoerr 'invalid argument (or job table is full) while opening gdb terminal window' exe 'bwipe! ' . s:ptybuf @@ -259,6 +280,8 @@ func s:StartDebug_term(dict) call s:CloseBuffers() return endif + let s:running = v:true + let s:starting = v:true let gdb_job_info = nvim_get_chan_info(s:gdb_job_id) let s:gdbbuf = gdb_job_info['buffer'] let s:gdbwin = win_getid(winnr()) @@ -272,8 +295,8 @@ func s:StartDebug_term(dict) for lnum in range(1, 200) if get(getbufline(s:gdbbuf, lnum), 0, '') =~ 'startupdone' - let try_count = 9999 - break + let try_count = 9999 + break endif endfor let try_count += 1 @@ -286,11 +309,12 @@ func s:StartDebug_term(dict) " Set arguments to be run. if len(proc_args) - call chansend(s:gdb_job_id, 'set args ' . join(proc_args) . "\r") + call chansend(s:gdb_job_id, 'server set args ' . join(proc_args) . "\r") endif - " Connect gdb to the communication pty, using the GDB/MI interface - call chansend(s:gdb_job_id, 'new-ui mi ' . commpty . "\r") + " Connect gdb to the communication pty, using the GDB/MI interface. + " Prefix "server" to avoid adding this to the history. + call chansend(s:gdb_job_id, 'server new-ui mi ' . commpty . "\r") " Wait for the response to show up, users may not notice the error and wonder " why the debugger doesn't work. @@ -309,7 +333,8 @@ func s:StartDebug_term(dict) let response = line1 . line2 if response =~ 'Undefined command' echoerr 'Sorry, your gdb is too old, gdb 7.12 is required' - call s:CloseBuffers() + " CHECKME: possibly send a "server show version" here + call s:CloseBuffers() return endif if response =~ 'New UI allocated' @@ -332,16 +357,7 @@ func s:StartDebug_term(dict) sleep 10m endwhile - " Interpret commands while the target is running. This should usually only be - " exec-interrupt, since many commands don't work properly while the target is - " running. - call s:SendCommand('-gdb-set mi-async on') - " Older gdb uses a different command. - call s:SendCommand('-gdb-set target-async on') - - " Disable pagination, it causes everything to stop at the gdb - " "Type <return> to continue" prompt. - call s:SendCommand('set pagination off') + let s:starting = v:false " Set the filetype, this can be used to add mappings. set filetype=termdebug @@ -370,14 +386,26 @@ func s:StartDebug_prompt(dict) exe (&columns / 2 - 1) . "wincmd |" endif - " Add -quiet to avoid the intro message causing a hit-enter prompt. let gdb_args = get(a:dict, 'gdb_args', []) let proc_args = get(a:dict, 'proc_args', []) - let cmd = [g:termdebugger, '-quiet', '--interpreter=mi2'] + gdb_args - "call ch_log('executing "' . join(cmd) . '"') + let gdb_cmd = s:GetCommand() + " Add -quiet to avoid the intro message causing a hit-enter prompt. + let gdb_cmd += ['-quiet'] + " Disable pagination, it causes everything to stop at the gdb, needs to be run early + let gdb_cmd += ['-iex', 'set pagination off'] + " Interpret commands while the target is running. This should usually only + " be exec-interrupt, since many commands don't work properly while the + " target is running (so execute during startup). + let gdb_cmd += ['-iex', 'set mi-async on'] + " directly communicate via mi2 + let gdb_cmd += ['--interpreter=mi2'] - let s:gdbjob = jobstart(cmd, { + " Adding arguments requested by the user + let gdb_cmd += gdb_args + + " call ch_log('executing "' . join(gdb_cmd) . '"') + let s:gdbjob = jobstart(gdb_cmd, { \ 'on_exit': function('s:EndPromptDebug'), \ 'on_stdout': function('s:GdbOutCallback'), \ }) @@ -391,13 +419,6 @@ func s:StartDebug_prompt(dict) return endif - " Interpret commands while the target is running. This should usually only - " be exec-interrupt, since many commands don't work properly while the - " target is running. - call s:SendCommand('-gdb-set mi-async on') - " Older gdb uses a different command. - call s:SendCommand('-gdb-set target-async on') - let s:ptybuf = 0 if has('win32') " MS-Windows: run in a new console window for maximum compatibility @@ -432,8 +453,6 @@ func s:StartDebug_prompt(dict) endif call s:SendCommand('set print pretty on') call s:SendCommand('set breakpoint pending on') - " Disable pagination, it causes everything to stop at the gdb - call s:SendCommand('set pagination off') " Set arguments to be run if len(proc_args) @@ -476,7 +495,7 @@ func s:StartDebugCommon(dict) " Run the command if the bang attribute was given and got to the debug " window. if get(a:dict, 'bang', 0) - call s:SendCommand('-exec-run') + call s:SendResumingCommand('-exec-run') call win_gotoid(s:ptywin) endif endfunc @@ -504,7 +523,7 @@ func TermDebugSendCommand(cmd) " needed once. call jobstop(s:gdbjob) else - call s:SendCommand('-exec-interrupt') + Stop endif sleep 10m endif @@ -515,6 +534,20 @@ func TermDebugSendCommand(cmd) endif endfunc +" Send a command that resumes the program. If the program isn't stopped the +" command is not sent (to avoid a repeated command to cause trouble). +" If the command is sent then reset s:stopped. +func s:SendResumingCommand(cmd) + if s:stopped + " reset s:stopped here, it may take a bit of time before we get a response + let s:stopped = 0 + " call ch_log('assume that program is running after this command') + call s:SendCommand(a:cmd) + " else + " call ch_log('dropping command, program is running: ' . a:cmd) + endif +endfunc + " Function called when entering a line in the prompt buffer. func s:PromptCallback(text) call s:SendCommand(a:text) @@ -583,33 +616,33 @@ func s:GdbOutCallback(job_id, msgs, event) endfunc " Decode a message from gdb. quotedText starts with a ", return the text up -" to the next ", unescaping characters. +" to the next ", unescaping characters: +" - remove line breaks +" - change \\t to \t +" - change \0xhh to \xhh (disabled for now) +" - change \ooo to octal +" - change \\ to \ func s:DecodeMessage(quotedText) if a:quotedText[0] != '"' echoerr 'DecodeMessage(): missing quote in ' . a:quotedText return endif - let result = '' - let i = 1 - while a:quotedText[i] != '"' && i < len(a:quotedText) - if a:quotedText[i] == '\' - let i += 1 - if a:quotedText[i] == 'n' - " drop \n - let i += 1 - continue - elseif a:quotedText[i] == 't' - " append \t - let i += 1 - let result .= "\t" - continue - endif - endif - let result .= a:quotedText[i] - let i += 1 - endwhile - return result + return a:quotedText + \ ->substitute('^"\|".*\|\\n', '', 'g') + \ ->substitute('\\t', "\t", 'g') + " multi-byte characters arrive in octal form + " NULL-values must be kept encoded as those break the string otherwise + \ ->substitute('\\000', s:NullRepl, 'g') + \ ->substitute('\\\o\o\o', {-> eval('"' .. submatch(0) .. '"')}, 'g') + " Note: GDB docs also mention hex encodings - the translations below work + " but we keep them out for performance-reasons until we actually see + " those in mi-returns + " \ ->substitute('\\0x\(\x\x\)', {-> eval('"\x' .. submatch(1) .. '"')}, 'g') + " \ ->substitute('\\0x00', s:NullRepl, 'g') + \ ->substitute('\\\\', '\', 'g') + \ ->substitute(s:NullRepl, '\\000', 'g') endfunc +const s:NullRepl = 'XXXNULLXXX' " Extract the "name" value from a gdb message with fullname="name". func s:GetFullname(msg) @@ -634,6 +667,11 @@ func s:GetAsmAddr(msg) endfunc function s:EndTermDebug(job_id, exit_code, event) + let s:running = v:false + if s:starting + return + endif + if exists('#User#TermdebugStopPre') doauto <nomodeline> User TermdebugStopPre endif @@ -657,8 +695,8 @@ func s:EndDebugCommon() if bufexists(bufnr) exe bufnr .. "buf" if exists('b:save_signcolumn') - let &signcolumn = b:save_signcolumn - unlet b:save_signcolumn + let &signcolumn = b:save_signcolumn + unlet b:save_signcolumn endif endif endfor @@ -770,7 +808,9 @@ func s:CommOutput(job_id, msgs, event) if msg =~ '^\(\*stopped\|\*running\|=thread-selected\)' call s:HandleCursor(msg) elseif msg =~ '^\^done,bkpt=' || msg =~ '^=breakpoint-created,' - call s:HandleNewBreakpoint(msg) + call s:HandleNewBreakpoint(msg, 0) + elseif msg =~ '^=breakpoint-modified,' + call s:HandleNewBreakpoint(msg, 1) elseif msg =~ '^=breakpoint-deleted,' call s:HandleBreakpointDelete(msg) elseif msg =~ '^=thread-group-started' @@ -804,17 +844,20 @@ func s:InstallCommands() command -nargs=? Break call s:SetBreakpoint(<q-args>) command Clear call s:ClearBreakpoint() - command Step call s:SendCommand('-exec-step') - command Over call s:SendCommand('-exec-next') - command Finish call s:SendCommand('-exec-finish') + command Step call s:SendResumingCommand('-exec-step') + command Over call s:SendResumingCommand('-exec-next') + command Finish call s:SendResumingCommand('-exec-finish') command -nargs=* Run call s:Run(<q-args>) - command -nargs=* Arguments call s:SendCommand('-exec-arguments ' . <q-args>) - command Stop call s:SendCommand('-exec-interrupt') + command -nargs=* Arguments call s:SendResumingCommand('-exec-arguments ' . <q-args>) - " using -exec-continue results in CTRL-C in gdb window not working if s:way == 'prompt' + command Stop call s:PromptInterrupt() command Continue call s:SendCommand('continue') else + command Stop call s:SendCommand('-exec-interrupt') + " using -exec-continue results in CTRL-C in the gdb window not working, + " communicating via commbuf (= use of SendCommand) has the same result + "command Continue call s:SendCommand('-exec-continue') command Continue call chansend(s:gdb_job_id, "continue\r") endif @@ -913,20 +956,16 @@ func s:SetBreakpoint(at) let do_continue = 0 if !s:stopped let do_continue = 1 - if s:way == 'prompt' - call s:PromptInterrupt() - else - call s:SendCommand('-exec-interrupt') - endif + Stop sleep 10m endif " Use the fname:lnum format, older gdb can't handle --source. let at = empty(a:at) ? - \ fnameescape(expand('%:p')) . ':' . line('.') : a:at + \ fnameescape(expand('%:p')) . ':' . line('.') : a:at call s:SendCommand('-break-insert ' . at) if do_continue - call s:SendCommand('-exec-continue') + Continue endif endfunc @@ -937,72 +976,112 @@ func s:ClearBreakpoint() let bploc = printf('%s:%d', fname, lnum) if has_key(s:breakpoint_locations, bploc) let idx = 0 + let nr = 0 for id in s:breakpoint_locations[bploc] if has_key(s:breakpoints, id) - " Assume this always works, the reply is simply "^done". - call s:SendCommand('-break-delete ' . id) - for subid in keys(s:breakpoints[id]) - exe 'sign unplace ' . s:Breakpoint2SignNumber(id, subid) - endfor - unlet s:breakpoints[id] - unlet s:breakpoint_locations[bploc][idx] - break + " Assume this always works, the reply is simply "^done". + call s:SendCommand('-break-delete ' . id) + for subid in keys(s:breakpoints[id]) + exe 'sign unplace ' . s:Breakpoint2SignNumber(id, subid) + endfor + unlet s:breakpoints[id] + unlet s:breakpoint_locations[bploc][idx] + let nr = id + break else - let idx += 1 + let idx += 1 endif endfor - if empty(s:breakpoint_locations[bploc]) - unlet s:breakpoint_locations[bploc] + if nr != 0 + if empty(s:breakpoint_locations[bploc]) + unlet s:breakpoint_locations[bploc] + endif + echomsg 'Breakpoint ' . id . ' cleared from line ' . lnum . '.' + else + echoerr 'Internal error trying to remove breakpoint at line ' . lnum . '!' endif + else + echomsg 'No breakpoint to remove at line ' . lnum . '.' endif endfunc func s:Run(args) if a:args != '' - call s:SendCommand('-exec-arguments ' . a:args) + call s:SendResumingCommand('-exec-arguments ' . a:args) endif - call s:SendCommand('-exec-run') + call s:SendResumingCommand('-exec-run') endfunc func s:SendEval(expr) - " clean up expression that may got in because of range - " (newlines and surrounding spaces) - let expr = a:expr - if &filetype ==# 'cobol' - " extra cleanup for COBOL: _every: expression ends with a period, - " a trailing comma is ignored as it commonly separates multiple expr. - let expr = substitute(expr, '\..*', '', '') - let expr = substitute(expr, '[;\n]', ' ', 'g') - let expr = substitute(expr, ',*$', '', '') + " check for "likely" boolean expressions, in which case we take it as lhs + if a:expr =~ "[=!<>]=" + let exprLHS = a:expr else - let expr = substitute(expr, '\n', ' ', 'g') + " remove text that is likely an assignment + let exprLHS = substitute(a:expr, ' *=.*', '', '') endif - let expr = substitute(expr, '^ *\(.*\) *', '\1', '') + " encoding expression to prevent bad errors + let expr = a:expr + let expr = substitute(expr, '\\', '\\\\', 'g') + let expr = substitute(expr, '"', '\\"', 'g') call s:SendCommand('-data-evaluate-expression "' . expr . '"') - let s:evalexpr = expr + let s:evalexpr = exprLHS endfunc -" :Evaluate - evaluate what is under the cursor +" :Evaluate - evaluate what is specified / under the cursor func s:Evaluate(range, arg) + let expr = s:GetEvaluationExpression(a:range, a:arg) + let s:ignoreEvalError = 0 + call s:SendEval(expr) +endfunc + +" get what is specified / under the cursor +func s:GetEvaluationExpression(range, arg) if a:arg != '' - let expr = a:arg - let s:evalFromBalloonExpr = 0 + " user supplied evaluation + let expr = s:CleanupExpr(a:arg) + " DSW: replace "likely copy + paste" assignment + let expr = substitute(expr, '"\([^"]*\)": *', '\1=', 'g') elseif a:range == 2 let pos = getcurpos() let reg = getreg('v', 1, 1) let regt = getregtype('v') normal! gv"vy - let expr = @v + let expr = s:CleanupExpr(@v) call setpos('.', pos) call setreg('v', reg, regt) let s:evalFromBalloonExpr = 1 else + " no evaluation provided: get from C-expression under cursor + " TODO: allow filetype specific lookup #9057 let expr = expand('<cexpr>') let s:evalFromBalloonExpr = 1 endif - let s:ignoreEvalError = 0 - call s:SendEval(expr) + return expr +endfunc + +" 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) +func s:CleanupExpr(expr) + " replace all embedded newlines/tabs/... + let expr = substitute(a:expr, '\_s', ' ', 'g') + + if &filetype ==# 'cobol' + " extra cleanup for COBOL: + " - a semicolon nmay be used instead of a space + " - a trailing comma or period is ignored as it commonly separates/ends + " multiple expr + let expr = substitute(expr, ';', ' ', 'g') + let expr = substitute(expr, '[,.]\+ *$', '', '') + endif + + " get rid of leading and trailing spaces + let expr = substitute(expr, '^ *', '', '') + let expr = substitute(expr, ' *$', '', '') + return expr endfunc let s:ignoreEvalError = 0 @@ -1011,9 +1090,20 @@ let s:evalFromBalloonExprResult = '' " Handle the result of data-evaluate-expression func s:HandleEvaluate(msg) - let value = substitute(a:msg, '.*value="\(.*\)"', '\1', '') - let value = substitute(value, '\\"', '"', 'g') - let value = substitute(value, '
', '\1', '') + let value = a:msg + \ ->substitute('.*value="\(.*\)"', '\1', '') + \ ->substitute('\\"', '"', 'g') + \ ->substitute('\\\\', '\\', 'g') + "\ 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 + "\ but we keep them out for performance-reasons until we actually see + "\ those in mi-returns + "\ ->substitute('\\0x00', s:NullRep, 'g') + "\ ->substitute('\\0x\(\x\x\)', {-> eval('"\x' .. submatch(1) .. '"')}, 'g') + \ ->substitute(s:NullRepl, '\\000', 'g') + \ ->substitute('
', '\1', '') if s:evalFromBalloonExpr if s:evalFromBalloonExprResult == '' let s:evalFromBalloonExprResult = s:evalexpr . ': ' . value @@ -1246,15 +1336,15 @@ func s:HandleCursor(msg) let curwinid = win_getid(winnr()) if win_gotoid(s:asmwin) - let lnum = search('^' . s:asm_addr) - if lnum == 0 - call s:SendCommand('disassemble $pc') - else - exe 'sign unplace ' . s:asm_id - exe 'sign place ' . s:asm_id . ' line=' . lnum . ' name=debugPC' - endif + let lnum = search('^' . s:asm_addr) + if lnum == 0 + call s:SendCommand('disassemble $pc') + else + exe 'sign unplace ' . s:asm_id + exe 'sign place ' . s:asm_id . ' line=' . lnum . ' name=debugPC' + endif - call win_gotoid(curwinid) + call win_gotoid(curwinid) endif endif endif @@ -1264,6 +1354,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) @@ -1272,14 +1372,17 @@ func s:HandleCursor(msg) else exe 'edit ' . fnameescape(fname) endif + augroup Termdebug + au! SwapExists + augroup END endif exe lnum normal! zv exe 'sign unplace ' . s:pc_id exe 'sign place ' . s:pc_id . ' line=' . lnum . ' name=debugPC file=' . fname if !exists('b:save_signcolumn') - let b:save_signcolumn = &signcolumn - call add(s:signcolumn_buflist, bufnr()) + let b:save_signcolumn = &signcolumn + call add(s:signcolumn_buflist, bufnr()) endif setlocal signcolumn=yes endif @@ -1292,11 +1395,16 @@ endfunc let s:BreakpointSigns = [] -func s:CreateBreakpoint(id, subid) +func s:CreateBreakpoint(id, subid, enabled) let nr = printf('%d.%d', a:id, a:subid) if index(s:BreakpointSigns, nr) == -1 call add(s:BreakpointSigns, nr) - exe "sign define debugBreakpoint" . nr . " text=" . substitute(nr, '\..*', '', '') . " texthl=debugBreakpoint" + if a:enabled == "n" + let hiName = "debugBreakpointDisabled" + else + let hiName = "debugBreakpoint" + endif + exe "sign define debugBreakpoint" . nr . " text=" . substitute(nr, '\..*', '', '') . " texthl=" . hiName endif endfunc @@ -1306,9 +1414,14 @@ endfunction " Handle setting a breakpoint " Will update the sign that shows the breakpoint -func s:HandleNewBreakpoint(msg) +func s:HandleNewBreakpoint(msg, modifiedFlag) if a:msg !~ 'fullname=' - " a watch does not have a file name + " a watch or a pending breakpoint does not have a file name + if a:msg =~ 'pending=' + let nr = substitute(a:msg, '.*number=\"\([0-9.]*\)\".*', '\1', '') + let target = substitute(a:msg, '.*pending=\"\([^"]*\)\".*', '\1', '') + echomsg 'Breakpoint ' . nr . ' (' . target . ') pending.' + endif return endif for msg in s:SplitMsg(a:msg) @@ -1324,7 +1437,8 @@ func s:HandleNewBreakpoint(msg) " If "nr" is 123 it becomes "123.0" and subid is "0". " If "nr" is 123.4 it becomes "123.4.0" and subid is "4"; "0" is discarded. let [id, subid; _] = map(split(nr . '.0', '\.'), 'v:val + 0') - call s:CreateBreakpoint(id, subid) + let enabled = substitute(msg, '.*enabled="\([yn]\)".*', '\1', '') + call s:CreateBreakpoint(id, subid, enabled) if has_key(s:breakpoints, id) let entries = s:breakpoints[id] @@ -1351,7 +1465,18 @@ func s:HandleNewBreakpoint(msg) if bufloaded(fname) call s:PlaceSign(id, subid, entry) + let posMsg = ' at line ' . lnum . '.' + else + let posMsg = ' in ' . fname . ' at line ' . lnum . '.' + endif + if !a:modifiedFlag + let actionTaken = 'created' + elseif enabled == 'n' + let actionTaken = 'disabled' + else + let actionTaken = 'enabled' endif + echomsg 'Breakpoint ' . nr . ' ' . actionTaken . posMsg endfor endfunc @@ -1371,11 +1496,12 @@ func s:HandleBreakpointDelete(msg) if has_key(s:breakpoints, id) for [subid, entry] in items(s:breakpoints[id]) if has_key(entry, 'placed') - exe 'sign unplace ' . s:Breakpoint2SignNumber(id, subid) - unlet entry['placed'] + exe 'sign unplace ' . s:Breakpoint2SignNumber(id, subid) + unlet entry['placed'] endif endfor unlet s:breakpoints[id] + echomsg 'Breakpoint ' . id . ' cleared.' endif endfunc @@ -1396,7 +1522,7 @@ func s:BufRead() for [id, entries] in items(s:breakpoints) for [subid, entry] in items(entries) if entry['fname'] == fname - call s:PlaceSign(id, subid, entry) + call s:PlaceSign(id, subid, entry) endif endfor endfor @@ -1408,7 +1534,7 @@ func s:BufUnloaded() for [id, entries] in items(s:breakpoints) for [subid, entry] in items(entries) if entry['fname'] == fname - let entry['placed'] = 0 + let entry['placed'] = 0 endif endfor endfor diff --git a/runtime/rgb.txt b/runtime/rgb.txt deleted file mode 100644 index 5bc2baa3d6..0000000000 --- a/runtime/rgb.txt +++ /dev/null @@ -1,753 +0,0 @@ -! $XConsortium: rgb.txt,v 10.41 94/02/20 18:39:36 rws Exp $ -255 250 250 snow -248 248 255 ghost white -248 248 255 GhostWhite -245 245 245 white smoke -245 245 245 WhiteSmoke -220 220 220 gainsboro -255 250 240 floral white -255 250 240 FloralWhite -253 245 230 old lace -253 245 230 OldLace -250 240 230 linen -250 235 215 antique white -250 235 215 AntiqueWhite -255 239 213 papaya whip -255 239 213 PapayaWhip -255 235 205 blanched almond -255 235 205 BlanchedAlmond -255 228 196 bisque -255 218 185 peach puff -255 218 185 PeachPuff -255 222 173 navajo white -255 222 173 NavajoWhite -255 228 181 moccasin -255 248 220 cornsilk -255 255 240 ivory -255 250 205 lemon chiffon -255 250 205 LemonChiffon -255 245 238 seashell -240 255 240 honeydew -245 255 250 mint cream -245 255 250 MintCream -240 255 255 azure -240 248 255 alice blue -240 248 255 AliceBlue -230 230 250 lavender -255 240 245 lavender blush -255 240 245 LavenderBlush -255 228 225 misty rose -255 228 225 MistyRose -255 255 255 white - 0 0 0 black - 47 79 79 dark slate gray - 47 79 79 DarkSlateGray - 47 79 79 dark slate grey - 47 79 79 DarkSlateGrey -105 105 105 dim gray -105 105 105 DimGray -105 105 105 dim grey -105 105 105 DimGrey -112 128 144 slate gray -112 128 144 SlateGray -112 128 144 slate grey -112 128 144 SlateGrey -119 136 153 light slate gray -119 136 153 LightSlateGray -119 136 153 light slate grey -119 136 153 LightSlateGrey -190 190 190 gray -190 190 190 grey -211 211 211 light grey -211 211 211 LightGrey -211 211 211 light gray -211 211 211 LightGray - 25 25 112 midnight blue - 25 25 112 MidnightBlue - 0 0 128 navy - 0 0 128 navy blue - 0 0 128 NavyBlue -100 149 237 cornflower blue -100 149 237 CornflowerBlue - 72 61 139 dark slate blue - 72 61 139 DarkSlateBlue -106 90 205 slate blue -106 90 205 SlateBlue -123 104 238 medium slate blue -123 104 238 MediumSlateBlue -132 112 255 light slate blue -132 112 255 LightSlateBlue - 0 0 205 medium blue - 0 0 205 MediumBlue - 65 105 225 royal blue - 65 105 225 RoyalBlue - 0 0 255 blue - 30 144 255 dodger blue - 30 144 255 DodgerBlue - 0 191 255 deep sky blue - 0 191 255 DeepSkyBlue -135 206 235 sky blue -135 206 235 SkyBlue -135 206 250 light sky blue -135 206 250 LightSkyBlue - 70 130 180 steel blue - 70 130 180 SteelBlue -176 196 222 light steel blue -176 196 222 LightSteelBlue -173 216 230 light blue -173 216 230 LightBlue -176 224 230 powder blue -176 224 230 PowderBlue -175 238 238 pale turquoise -175 238 238 PaleTurquoise - 0 206 209 dark turquoise - 0 206 209 DarkTurquoise - 72 209 204 medium turquoise - 72 209 204 MediumTurquoise - 64 224 208 turquoise - 0 255 255 cyan -224 255 255 light cyan -224 255 255 LightCyan - 95 158 160 cadet blue - 95 158 160 CadetBlue -102 205 170 medium aquamarine -102 205 170 MediumAquamarine -127 255 212 aquamarine - 0 100 0 dark green - 0 100 0 DarkGreen - 85 107 47 dark olive green - 85 107 47 DarkOliveGreen -143 188 143 dark sea green -143 188 143 DarkSeaGreen - 46 139 87 sea green - 46 139 87 SeaGreen - 60 179 113 medium sea green - 60 179 113 MediumSeaGreen - 32 178 170 light sea green - 32 178 170 LightSeaGreen -152 251 152 pale green -152 251 152 PaleGreen - 0 255 127 spring green - 0 255 127 SpringGreen -124 252 0 lawn green -124 252 0 LawnGreen - 0 255 0 green -127 255 0 chartreuse - 0 250 154 medium spring green - 0 250 154 MediumSpringGreen -173 255 47 green yellow -173 255 47 GreenYellow - 50 205 50 lime green - 50 205 50 LimeGreen -154 205 50 yellow green -154 205 50 YellowGreen - 34 139 34 forest green - 34 139 34 ForestGreen -107 142 35 olive drab -107 142 35 OliveDrab -189 183 107 dark khaki -189 183 107 DarkKhaki -240 230 140 khaki -238 232 170 pale goldenrod -238 232 170 PaleGoldenrod -250 250 210 light goldenrod yellow -250 250 210 LightGoldenrodYellow -255 255 224 light yellow -255 255 224 LightYellow -255 255 0 yellow -255 215 0 gold -238 221 130 light goldenrod -238 221 130 LightGoldenrod -218 165 32 goldenrod -184 134 11 dark goldenrod -184 134 11 DarkGoldenrod -188 143 143 rosy brown -188 143 143 RosyBrown -205 92 92 indian red -205 92 92 IndianRed -139 69 19 saddle brown -139 69 19 SaddleBrown -160 82 45 sienna -205 133 63 peru -222 184 135 burlywood -245 245 220 beige -245 222 179 wheat -244 164 96 sandy brown -244 164 96 SandyBrown -210 180 140 tan -210 105 30 chocolate -178 34 34 firebrick -165 42 42 brown -233 150 122 dark salmon -233 150 122 DarkSalmon -250 128 114 salmon -255 160 122 light salmon -255 160 122 LightSalmon -255 165 0 orange -255 140 0 dark orange -255 140 0 DarkOrange -255 127 80 coral -240 128 128 light coral -240 128 128 LightCoral -255 99 71 tomato -255 69 0 orange red -255 69 0 OrangeRed -255 0 0 red -255 105 180 hot pink -255 105 180 HotPink -255 20 147 deep pink -255 20 147 DeepPink -255 192 203 pink -255 182 193 light pink -255 182 193 LightPink -219 112 147 pale violet red -219 112 147 PaleVioletRed -176 48 96 maroon -199 21 133 medium violet red -199 21 133 MediumVioletRed -208 32 144 violet red -208 32 144 VioletRed -255 0 255 magenta -238 130 238 violet -221 160 221 plum -218 112 214 orchid -186 85 211 medium orchid -186 85 211 MediumOrchid -153 50 204 dark orchid -153 50 204 DarkOrchid -148 0 211 dark violet -148 0 211 DarkViolet -138 43 226 blue violet -138 43 226 BlueViolet -160 32 240 purple -147 112 219 medium purple -147 112 219 MediumPurple -216 191 216 thistle -255 250 250 snow1 -238 233 233 snow2 -205 201 201 snow3 -139 137 137 snow4 -255 245 238 seashell1 -238 229 222 seashell2 -205 197 191 seashell3 -139 134 130 seashell4 -255 239 219 AntiqueWhite1 -238 223 204 AntiqueWhite2 -205 192 176 AntiqueWhite3 -139 131 120 AntiqueWhite4 -255 228 196 bisque1 -238 213 183 bisque2 -205 183 158 bisque3 -139 125 107 bisque4 -255 218 185 PeachPuff1 -238 203 173 PeachPuff2 -205 175 149 PeachPuff3 -139 119 101 PeachPuff4 -255 222 173 NavajoWhite1 -238 207 161 NavajoWhite2 -205 179 139 NavajoWhite3 -139 121 94 NavajoWhite4 -255 250 205 LemonChiffon1 -238 233 191 LemonChiffon2 -205 201 165 LemonChiffon3 -139 137 112 LemonChiffon4 -255 248 220 cornsilk1 -238 232 205 cornsilk2 -205 200 177 cornsilk3 -139 136 120 cornsilk4 -255 255 240 ivory1 -238 238 224 ivory2 -205 205 193 ivory3 -139 139 131 ivory4 -240 255 240 honeydew1 -224 238 224 honeydew2 -193 205 193 honeydew3 -131 139 131 honeydew4 -255 240 245 LavenderBlush1 -238 224 229 LavenderBlush2 -205 193 197 LavenderBlush3 -139 131 134 LavenderBlush4 -255 228 225 MistyRose1 -238 213 210 MistyRose2 -205 183 181 MistyRose3 -139 125 123 MistyRose4 -240 255 255 azure1 -224 238 238 azure2 -193 205 205 azure3 -131 139 139 azure4 -131 111 255 SlateBlue1 -122 103 238 SlateBlue2 -105 89 205 SlateBlue3 - 71 60 139 SlateBlue4 - 72 118 255 RoyalBlue1 - 67 110 238 RoyalBlue2 - 58 95 205 RoyalBlue3 - 39 64 139 RoyalBlue4 - 0 0 255 blue1 - 0 0 238 blue2 - 0 0 205 blue3 - 0 0 139 blue4 - 30 144 255 DodgerBlue1 - 28 134 238 DodgerBlue2 - 24 116 205 DodgerBlue3 - 16 78 139 DodgerBlue4 - 99 184 255 SteelBlue1 - 92 172 238 SteelBlue2 - 79 148 205 SteelBlue3 - 54 100 139 SteelBlue4 - 0 191 255 DeepSkyBlue1 - 0 178 238 DeepSkyBlue2 - 0 154 205 DeepSkyBlue3 - 0 104 139 DeepSkyBlue4 -135 206 255 SkyBlue1 -126 192 238 SkyBlue2 -108 166 205 SkyBlue3 - 74 112 139 SkyBlue4 -176 226 255 LightSkyBlue1 -164 211 238 LightSkyBlue2 -141 182 205 LightSkyBlue3 - 96 123 139 LightSkyBlue4 -198 226 255 SlateGray1 -185 211 238 SlateGray2 -159 182 205 SlateGray3 -108 123 139 SlateGray4 -202 225 255 LightSteelBlue1 -188 210 238 LightSteelBlue2 -162 181 205 LightSteelBlue3 -110 123 139 LightSteelBlue4 -191 239 255 LightBlue1 -178 223 238 LightBlue2 -154 192 205 LightBlue3 -104 131 139 LightBlue4 -224 255 255 LightCyan1 -209 238 238 LightCyan2 -180 205 205 LightCyan3 -122 139 139 LightCyan4 -187 255 255 PaleTurquoise1 -174 238 238 PaleTurquoise2 -150 205 205 PaleTurquoise3 -102 139 139 PaleTurquoise4 -152 245 255 CadetBlue1 -142 229 238 CadetBlue2 -122 197 205 CadetBlue3 - 83 134 139 CadetBlue4 - 0 245 255 turquoise1 - 0 229 238 turquoise2 - 0 197 205 turquoise3 - 0 134 139 turquoise4 - 0 255 255 cyan1 - 0 238 238 cyan2 - 0 205 205 cyan3 - 0 139 139 cyan4 -151 255 255 DarkSlateGray1 -141 238 238 DarkSlateGray2 -121 205 205 DarkSlateGray3 - 82 139 139 DarkSlateGray4 -127 255 212 aquamarine1 -118 238 198 aquamarine2 -102 205 170 aquamarine3 - 69 139 116 aquamarine4 -193 255 193 DarkSeaGreen1 -180 238 180 DarkSeaGreen2 -155 205 155 DarkSeaGreen3 -105 139 105 DarkSeaGreen4 - 84 255 159 SeaGreen1 - 78 238 148 SeaGreen2 - 67 205 128 SeaGreen3 - 46 139 87 SeaGreen4 -154 255 154 PaleGreen1 -144 238 144 PaleGreen2 -124 205 124 PaleGreen3 - 84 139 84 PaleGreen4 - 0 255 127 SpringGreen1 - 0 238 118 SpringGreen2 - 0 205 102 SpringGreen3 - 0 139 69 SpringGreen4 - 0 255 0 green1 - 0 238 0 green2 - 0 205 0 green3 - 0 139 0 green4 -127 255 0 chartreuse1 -118 238 0 chartreuse2 -102 205 0 chartreuse3 - 69 139 0 chartreuse4 -192 255 62 OliveDrab1 -179 238 58 OliveDrab2 -154 205 50 OliveDrab3 -105 139 34 OliveDrab4 -202 255 112 DarkOliveGreen1 -188 238 104 DarkOliveGreen2 -162 205 90 DarkOliveGreen3 -110 139 61 DarkOliveGreen4 -255 246 143 khaki1 -238 230 133 khaki2 -205 198 115 khaki3 -139 134 78 khaki4 -255 236 139 LightGoldenrod1 -238 220 130 LightGoldenrod2 -205 190 112 LightGoldenrod3 -139 129 76 LightGoldenrod4 -255 255 224 LightYellow1 -238 238 209 LightYellow2 -205 205 180 LightYellow3 -139 139 122 LightYellow4 -255 255 0 yellow1 -238 238 0 yellow2 -205 205 0 yellow3 -139 139 0 yellow4 -255 215 0 gold1 -238 201 0 gold2 -205 173 0 gold3 -139 117 0 gold4 -255 193 37 goldenrod1 -238 180 34 goldenrod2 -205 155 29 goldenrod3 -139 105 20 goldenrod4 -255 185 15 DarkGoldenrod1 -238 173 14 DarkGoldenrod2 -205 149 12 DarkGoldenrod3 -139 101 8 DarkGoldenrod4 -255 193 193 RosyBrown1 -238 180 180 RosyBrown2 -205 155 155 RosyBrown3 -139 105 105 RosyBrown4 -255 106 106 IndianRed1 -238 99 99 IndianRed2 -205 85 85 IndianRed3 -139 58 58 IndianRed4 -255 130 71 sienna1 -238 121 66 sienna2 -205 104 57 sienna3 -139 71 38 sienna4 -255 211 155 burlywood1 -238 197 145 burlywood2 -205 170 125 burlywood3 -139 115 85 burlywood4 -255 231 186 wheat1 -238 216 174 wheat2 -205 186 150 wheat3 -139 126 102 wheat4 -255 165 79 tan1 -238 154 73 tan2 -205 133 63 tan3 -139 90 43 tan4 -255 127 36 chocolate1 -238 118 33 chocolate2 -205 102 29 chocolate3 -139 69 19 chocolate4 -255 48 48 firebrick1 -238 44 44 firebrick2 -205 38 38 firebrick3 -139 26 26 firebrick4 -255 64 64 brown1 -238 59 59 brown2 -205 51 51 brown3 -139 35 35 brown4 -255 140 105 salmon1 -238 130 98 salmon2 -205 112 84 salmon3 -139 76 57 salmon4 -255 160 122 LightSalmon1 -238 149 114 LightSalmon2 -205 129 98 LightSalmon3 -139 87 66 LightSalmon4 -255 165 0 orange1 -238 154 0 orange2 -205 133 0 orange3 -139 90 0 orange4 -255 127 0 DarkOrange1 -238 118 0 DarkOrange2 -205 102 0 DarkOrange3 -139 69 0 DarkOrange4 -255 114 86 coral1 -238 106 80 coral2 -205 91 69 coral3 -139 62 47 coral4 -255 99 71 tomato1 -238 92 66 tomato2 -205 79 57 tomato3 -139 54 38 tomato4 -255 69 0 OrangeRed1 -238 64 0 OrangeRed2 -205 55 0 OrangeRed3 -139 37 0 OrangeRed4 -255 0 0 red1 -238 0 0 red2 -205 0 0 red3 -139 0 0 red4 -255 20 147 DeepPink1 -238 18 137 DeepPink2 -205 16 118 DeepPink3 -139 10 80 DeepPink4 -255 110 180 HotPink1 -238 106 167 HotPink2 -205 96 144 HotPink3 -139 58 98 HotPink4 -255 181 197 pink1 -238 169 184 pink2 -205 145 158 pink3 -139 99 108 pink4 -255 174 185 LightPink1 -238 162 173 LightPink2 -205 140 149 LightPink3 -139 95 101 LightPink4 -255 130 171 PaleVioletRed1 -238 121 159 PaleVioletRed2 -205 104 137 PaleVioletRed3 -139 71 93 PaleVioletRed4 -255 52 179 maroon1 -238 48 167 maroon2 -205 41 144 maroon3 -139 28 98 maroon4 -255 62 150 VioletRed1 -238 58 140 VioletRed2 -205 50 120 VioletRed3 -139 34 82 VioletRed4 -255 0 255 magenta1 -238 0 238 magenta2 -205 0 205 magenta3 -139 0 139 magenta4 -255 131 250 orchid1 -238 122 233 orchid2 -205 105 201 orchid3 -139 71 137 orchid4 -255 187 255 plum1 -238 174 238 plum2 -205 150 205 plum3 -139 102 139 plum4 -224 102 255 MediumOrchid1 -209 95 238 MediumOrchid2 -180 82 205 MediumOrchid3 -122 55 139 MediumOrchid4 -191 62 255 DarkOrchid1 -178 58 238 DarkOrchid2 -154 50 205 DarkOrchid3 -104 34 139 DarkOrchid4 -155 48 255 purple1 -145 44 238 purple2 -125 38 205 purple3 - 85 26 139 purple4 -171 130 255 MediumPurple1 -159 121 238 MediumPurple2 -137 104 205 MediumPurple3 - 93 71 139 MediumPurple4 -255 225 255 thistle1 -238 210 238 thistle2 -205 181 205 thistle3 -139 123 139 thistle4 - 0 0 0 gray0 - 0 0 0 grey0 - 3 3 3 gray1 - 3 3 3 grey1 - 5 5 5 gray2 - 5 5 5 grey2 - 8 8 8 gray3 - 8 8 8 grey3 - 10 10 10 gray4 - 10 10 10 grey4 - 13 13 13 gray5 - 13 13 13 grey5 - 15 15 15 gray6 - 15 15 15 grey6 - 18 18 18 gray7 - 18 18 18 grey7 - 20 20 20 gray8 - 20 20 20 grey8 - 23 23 23 gray9 - 23 23 23 grey9 - 26 26 26 gray10 - 26 26 26 grey10 - 28 28 28 gray11 - 28 28 28 grey11 - 31 31 31 gray12 - 31 31 31 grey12 - 33 33 33 gray13 - 33 33 33 grey13 - 36 36 36 gray14 - 36 36 36 grey14 - 38 38 38 gray15 - 38 38 38 grey15 - 41 41 41 gray16 - 41 41 41 grey16 - 43 43 43 gray17 - 43 43 43 grey17 - 46 46 46 gray18 - 46 46 46 grey18 - 48 48 48 gray19 - 48 48 48 grey19 - 51 51 51 gray20 - 51 51 51 grey20 - 54 54 54 gray21 - 54 54 54 grey21 - 56 56 56 gray22 - 56 56 56 grey22 - 59 59 59 gray23 - 59 59 59 grey23 - 61 61 61 gray24 - 61 61 61 grey24 - 64 64 64 gray25 - 64 64 64 grey25 - 66 66 66 gray26 - 66 66 66 grey26 - 69 69 69 gray27 - 69 69 69 grey27 - 71 71 71 gray28 - 71 71 71 grey28 - 74 74 74 gray29 - 74 74 74 grey29 - 77 77 77 gray30 - 77 77 77 grey30 - 79 79 79 gray31 - 79 79 79 grey31 - 82 82 82 gray32 - 82 82 82 grey32 - 84 84 84 gray33 - 84 84 84 grey33 - 87 87 87 gray34 - 87 87 87 grey34 - 89 89 89 gray35 - 89 89 89 grey35 - 92 92 92 gray36 - 92 92 92 grey36 - 94 94 94 gray37 - 94 94 94 grey37 - 97 97 97 gray38 - 97 97 97 grey38 - 99 99 99 gray39 - 99 99 99 grey39 -102 102 102 gray40 -102 102 102 grey40 -105 105 105 gray41 -105 105 105 grey41 -107 107 107 gray42 -107 107 107 grey42 -110 110 110 gray43 -110 110 110 grey43 -112 112 112 gray44 -112 112 112 grey44 -115 115 115 gray45 -115 115 115 grey45 -117 117 117 gray46 -117 117 117 grey46 -120 120 120 gray47 -120 120 120 grey47 -122 122 122 gray48 -122 122 122 grey48 -125 125 125 gray49 -125 125 125 grey49 -127 127 127 gray50 -127 127 127 grey50 -130 130 130 gray51 -130 130 130 grey51 -133 133 133 gray52 -133 133 133 grey52 -135 135 135 gray53 -135 135 135 grey53 -138 138 138 gray54 -138 138 138 grey54 -140 140 140 gray55 -140 140 140 grey55 -143 143 143 gray56 -143 143 143 grey56 -145 145 145 gray57 -145 145 145 grey57 -148 148 148 gray58 -148 148 148 grey58 -150 150 150 gray59 -150 150 150 grey59 -153 153 153 gray60 -153 153 153 grey60 -156 156 156 gray61 -156 156 156 grey61 -158 158 158 gray62 -158 158 158 grey62 -161 161 161 gray63 -161 161 161 grey63 -163 163 163 gray64 -163 163 163 grey64 -166 166 166 gray65 -166 166 166 grey65 -168 168 168 gray66 -168 168 168 grey66 -171 171 171 gray67 -171 171 171 grey67 -173 173 173 gray68 -173 173 173 grey68 -176 176 176 gray69 -176 176 176 grey69 -179 179 179 gray70 -179 179 179 grey70 -181 181 181 gray71 -181 181 181 grey71 -184 184 184 gray72 -184 184 184 grey72 -186 186 186 gray73 -186 186 186 grey73 -189 189 189 gray74 -189 189 189 grey74 -191 191 191 gray75 -191 191 191 grey75 -194 194 194 gray76 -194 194 194 grey76 -196 196 196 gray77 -196 196 196 grey77 -199 199 199 gray78 -199 199 199 grey78 -201 201 201 gray79 -201 201 201 grey79 -204 204 204 gray80 -204 204 204 grey80 -207 207 207 gray81 -207 207 207 grey81 -209 209 209 gray82 -209 209 209 grey82 -212 212 212 gray83 -212 212 212 grey83 -214 214 214 gray84 -214 214 214 grey84 -217 217 217 gray85 -217 217 217 grey85 -219 219 219 gray86 -219 219 219 grey86 -222 222 222 gray87 -222 222 222 grey87 -224 224 224 gray88 -224 224 224 grey88 -227 227 227 gray89 -227 227 227 grey89 -229 229 229 gray90 -229 229 229 grey90 -232 232 232 gray91 -232 232 232 grey91 -235 235 235 gray92 -235 235 235 grey92 -237 237 237 gray93 -237 237 237 grey93 -240 240 240 gray94 -240 240 240 grey94 -242 242 242 gray95 -242 242 242 grey95 -245 245 245 gray96 -245 245 245 grey96 -247 247 247 gray97 -247 247 247 grey97 -250 250 250 gray98 -250 250 250 grey98 -252 252 252 gray99 -252 252 252 grey99 -255 255 255 gray100 -255 255 255 grey100 -169 169 169 dark grey -169 169 169 DarkGrey -169 169 169 dark gray -169 169 169 DarkGray -0 0 139 dark blue -0 0 139 DarkBlue -0 139 139 dark cyan -0 139 139 DarkCyan -139 0 139 dark magenta -139 0 139 DarkMagenta -139 0 0 dark red -139 0 0 DarkRed -144 238 144 light green -144 238 144 LightGreen diff --git a/runtime/scripts.vim b/runtime/scripts.vim index 0ff8e49088..dd47f65ba0 100644 --- a/runtime/scripts.vim +++ b/runtime/scripts.vim @@ -198,6 +198,10 @@ if s:line1 =~# "^#!" elseif s:name =~# 'fish\>' set ft=fish + " Gforth + elseif s:name =~# 'gforth\>' + set ft=forth + endif unlet s:name @@ -380,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) @@ -402,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/spell/cleanadd.vim b/runtime/spell/cleanadd.vim new file mode 100644 index 0000000000..6dc0692186 --- /dev/null +++ b/runtime/spell/cleanadd.vim @@ -0,0 +1,32 @@ +" Vim script to clean the ll.xxxxx.add files of commented out entries +" Author: Antonio Colombo, Bram Moolenaar +" Last Update: 2008 Jun 3 + +" Time in seconds after last time an ll.xxxxx.add file was updated +" Default is one second. +" If you invoke this script often set it to something bigger, e.g. 60 * 60 +" (one hour) +if !exists("g:spell_clean_limit") + let g:spell_clean_limit = 1 +endif + +" Loop over all the runtime/spell/*.add files. +" Delete all comment lines, except the ones starting with ##. +for s:fname in split(globpath(&rtp, "spell/*.add"), "\n") + if filewritable(s:fname) && localtime() - getftime(s:fname) > g:spell_clean_limit + if exists('*fnameescape') + let s:f = fnameescape(s:fname) + else + let s:f = escape(s:fname, ' \|<') + endif + silent exe "tab split " . s:f + echo "Processing" s:f + silent! g/^#[^#]/d + silent update + close + unlet s:f + endif +endfor +unlet s:fname + +echo "Done" 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/c.vim b/runtime/syntax/c.vim index 20f8632006..2dc21f0b6a 100644 --- a/runtime/syntax/c.vim +++ b/runtime/syntax/c.vim @@ -1,7 +1,7 @@ " Vim syntax file " Language: C " Maintainer: Bram Moolenaar <Bram@vim.org> -" Last Change: 2021 May 24 +" Last Change: 2022 Mar 17 " Quit when a (custom) syntax file was already loaded if exists("b:current_syntax") @@ -196,7 +196,6 @@ syn match cNumber display contained "0x\x\+\(u\=l\{0,2}\|ll\=u\)\>" " Flag the first zero of an octal number as something special syn match cOctal display contained "0\o\+\(u\=l\{0,2}\|ll\=u\)\>" contains=cOctalZero syn match cOctalZero display contained "\<0" -syn match cFloat display contained "\d\+f" "floating point number, with dot, optional exponent syn match cFloat display contained "\d\+\.\d*\(e[-+]\=\d\+\)\=[fl]\=" "floating point number, starting with a dot, optional exponent @@ -246,8 +245,14 @@ syn match cWrongComTail display "\*/" syn keyword cOperator sizeof if exists("c_gnu") + syn keyword cType __label__ __complex__ syn keyword cStatement __asm__ - syn keyword cOperator typeof __real__ __imag__ + syn keyword cOperator __alignof__ + syn keyword cOperator typeof __typeof__ + syn keyword cOperator __real__ __imag__ + syn keyword cStorageClass __attribute__ __const__ __extension__ + syn keyword cStorageClass inline __inline__ + syn keyword cStorageClass __restrict__ __volatile__ __noreturn__ endif syn keyword cType int long short char void syn keyword cType signed unsigned float double @@ -271,16 +276,10 @@ if !exists("c_no_c99") " ISO C99 syn keyword cType intptr_t uintptr_t syn keyword cType intmax_t uintmax_t endif -if exists("c_gnu") - syn keyword cType __label__ __complex__ __volatile__ -endif syn keyword cTypedef typedef syn keyword cStructure struct union enum syn keyword cStorageClass static register auto volatile extern const -if exists("c_gnu") - syn keyword cStorageClass inline __attribute__ -endif if !exists("c_no_c99") && !s:in_cpp_family syn keyword cStorageClass inline restrict endif @@ -293,6 +292,7 @@ if !exists("c_no_c11") syn keyword cOperator _Static_assert static_assert syn keyword cStorageClass _Thread_local thread_local syn keyword cType char16_t char32_t + syn keyword cType max_align_t " C11 atomics (take down the shield wall!) syn keyword cType atomic_bool atomic_char atomic_schar atomic_uchar syn keyword Ctype atomic_short atomic_ushort atomic_int atomic_uint diff --git a/runtime/syntax/checkhealth.vim b/runtime/syntax/checkhealth.vim new file mode 100644 index 0000000000..37f1822740 --- /dev/null +++ b/runtime/syntax/checkhealth.vim @@ -0,0 +1,30 @@ +" Vim syntax file +" Language: Neovim checkhealth buffer +" Last Change: 2021 Dec 15 + +if exists("b:current_syntax") + finish +endif + +runtime! syntax/markdown.vim +unlet! b:current_syntax + +syn case match + +" We do not care about markdown syntax errors +if hlexists('markdownError') + syn clear markdownError +endif + +syn keyword healthError ERROR[:] containedin=markdownCodeBlock,mkdListItemLine +syn keyword healthWarning WARNING[:] containedin=markdownCodeBlock,mkdListItemLine +syn keyword healthSuccess OK[:] containedin=markdownCodeBlock,mkdListItemLine +syn match healthHelp "|.\{-}|" containedin=markdownCodeBlock,mkdListItemLine contains=healthBar +syn match healthBar "|" contained conceal + +hi def link healthError Error +hi def link healthWarning WarningMsg +hi def healthSuccess guibg=#5fff00 guifg=#080808 ctermbg=82 ctermfg=232 +hi def link healthHelp Identifier + +let b:current_syntax = "checkhealth" diff --git a/runtime/syntax/clojure.vim b/runtime/syntax/clojure.vim index 9782dc41ad..0d63728250 100644 --- a/runtime/syntax/clojure.vim +++ b/runtime/syntax/clojure.vim @@ -7,7 +7,7 @@ " Contributors: Joel Holdbrooks <cjholdbrooks@gmail.com> (Regexp support, bug fixes) " URL: https://github.com/clojure-vim/clojure.vim " License: Vim (see :h license) -" Last Change: 2021-10-26 +" Last Change: 2022-03-24 if exists("b:current_syntax") finish @@ -21,20 +21,20 @@ if has("folding") && exists("g:clojure_fold") && g:clojure_fold > 0 endif " -*- KEYWORDS -*- -" Generated from https://github.com/clojure-vim/clojure.vim/blob/62b215f079ce0f3834fd295c7a7f6bd8cc54bcc3/clj/src/vim_clojure_static/generate.clj -" Clojure version 1.10.3 +" Generated from https://github.com/clojure-vim/clojure.vim/blob/fd280e33e84c88e97860930557dba3ff80b1a82d/clj/src/vim_clojure_static/generate.clj +" Clojure version 1.11.0 let s:clojure_syntax_keywords = { - \ 'clojureBoolean': ["false","true"] - \ , 'clojureCond': ["case","clojure.core/case","clojure.core/cond","clojure.core/cond->","clojure.core/cond->>","clojure.core/condp","clojure.core/if-let","clojure.core/if-not","clojure.core/if-some","clojure.core/when","clojure.core/when-first","clojure.core/when-let","clojure.core/when-not","clojure.core/when-some","cond","cond->","cond->>","condp","if-let","if-not","if-some","when","when-first","when-let","when-not","when-some"] - \ , 'clojureConstant': ["nil"] - \ , 'clojureDefine': ["clojure.core/definline","clojure.core/definterface","clojure.core/defmacro","clojure.core/defmethod","clojure.core/defmulti","clojure.core/defn","clojure.core/defn-","clojure.core/defonce","clojure.core/defprotocol","clojure.core/defrecord","clojure.core/defstruct","clojure.core/deftype","definline","definterface","defmacro","defmethod","defmulti","defn","defn-","defonce","defprotocol","defrecord","defstruct","deftype"] - \ , 'clojureException': ["catch","finally","throw","try"] - \ , 'clojureFunc': ["*","*'","+","+'","-","-'","->ArrayChunk","->Eduction","->Vec","->VecNode","->VecSeq","-cache-protocol-fn","-reset-methods","/","<","<=","=","==",">",">=","PrintWriter-on","StackTraceElement->vec","Throwable->map","accessor","aclone","add-classpath","add-tap","add-watch","agent","agent-error","agent-errors","aget","alength","alias","all-ns","alter","alter-meta!","alter-var-root","ancestors","any?","apply","array-map","aset","aset-boolean","aset-byte","aset-char","aset-double","aset-float","aset-int","aset-long","aset-short","assoc","assoc!","assoc-in","associative?","atom","await","await-for","await1","bases","bean","bigdec","bigint","biginteger","bit-and","bit-and-not","bit-clear","bit-flip","bit-not","bit-or","bit-set","bit-shift-left","bit-shift-right","bit-test","bit-xor","boolean","boolean-array","boolean?","booleans","bound-fn*","bound?","bounded-count","butlast","byte","byte-array","bytes","bytes?","cast","cat","char","char-array","char?","chars","chunk","chunk-append","chunk-buffer","chunk-cons","chunk-first","chunk-next","chunk-rest","chunked-seq?","class","class?","clear-agent-errors","clojure-version","clojure.core/*","clojure.core/*'","clojure.core/+","clojure.core/+'","clojure.core/-","clojure.core/-'","clojure.core/->ArrayChunk","clojure.core/->Eduction","clojure.core/->Vec","clojure.core/->VecNode","clojure.core/->VecSeq","clojure.core/-cache-protocol-fn","clojure.core/-reset-methods","clojure.core//","clojure.core/<","clojure.core/<=","clojure.core/=","clojure.core/==","clojure.core/>","clojure.core/>=","clojure.core/PrintWriter-on","clojure.core/StackTraceElement->vec","clojure.core/Throwable->map","clojure.core/accessor","clojure.core/aclone","clojure.core/add-classpath","clojure.core/add-tap","clojure.core/add-watch","clojure.core/agent","clojure.core/agent-error","clojure.core/agent-errors","clojure.core/aget","clojure.core/alength","clojure.core/alias","clojure.core/all-ns","clojure.core/alter","clojure.core/alter-meta!","clojure.core/alter-var-root","clojure.core/ancestors","clojure.core/any?","clojure.core/apply","clojure.core/array-map","clojure.core/aset","clojure.core/aset-boolean","clojure.core/aset-byte","clojure.core/aset-char","clojure.core/aset-double","clojure.core/aset-float","clojure.core/aset-int","clojure.core/aset-long","clojure.core/aset-short","clojure.core/assoc","clojure.core/assoc!","clojure.core/assoc-in","clojure.core/associative?","clojure.core/atom","clojure.core/await","clojure.core/await-for","clojure.core/await1","clojure.core/bases","clojure.core/bean","clojure.core/bigdec","clojure.core/bigint","clojure.core/biginteger","clojure.core/bit-and","clojure.core/bit-and-not","clojure.core/bit-clear","clojure.core/bit-flip","clojure.core/bit-not","clojure.core/bit-or","clojure.core/bit-set","clojure.core/bit-shift-left","clojure.core/bit-shift-right","clojure.core/bit-test","clojure.core/bit-xor","clojure.core/boolean","clojure.core/boolean-array","clojure.core/boolean?","clojure.core/booleans","clojure.core/bound-fn*","clojure.core/bound?","clojure.core/bounded-count","clojure.core/butlast","clojure.core/byte","clojure.core/byte-array","clojure.core/bytes","clojure.core/bytes?","clojure.core/cast","clojure.core/cat","clojure.core/char","clojure.core/char-array","clojure.core/char?","clojure.core/chars","clojure.core/chunk","clojure.core/chunk-append","clojure.core/chunk-buffer","clojure.core/chunk-cons","clojure.core/chunk-first","clojure.core/chunk-next","clojure.core/chunk-rest","clojure.core/chunked-seq?","clojure.core/class","clojure.core/class?","clojure.core/clear-agent-errors","clojure.core/clojure-version","clojure.core/coll?","clojure.core/commute","clojure.core/comp","clojure.core/comparator","clojure.core/compare","clojure.core/compare-and-set!","clojure.core/compile","clojure.core/complement","clojure.core/completing","clojure.core/concat","clojure.core/conj","clojure.core/conj!","clojure.core/cons","clojure.core/constantly","clojure.core/construct-proxy","clojure.core/contains?","clojure.core/count","clojure.core/counted?","clojure.core/create-ns","clojure.core/create-struct","clojure.core/cycle","clojure.core/dec","clojure.core/dec'","clojure.core/decimal?","clojure.core/dedupe","clojure.core/delay?","clojure.core/deliver","clojure.core/denominator","clojure.core/deref","clojure.core/derive","clojure.core/descendants","clojure.core/destructure","clojure.core/disj","clojure.core/disj!","clojure.core/dissoc","clojure.core/dissoc!","clojure.core/distinct","clojure.core/distinct?","clojure.core/doall","clojure.core/dorun","clojure.core/double","clojure.core/double-array","clojure.core/double?","clojure.core/doubles","clojure.core/drop","clojure.core/drop-last","clojure.core/drop-while","clojure.core/eduction","clojure.core/empty","clojure.core/empty?","clojure.core/ensure","clojure.core/ensure-reduced","clojure.core/enumeration-seq","clojure.core/error-handler","clojure.core/error-mode","clojure.core/eval","clojure.core/even?","clojure.core/every-pred","clojure.core/every?","clojure.core/ex-cause","clojure.core/ex-data","clojure.core/ex-info","clojure.core/ex-message","clojure.core/extend","clojure.core/extenders","clojure.core/extends?","clojure.core/false?","clojure.core/ffirst","clojure.core/file-seq","clojure.core/filter","clojure.core/filterv","clojure.core/find","clojure.core/find-keyword","clojure.core/find-ns","clojure.core/find-protocol-impl","clojure.core/find-protocol-method","clojure.core/find-var","clojure.core/first","clojure.core/flatten","clojure.core/float","clojure.core/float-array","clojure.core/float?","clojure.core/floats","clojure.core/flush","clojure.core/fn?","clojure.core/fnext","clojure.core/fnil","clojure.core/force","clojure.core/format","clojure.core/frequencies","clojure.core/future-call","clojure.core/future-cancel","clojure.core/future-cancelled?","clojure.core/future-done?","clojure.core/future?","clojure.core/gensym","clojure.core/get","clojure.core/get-in","clojure.core/get-method","clojure.core/get-proxy-class","clojure.core/get-thread-bindings","clojure.core/get-validator","clojure.core/group-by","clojure.core/halt-when","clojure.core/hash","clojure.core/hash-combine","clojure.core/hash-map","clojure.core/hash-ordered-coll","clojure.core/hash-set","clojure.core/hash-unordered-coll","clojure.core/ident?","clojure.core/identical?","clojure.core/identity","clojure.core/ifn?","clojure.core/in-ns","clojure.core/inc","clojure.core/inc'","clojure.core/indexed?","clojure.core/init-proxy","clojure.core/inst-ms","clojure.core/inst-ms*","clojure.core/inst?","clojure.core/instance?","clojure.core/int","clojure.core/int-array","clojure.core/int?","clojure.core/integer?","clojure.core/interleave","clojure.core/intern","clojure.core/interpose","clojure.core/into","clojure.core/into-array","clojure.core/ints","clojure.core/isa?","clojure.core/iterate","clojure.core/iterator-seq","clojure.core/juxt","clojure.core/keep","clojure.core/keep-indexed","clojure.core/key","clojure.core/keys","clojure.core/keyword","clojure.core/keyword?","clojure.core/last","clojure.core/line-seq","clojure.core/list","clojure.core/list*","clojure.core/list?","clojure.core/load","clojure.core/load-file","clojure.core/load-reader","clojure.core/load-string","clojure.core/loaded-libs","clojure.core/long","clojure.core/long-array","clojure.core/longs","clojure.core/macroexpand","clojure.core/macroexpand-1","clojure.core/make-array","clojure.core/make-hierarchy","clojure.core/map","clojure.core/map-entry?","clojure.core/map-indexed","clojure.core/map?","clojure.core/mapcat","clojure.core/mapv","clojure.core/max","clojure.core/max-key","clojure.core/memoize","clojure.core/merge","clojure.core/merge-with","clojure.core/meta","clojure.core/method-sig","clojure.core/methods","clojure.core/min","clojure.core/min-key","clojure.core/mix-collection-hash","clojure.core/mod","clojure.core/munge","clojure.core/name","clojure.core/namespace","clojure.core/namespace-munge","clojure.core/nat-int?","clojure.core/neg-int?","clojure.core/neg?","clojure.core/newline","clojure.core/next","clojure.core/nfirst","clojure.core/nil?","clojure.core/nnext","clojure.core/not","clojure.core/not-any?","clojure.core/not-empty","clojure.core/not-every?","clojure.core/not=","clojure.core/ns-aliases","clojure.core/ns-imports","clojure.core/ns-interns","clojure.core/ns-map","clojure.core/ns-name","clojure.core/ns-publics","clojure.core/ns-refers","clojure.core/ns-resolve","clojure.core/ns-unalias","clojure.core/ns-unmap","clojure.core/nth","clojure.core/nthnext","clojure.core/nthrest","clojure.core/num","clojure.core/number?","clojure.core/numerator","clojure.core/object-array","clojure.core/odd?","clojure.core/parents","clojure.core/partial","clojure.core/partition","clojure.core/partition-all","clojure.core/partition-by","clojure.core/pcalls","clojure.core/peek","clojure.core/persistent!","clojure.core/pmap","clojure.core/pop","clojure.core/pop!","clojure.core/pop-thread-bindings","clojure.core/pos-int?","clojure.core/pos?","clojure.core/pr","clojure.core/pr-str","clojure.core/prefer-method","clojure.core/prefers","clojure.core/print","clojure.core/print-ctor","clojure.core/print-dup","clojure.core/print-method","clojure.core/print-simple","clojure.core/print-str","clojure.core/printf","clojure.core/println","clojure.core/println-str","clojure.core/prn","clojure.core/prn-str","clojure.core/promise","clojure.core/proxy-call-with-super","clojure.core/proxy-mappings","clojure.core/proxy-name","clojure.core/push-thread-bindings","clojure.core/qualified-ident?","clojure.core/qualified-keyword?","clojure.core/qualified-symbol?","clojure.core/quot","clojure.core/rand","clojure.core/rand-int","clojure.core/rand-nth","clojure.core/random-sample","clojure.core/range","clojure.core/ratio?","clojure.core/rational?","clojure.core/rationalize","clojure.core/re-find","clojure.core/re-groups","clojure.core/re-matcher","clojure.core/re-matches","clojure.core/re-pattern","clojure.core/re-seq","clojure.core/read","clojure.core/read+string","clojure.core/read-line","clojure.core/read-string","clojure.core/reader-conditional","clojure.core/reader-conditional?","clojure.core/realized?","clojure.core/record?","clojure.core/reduce","clojure.core/reduce-kv","clojure.core/reduced","clojure.core/reduced?","clojure.core/reductions","clojure.core/ref","clojure.core/ref-history-count","clojure.core/ref-max-history","clojure.core/ref-min-history","clojure.core/ref-set","clojure.core/refer","clojure.core/release-pending-sends","clojure.core/rem","clojure.core/remove","clojure.core/remove-all-methods","clojure.core/remove-method","clojure.core/remove-ns","clojure.core/remove-tap","clojure.core/remove-watch","clojure.core/repeat","clojure.core/repeatedly","clojure.core/replace","clojure.core/replicate","clojure.core/require","clojure.core/requiring-resolve","clojure.core/reset!","clojure.core/reset-meta!","clojure.core/reset-vals!","clojure.core/resolve","clojure.core/rest","clojure.core/restart-agent","clojure.core/resultset-seq","clojure.core/reverse","clojure.core/reversible?","clojure.core/rseq","clojure.core/rsubseq","clojure.core/run!","clojure.core/satisfies?","clojure.core/second","clojure.core/select-keys","clojure.core/send","clojure.core/send-off","clojure.core/send-via","clojure.core/seq","clojure.core/seq?","clojure.core/seqable?","clojure.core/seque","clojure.core/sequence","clojure.core/sequential?","clojure.core/set","clojure.core/set-agent-send-executor!","clojure.core/set-agent-send-off-executor!","clojure.core/set-error-handler!","clojure.core/set-error-mode!","clojure.core/set-validator!","clojure.core/set?","clojure.core/short","clojure.core/short-array","clojure.core/shorts","clojure.core/shuffle","clojure.core/shutdown-agents","clojure.core/simple-ident?","clojure.core/simple-keyword?","clojure.core/simple-symbol?","clojure.core/slurp","clojure.core/some","clojure.core/some-fn","clojure.core/some?","clojure.core/sort","clojure.core/sort-by","clojure.core/sorted-map","clojure.core/sorted-map-by","clojure.core/sorted-set","clojure.core/sorted-set-by","clojure.core/sorted?","clojure.core/special-symbol?","clojure.core/spit","clojure.core/split-at","clojure.core/split-with","clojure.core/str","clojure.core/string?","clojure.core/struct","clojure.core/struct-map","clojure.core/subs","clojure.core/subseq","clojure.core/subvec","clojure.core/supers","clojure.core/swap!","clojure.core/swap-vals!","clojure.core/symbol","clojure.core/symbol?","clojure.core/tagged-literal","clojure.core/tagged-literal?","clojure.core/take","clojure.core/take-last","clojure.core/take-nth","clojure.core/take-while","clojure.core/tap>","clojure.core/test","clojure.core/the-ns","clojure.core/thread-bound?","clojure.core/to-array","clojure.core/to-array-2d","clojure.core/trampoline","clojure.core/transduce","clojure.core/transient","clojure.core/tree-seq","clojure.core/true?","clojure.core/type","clojure.core/unchecked-add","clojure.core/unchecked-add-int","clojure.core/unchecked-byte","clojure.core/unchecked-char","clojure.core/unchecked-dec","clojure.core/unchecked-dec-int","clojure.core/unchecked-divide-int","clojure.core/unchecked-double","clojure.core/unchecked-float","clojure.core/unchecked-inc","clojure.core/unchecked-inc-int","clojure.core/unchecked-int","clojure.core/unchecked-long","clojure.core/unchecked-multiply","clojure.core/unchecked-multiply-int","clojure.core/unchecked-negate","clojure.core/unchecked-negate-int","clojure.core/unchecked-remainder-int","clojure.core/unchecked-short","clojure.core/unchecked-subtract","clojure.core/unchecked-subtract-int","clojure.core/underive","clojure.core/unreduced","clojure.core/unsigned-bit-shift-right","clojure.core/update","clojure.core/update-in","clojure.core/update-proxy","clojure.core/uri?","clojure.core/use","clojure.core/uuid?","clojure.core/val","clojure.core/vals","clojure.core/var-get","clojure.core/var-set","clojure.core/var?","clojure.core/vary-meta","clojure.core/vec","clojure.core/vector","clojure.core/vector-of","clojure.core/vector?","clojure.core/volatile!","clojure.core/volatile?","clojure.core/vreset!","clojure.core/with-bindings*","clojure.core/with-meta","clojure.core/with-redefs-fn","clojure.core/xml-seq","clojure.core/zero?","clojure.core/zipmap","coll?","commute","comp","comparator","compare","compare-and-set!","compile","complement","completing","concat","conj","conj!","cons","constantly","construct-proxy","contains?","count","counted?","create-ns","create-struct","cycle","dec","dec'","decimal?","dedupe","delay?","deliver","denominator","deref","derive","descendants","destructure","disj","disj!","dissoc","dissoc!","distinct","distinct?","doall","dorun","double","double-array","double?","doubles","drop","drop-last","drop-while","eduction","empty","empty?","ensure","ensure-reduced","enumeration-seq","error-handler","error-mode","eval","even?","every-pred","every?","ex-cause","ex-data","ex-info","ex-message","extend","extenders","extends?","false?","ffirst","file-seq","filter","filterv","find","find-keyword","find-ns","find-protocol-impl","find-protocol-method","find-var","first","flatten","float","float-array","float?","floats","flush","fn?","fnext","fnil","force","format","frequencies","future-call","future-cancel","future-cancelled?","future-done?","future?","gensym","get","get-in","get-method","get-proxy-class","get-thread-bindings","get-validator","group-by","halt-when","hash","hash-combine","hash-map","hash-ordered-coll","hash-set","hash-unordered-coll","ident?","identical?","identity","ifn?","in-ns","inc","inc'","indexed?","init-proxy","inst-ms","inst-ms*","inst?","instance?","int","int-array","int?","integer?","interleave","intern","interpose","into","into-array","ints","isa?","iterate","iterator-seq","juxt","keep","keep-indexed","key","keys","keyword","keyword?","last","line-seq","list","list*","list?","load","load-file","load-reader","load-string","loaded-libs","long","long-array","longs","macroexpand","macroexpand-1","make-array","make-hierarchy","map","map-entry?","map-indexed","map?","mapcat","mapv","max","max-key","memoize","merge","merge-with","meta","method-sig","methods","min","min-key","mix-collection-hash","mod","munge","name","namespace","namespace-munge","nat-int?","neg-int?","neg?","newline","next","nfirst","nil?","nnext","not","not-any?","not-empty","not-every?","not=","ns-aliases","ns-imports","ns-interns","ns-map","ns-name","ns-publics","ns-refers","ns-resolve","ns-unalias","ns-unmap","nth","nthnext","nthrest","num","number?","numerator","object-array","odd?","parents","partial","partition","partition-all","partition-by","pcalls","peek","persistent!","pmap","pop","pop!","pop-thread-bindings","pos-int?","pos?","pr","pr-str","prefer-method","prefers","print","print-ctor","print-dup","print-method","print-simple","print-str","printf","println","println-str","prn","prn-str","promise","proxy-call-with-super","proxy-mappings","proxy-name","push-thread-bindings","qualified-ident?","qualified-keyword?","qualified-symbol?","quot","rand","rand-int","rand-nth","random-sample","range","ratio?","rational?","rationalize","re-find","re-groups","re-matcher","re-matches","re-pattern","re-seq","read","read+string","read-line","read-string","reader-conditional","reader-conditional?","realized?","record?","reduce","reduce-kv","reduced","reduced?","reductions","ref","ref-history-count","ref-max-history","ref-min-history","ref-set","refer","release-pending-sends","rem","remove","remove-all-methods","remove-method","remove-ns","remove-tap","remove-watch","repeat","repeatedly","replace","replicate","require","requiring-resolve","reset!","reset-meta!","reset-vals!","resolve","rest","restart-agent","resultset-seq","reverse","reversible?","rseq","rsubseq","run!","satisfies?","second","select-keys","send","send-off","send-via","seq","seq?","seqable?","seque","sequence","sequential?","set","set-agent-send-executor!","set-agent-send-off-executor!","set-error-handler!","set-error-mode!","set-validator!","set?","short","short-array","shorts","shuffle","shutdown-agents","simple-ident?","simple-keyword?","simple-symbol?","slurp","some","some-fn","some?","sort","sort-by","sorted-map","sorted-map-by","sorted-set","sorted-set-by","sorted?","special-symbol?","spit","split-at","split-with","str","string?","struct","struct-map","subs","subseq","subvec","supers","swap!","swap-vals!","symbol","symbol?","tagged-literal","tagged-literal?","take","take-last","take-nth","take-while","tap>","test","the-ns","thread-bound?","to-array","to-array-2d","trampoline","transduce","transient","tree-seq","true?","type","unchecked-add","unchecked-add-int","unchecked-byte","unchecked-char","unchecked-dec","unchecked-dec-int","unchecked-divide-int","unchecked-double","unchecked-float","unchecked-inc","unchecked-inc-int","unchecked-int","unchecked-long","unchecked-multiply","unchecked-multiply-int","unchecked-negate","unchecked-negate-int","unchecked-remainder-int","unchecked-short","unchecked-subtract","unchecked-subtract-int","underive","unreduced","unsigned-bit-shift-right","update","update-in","update-proxy","uri?","use","uuid?","val","vals","var-get","var-set","var?","vary-meta","vec","vector","vector-of","vector?","volatile!","volatile?","vreset!","with-bindings*","with-meta","with-redefs-fn","xml-seq","zero?","zipmap"] - \ , 'clojureMacro': ["->","->>","..","amap","and","areduce","as->","assert","binding","bound-fn","clojure.core/->","clojure.core/->>","clojure.core/..","clojure.core/amap","clojure.core/and","clojure.core/areduce","clojure.core/as->","clojure.core/assert","clojure.core/binding","clojure.core/bound-fn","clojure.core/comment","clojure.core/declare","clojure.core/delay","clojure.core/dosync","clojure.core/doto","clojure.core/extend-protocol","clojure.core/extend-type","clojure.core/for","clojure.core/future","clojure.core/gen-class","clojure.core/gen-interface","clojure.core/import","clojure.core/io!","clojure.core/lazy-cat","clojure.core/lazy-seq","clojure.core/letfn","clojure.core/locking","clojure.core/memfn","clojure.core/ns","clojure.core/or","clojure.core/proxy","clojure.core/proxy-super","clojure.core/pvalues","clojure.core/refer-clojure","clojure.core/reify","clojure.core/some->","clojure.core/some->>","clojure.core/sync","clojure.core/time","clojure.core/vswap!","clojure.core/with-bindings","clojure.core/with-in-str","clojure.core/with-loading-context","clojure.core/with-local-vars","clojure.core/with-open","clojure.core/with-out-str","clojure.core/with-precision","clojure.core/with-redefs","comment","declare","delay","dosync","doto","extend-protocol","extend-type","for","future","gen-class","gen-interface","import","io!","lazy-cat","lazy-seq","letfn","locking","memfn","ns","or","proxy","proxy-super","pvalues","refer-clojure","reify","some->","some->>","sync","time","vswap!","with-bindings","with-in-str","with-loading-context","with-local-vars","with-open","with-out-str","with-precision","with-redefs"] - \ , 'clojureRepeat': ["clojure.core/doseq","clojure.core/dotimes","clojure.core/while","doseq","dotimes","while"] - \ , 'clojureSpecial': [".","clojure.core/fn","clojure.core/let","clojure.core/loop","def","do","fn","if","let","loop","monitor-enter","monitor-exit","new","quote","recur","set!","var"] - \ , 'clojureVariable': ["*1","*2","*3","*agent*","*allow-unresolved-vars*","*assert*","*clojure-version*","*command-line-args*","*compile-files*","*compile-path*","*compiler-options*","*data-readers*","*default-data-reader-fn*","*e","*err*","*file*","*flush-on-newline*","*fn-loader*","*in*","*math-context*","*ns*","*out*","*print-dup*","*print-length*","*print-level*","*print-meta*","*print-namespace-maps*","*print-readably*","*read-eval*","*reader-resolver*","*source-path*","*suppress-read*","*unchecked-math*","*use-context-classloader*","*verbose-defrecords*","*warn-on-reflection*","EMPTY-NODE","Inst","char-escape-string","char-name-string","clojure.core/*1","clojure.core/*2","clojure.core/*3","clojure.core/*agent*","clojure.core/*allow-unresolved-vars*","clojure.core/*assert*","clojure.core/*clojure-version*","clojure.core/*command-line-args*","clojure.core/*compile-files*","clojure.core/*compile-path*","clojure.core/*compiler-options*","clojure.core/*data-readers*","clojure.core/*default-data-reader-fn*","clojure.core/*e","clojure.core/*err*","clojure.core/*file*","clojure.core/*flush-on-newline*","clojure.core/*fn-loader*","clojure.core/*in*","clojure.core/*math-context*","clojure.core/*ns*","clojure.core/*out*","clojure.core/*print-dup*","clojure.core/*print-length*","clojure.core/*print-level*","clojure.core/*print-meta*","clojure.core/*print-namespace-maps*","clojure.core/*print-readably*","clojure.core/*read-eval*","clojure.core/*reader-resolver*","clojure.core/*source-path*","clojure.core/*suppress-read*","clojure.core/*unchecked-math*","clojure.core/*use-context-classloader*","clojure.core/*verbose-defrecords*","clojure.core/*warn-on-reflection*","clojure.core/EMPTY-NODE","clojure.core/Inst","clojure.core/char-escape-string","clojure.core/char-name-string","clojure.core/default-data-readers","clojure.core/primitives-classnames","clojure.core/unquote","clojure.core/unquote-splicing","default-data-readers","primitives-classnames","unquote","unquote-splicing"] - \ } + \ 'clojureBoolean': ["false","true"], + \ 'clojureCond': ["case","case*","clojure.core/case","clojure.core/cond","clojure.core/cond->","clojure.core/cond->>","clojure.core/condp","clojure.core/if-let","clojure.core/if-not","clojure.core/if-some","clojure.core/when","clojure.core/when-first","clojure.core/when-let","clojure.core/when-not","clojure.core/when-some","cond","cond->","cond->>","condp","if","if-let","if-not","if-some","when","when-first","when-let","when-not","when-some"], + \ 'clojureConstant': ["nil"], + \ 'clojureDefine': ["clojure.core/definline","clojure.core/definterface","clojure.core/defmacro","clojure.core/defmethod","clojure.core/defmulti","clojure.core/defn","clojure.core/defn-","clojure.core/defonce","clojure.core/defprotocol","clojure.core/defrecord","clojure.core/defstruct","clojure.core/deftype","def","definline","definterface","defmacro","defmethod","defmulti","defn","defn-","defonce","defprotocol","defrecord","defstruct","deftype","deftype*"], + \ 'clojureException': ["catch","finally","throw","try"], + \ 'clojureFunc': ["*","*'","+","+'","-","-'","->ArrayChunk","->Eduction","->Vec","->VecNode","->VecSeq","-cache-protocol-fn","-reset-methods","/","<","<=","=","==",">",">=","NaN?","PrintWriter-on","StackTraceElement->vec","Throwable->map","abs","accessor","aclone","add-classpath","add-tap","add-watch","agent","agent-error","agent-errors","aget","alength","alias","all-ns","alter","alter-meta!","alter-var-root","ancestors","any?","apply","array-map","aset","aset-boolean","aset-byte","aset-char","aset-double","aset-float","aset-int","aset-long","aset-short","assoc","assoc!","assoc-in","associative?","atom","await","await-for","await1","bases","bean","bigdec","bigint","biginteger","bit-and","bit-and-not","bit-clear","bit-flip","bit-not","bit-or","bit-set","bit-shift-left","bit-shift-right","bit-test","bit-xor","boolean","boolean-array","boolean?","booleans","bound-fn*","bound?","bounded-count","butlast","byte","byte-array","bytes","bytes?","cast","cat","char","char-array","char?","chars","chunk","chunk-append","chunk-buffer","chunk-cons","chunk-first","chunk-next","chunk-rest","chunked-seq?","class","class?","clear-agent-errors","clojure-version","clojure.core/*","clojure.core/*'","clojure.core/+","clojure.core/+'","clojure.core/-","clojure.core/-'","clojure.core/->ArrayChunk","clojure.core/->Eduction","clojure.core/->Vec","clojure.core/->VecNode","clojure.core/->VecSeq","clojure.core/-cache-protocol-fn","clojure.core/-reset-methods","clojure.core//","clojure.core/<","clojure.core/<=","clojure.core/=","clojure.core/==","clojure.core/>","clojure.core/>=","clojure.core/NaN?","clojure.core/PrintWriter-on","clojure.core/StackTraceElement->vec","clojure.core/Throwable->map","clojure.core/abs","clojure.core/accessor","clojure.core/aclone","clojure.core/add-classpath","clojure.core/add-tap","clojure.core/add-watch","clojure.core/agent","clojure.core/agent-error","clojure.core/agent-errors","clojure.core/aget","clojure.core/alength","clojure.core/alias","clojure.core/all-ns","clojure.core/alter","clojure.core/alter-meta!","clojure.core/alter-var-root","clojure.core/ancestors","clojure.core/any?","clojure.core/apply","clojure.core/array-map","clojure.core/aset","clojure.core/aset-boolean","clojure.core/aset-byte","clojure.core/aset-char","clojure.core/aset-double","clojure.core/aset-float","clojure.core/aset-int","clojure.core/aset-long","clojure.core/aset-short","clojure.core/assoc","clojure.core/assoc!","clojure.core/assoc-in","clojure.core/associative?","clojure.core/atom","clojure.core/await","clojure.core/await-for","clojure.core/await1","clojure.core/bases","clojure.core/bean","clojure.core/bigdec","clojure.core/bigint","clojure.core/biginteger","clojure.core/bit-and","clojure.core/bit-and-not","clojure.core/bit-clear","clojure.core/bit-flip","clojure.core/bit-not","clojure.core/bit-or","clojure.core/bit-set","clojure.core/bit-shift-left","clojure.core/bit-shift-right","clojure.core/bit-test","clojure.core/bit-xor","clojure.core/boolean","clojure.core/boolean-array","clojure.core/boolean?","clojure.core/booleans","clojure.core/bound-fn*","clojure.core/bound?","clojure.core/bounded-count","clojure.core/butlast","clojure.core/byte","clojure.core/byte-array","clojure.core/bytes","clojure.core/bytes?","clojure.core/cast","clojure.core/cat","clojure.core/char","clojure.core/char-array","clojure.core/char?","clojure.core/chars","clojure.core/chunk","clojure.core/chunk-append","clojure.core/chunk-buffer","clojure.core/chunk-cons","clojure.core/chunk-first","clojure.core/chunk-next","clojure.core/chunk-rest","clojure.core/chunked-seq?","clojure.core/class","clojure.core/class?","clojure.core/clear-agent-errors","clojure.core/clojure-version","clojure.core/coll?","clojure.core/commute","clojure.core/comp","clojure.core/comparator","clojure.core/compare","clojure.core/compare-and-set!","clojure.core/compile","clojure.core/complement","clojure.core/completing","clojure.core/concat","clojure.core/conj","clojure.core/conj!","clojure.core/cons","clojure.core/constantly","clojure.core/construct-proxy","clojure.core/contains?","clojure.core/count","clojure.core/counted?","clojure.core/create-ns","clojure.core/create-struct","clojure.core/cycle","clojure.core/dec","clojure.core/dec'","clojure.core/decimal?","clojure.core/dedupe","clojure.core/delay?","clojure.core/deliver","clojure.core/denominator","clojure.core/deref","clojure.core/derive","clojure.core/descendants","clojure.core/destructure","clojure.core/disj","clojure.core/disj!","clojure.core/dissoc","clojure.core/dissoc!","clojure.core/distinct","clojure.core/distinct?","clojure.core/doall","clojure.core/dorun","clojure.core/double","clojure.core/double-array","clojure.core/double?","clojure.core/doubles","clojure.core/drop","clojure.core/drop-last","clojure.core/drop-while","clojure.core/eduction","clojure.core/empty","clojure.core/empty?","clojure.core/ensure","clojure.core/ensure-reduced","clojure.core/enumeration-seq","clojure.core/error-handler","clojure.core/error-mode","clojure.core/eval","clojure.core/even?","clojure.core/every-pred","clojure.core/every?","clojure.core/ex-cause","clojure.core/ex-data","clojure.core/ex-info","clojure.core/ex-message","clojure.core/extend","clojure.core/extenders","clojure.core/extends?","clojure.core/false?","clojure.core/ffirst","clojure.core/file-seq","clojure.core/filter","clojure.core/filterv","clojure.core/find","clojure.core/find-keyword","clojure.core/find-ns","clojure.core/find-protocol-impl","clojure.core/find-protocol-method","clojure.core/find-var","clojure.core/first","clojure.core/flatten","clojure.core/float","clojure.core/float-array","clojure.core/float?","clojure.core/floats","clojure.core/flush","clojure.core/fn?","clojure.core/fnext","clojure.core/fnil","clojure.core/force","clojure.core/format","clojure.core/frequencies","clojure.core/future-call","clojure.core/future-cancel","clojure.core/future-cancelled?","clojure.core/future-done?","clojure.core/future?","clojure.core/gensym","clojure.core/get","clojure.core/get-in","clojure.core/get-method","clojure.core/get-proxy-class","clojure.core/get-thread-bindings","clojure.core/get-validator","clojure.core/group-by","clojure.core/halt-when","clojure.core/hash","clojure.core/hash-combine","clojure.core/hash-map","clojure.core/hash-ordered-coll","clojure.core/hash-set","clojure.core/hash-unordered-coll","clojure.core/ident?","clojure.core/identical?","clojure.core/identity","clojure.core/ifn?","clojure.core/in-ns","clojure.core/inc","clojure.core/inc'","clojure.core/indexed?","clojure.core/infinite?","clojure.core/init-proxy","clojure.core/inst-ms","clojure.core/inst-ms*","clojure.core/inst?","clojure.core/instance?","clojure.core/int","clojure.core/int-array","clojure.core/int?","clojure.core/integer?","clojure.core/interleave","clojure.core/intern","clojure.core/interpose","clojure.core/into","clojure.core/into-array","clojure.core/ints","clojure.core/isa?","clojure.core/iterate","clojure.core/iteration","clojure.core/iterator-seq","clojure.core/juxt","clojure.core/keep","clojure.core/keep-indexed","clojure.core/key","clojure.core/keys","clojure.core/keyword","clojure.core/keyword?","clojure.core/last","clojure.core/line-seq","clojure.core/list","clojure.core/list*","clojure.core/list?","clojure.core/load","clojure.core/load-file","clojure.core/load-reader","clojure.core/load-string","clojure.core/loaded-libs","clojure.core/long","clojure.core/long-array","clojure.core/longs","clojure.core/macroexpand","clojure.core/macroexpand-1","clojure.core/make-array","clojure.core/make-hierarchy","clojure.core/map","clojure.core/map-entry?","clojure.core/map-indexed","clojure.core/map?","clojure.core/mapcat","clojure.core/mapv","clojure.core/max","clojure.core/max-key","clojure.core/memoize","clojure.core/merge","clojure.core/merge-with","clojure.core/meta","clojure.core/method-sig","clojure.core/methods","clojure.core/min","clojure.core/min-key","clojure.core/mix-collection-hash","clojure.core/mod","clojure.core/munge","clojure.core/name","clojure.core/namespace","clojure.core/namespace-munge","clojure.core/nat-int?","clojure.core/neg-int?","clojure.core/neg?","clojure.core/newline","clojure.core/next","clojure.core/nfirst","clojure.core/nil?","clojure.core/nnext","clojure.core/not","clojure.core/not-any?","clojure.core/not-empty","clojure.core/not-every?","clojure.core/not=","clojure.core/ns-aliases","clojure.core/ns-imports","clojure.core/ns-interns","clojure.core/ns-map","clojure.core/ns-name","clojure.core/ns-publics","clojure.core/ns-refers","clojure.core/ns-resolve","clojure.core/ns-unalias","clojure.core/ns-unmap","clojure.core/nth","clojure.core/nthnext","clojure.core/nthrest","clojure.core/num","clojure.core/number?","clojure.core/numerator","clojure.core/object-array","clojure.core/odd?","clojure.core/parents","clojure.core/parse-boolean","clojure.core/parse-double","clojure.core/parse-long","clojure.core/parse-uuid","clojure.core/partial","clojure.core/partition","clojure.core/partition-all","clojure.core/partition-by","clojure.core/pcalls","clojure.core/peek","clojure.core/persistent!","clojure.core/pmap","clojure.core/pop","clojure.core/pop!","clojure.core/pop-thread-bindings","clojure.core/pos-int?","clojure.core/pos?","clojure.core/pr","clojure.core/pr-str","clojure.core/prefer-method","clojure.core/prefers","clojure.core/print","clojure.core/print-ctor","clojure.core/print-dup","clojure.core/print-method","clojure.core/print-simple","clojure.core/print-str","clojure.core/printf","clojure.core/println","clojure.core/println-str","clojure.core/prn","clojure.core/prn-str","clojure.core/promise","clojure.core/proxy-call-with-super","clojure.core/proxy-mappings","clojure.core/proxy-name","clojure.core/push-thread-bindings","clojure.core/qualified-ident?","clojure.core/qualified-keyword?","clojure.core/qualified-symbol?","clojure.core/quot","clojure.core/rand","clojure.core/rand-int","clojure.core/rand-nth","clojure.core/random-sample","clojure.core/random-uuid","clojure.core/range","clojure.core/ratio?","clojure.core/rational?","clojure.core/rationalize","clojure.core/re-find","clojure.core/re-groups","clojure.core/re-matcher","clojure.core/re-matches","clojure.core/re-pattern","clojure.core/re-seq","clojure.core/read","clojure.core/read+string","clojure.core/read-line","clojure.core/read-string","clojure.core/reader-conditional","clojure.core/reader-conditional?","clojure.core/realized?","clojure.core/record?","clojure.core/reduce","clojure.core/reduce-kv","clojure.core/reduced","clojure.core/reduced?","clojure.core/reductions","clojure.core/ref","clojure.core/ref-history-count","clojure.core/ref-max-history","clojure.core/ref-min-history","clojure.core/ref-set","clojure.core/refer","clojure.core/release-pending-sends","clojure.core/rem","clojure.core/remove","clojure.core/remove-all-methods","clojure.core/remove-method","clojure.core/remove-ns","clojure.core/remove-tap","clojure.core/remove-watch","clojure.core/repeat","clojure.core/repeatedly","clojure.core/replace","clojure.core/replicate","clojure.core/require","clojure.core/requiring-resolve","clojure.core/reset!","clojure.core/reset-meta!","clojure.core/reset-vals!","clojure.core/resolve","clojure.core/rest","clojure.core/restart-agent","clojure.core/resultset-seq","clojure.core/reverse","clojure.core/reversible?","clojure.core/rseq","clojure.core/rsubseq","clojure.core/run!","clojure.core/satisfies?","clojure.core/second","clojure.core/select-keys","clojure.core/send","clojure.core/send-off","clojure.core/send-via","clojure.core/seq","clojure.core/seq-to-map-for-destructuring","clojure.core/seq?","clojure.core/seqable?","clojure.core/seque","clojure.core/sequence","clojure.core/sequential?","clojure.core/set","clojure.core/set-agent-send-executor!","clojure.core/set-agent-send-off-executor!","clojure.core/set-error-handler!","clojure.core/set-error-mode!","clojure.core/set-validator!","clojure.core/set?","clojure.core/short","clojure.core/short-array","clojure.core/shorts","clojure.core/shuffle","clojure.core/shutdown-agents","clojure.core/simple-ident?","clojure.core/simple-keyword?","clojure.core/simple-symbol?","clojure.core/slurp","clojure.core/some","clojure.core/some-fn","clojure.core/some?","clojure.core/sort","clojure.core/sort-by","clojure.core/sorted-map","clojure.core/sorted-map-by","clojure.core/sorted-set","clojure.core/sorted-set-by","clojure.core/sorted?","clojure.core/special-symbol?","clojure.core/spit","clojure.core/split-at","clojure.core/split-with","clojure.core/str","clojure.core/string?","clojure.core/struct","clojure.core/struct-map","clojure.core/subs","clojure.core/subseq","clojure.core/subvec","clojure.core/supers","clojure.core/swap!","clojure.core/swap-vals!","clojure.core/symbol","clojure.core/symbol?","clojure.core/tagged-literal","clojure.core/tagged-literal?","clojure.core/take","clojure.core/take-last","clojure.core/take-nth","clojure.core/take-while","clojure.core/tap>","clojure.core/test","clojure.core/the-ns","clojure.core/thread-bound?","clojure.core/to-array","clojure.core/to-array-2d","clojure.core/trampoline","clojure.core/transduce","clojure.core/transient","clojure.core/tree-seq","clojure.core/true?","clojure.core/type","clojure.core/unchecked-add","clojure.core/unchecked-add-int","clojure.core/unchecked-byte","clojure.core/unchecked-char","clojure.core/unchecked-dec","clojure.core/unchecked-dec-int","clojure.core/unchecked-divide-int","clojure.core/unchecked-double","clojure.core/unchecked-float","clojure.core/unchecked-inc","clojure.core/unchecked-inc-int","clojure.core/unchecked-int","clojure.core/unchecked-long","clojure.core/unchecked-multiply","clojure.core/unchecked-multiply-int","clojure.core/unchecked-negate","clojure.core/unchecked-negate-int","clojure.core/unchecked-remainder-int","clojure.core/unchecked-short","clojure.core/unchecked-subtract","clojure.core/unchecked-subtract-int","clojure.core/underive","clojure.core/unreduced","clojure.core/unsigned-bit-shift-right","clojure.core/update","clojure.core/update-in","clojure.core/update-keys","clojure.core/update-proxy","clojure.core/update-vals","clojure.core/uri?","clojure.core/use","clojure.core/uuid?","clojure.core/val","clojure.core/vals","clojure.core/var-get","clojure.core/var-set","clojure.core/var?","clojure.core/vary-meta","clojure.core/vec","clojure.core/vector","clojure.core/vector-of","clojure.core/vector?","clojure.core/volatile!","clojure.core/volatile?","clojure.core/vreset!","clojure.core/with-bindings*","clojure.core/with-meta","clojure.core/with-redefs-fn","clojure.core/xml-seq","clojure.core/zero?","clojure.core/zipmap","coll?","commute","comp","comparator","compare","compare-and-set!","compile","complement","completing","concat","conj","conj!","cons","constantly","construct-proxy","contains?","count","counted?","create-ns","create-struct","cycle","dec","dec'","decimal?","dedupe","delay?","deliver","denominator","deref","derive","descendants","destructure","disj","disj!","dissoc","dissoc!","distinct","distinct?","doall","dorun","double","double-array","double?","doubles","drop","drop-last","drop-while","eduction","empty","empty?","ensure","ensure-reduced","enumeration-seq","error-handler","error-mode","eval","even?","every-pred","every?","ex-cause","ex-data","ex-info","ex-message","extend","extenders","extends?","false?","ffirst","file-seq","filter","filterv","find","find-keyword","find-ns","find-protocol-impl","find-protocol-method","find-var","first","flatten","float","float-array","float?","floats","flush","fn?","fnext","fnil","force","format","frequencies","future-call","future-cancel","future-cancelled?","future-done?","future?","gensym","get","get-in","get-method","get-proxy-class","get-thread-bindings","get-validator","group-by","halt-when","hash","hash-combine","hash-map","hash-ordered-coll","hash-set","hash-unordered-coll","ident?","identical?","identity","ifn?","in-ns","inc","inc'","indexed?","infinite?","init-proxy","inst-ms","inst-ms*","inst?","instance?","int","int-array","int?","integer?","interleave","intern","interpose","into","into-array","ints","isa?","iterate","iteration","iterator-seq","juxt","keep","keep-indexed","key","keys","keyword","keyword?","last","line-seq","list","list*","list?","load","load-file","load-reader","load-string","loaded-libs","long","long-array","longs","macroexpand","macroexpand-1","make-array","make-hierarchy","map","map-entry?","map-indexed","map?","mapcat","mapv","max","max-key","memoize","merge","merge-with","meta","method-sig","methods","min","min-key","mix-collection-hash","mod","munge","name","namespace","namespace-munge","nat-int?","neg-int?","neg?","newline","next","nfirst","nil?","nnext","not","not-any?","not-empty","not-every?","not=","ns-aliases","ns-imports","ns-interns","ns-map","ns-name","ns-publics","ns-refers","ns-resolve","ns-unalias","ns-unmap","nth","nthnext","nthrest","num","number?","numerator","object-array","odd?","parents","parse-boolean","parse-double","parse-long","parse-uuid","partial","partition","partition-all","partition-by","pcalls","peek","persistent!","pmap","pop","pop!","pop-thread-bindings","pos-int?","pos?","pr","pr-str","prefer-method","prefers","print","print-ctor","print-dup","print-method","print-simple","print-str","printf","println","println-str","prn","prn-str","promise","proxy-call-with-super","proxy-mappings","proxy-name","push-thread-bindings","qualified-ident?","qualified-keyword?","qualified-symbol?","quot","rand","rand-int","rand-nth","random-sample","random-uuid","range","ratio?","rational?","rationalize","re-find","re-groups","re-matcher","re-matches","re-pattern","re-seq","read","read+string","read-line","read-string","reader-conditional","reader-conditional?","realized?","record?","reduce","reduce-kv","reduced","reduced?","reductions","ref","ref-history-count","ref-max-history","ref-min-history","ref-set","refer","release-pending-sends","rem","remove","remove-all-methods","remove-method","remove-ns","remove-tap","remove-watch","repeat","repeatedly","replace","replicate","require","requiring-resolve","reset!","reset-meta!","reset-vals!","resolve","rest","restart-agent","resultset-seq","reverse","reversible?","rseq","rsubseq","run!","satisfies?","second","select-keys","send","send-off","send-via","seq","seq-to-map-for-destructuring","seq?","seqable?","seque","sequence","sequential?","set","set-agent-send-executor!","set-agent-send-off-executor!","set-error-handler!","set-error-mode!","set-validator!","set?","short","short-array","shorts","shuffle","shutdown-agents","simple-ident?","simple-keyword?","simple-symbol?","slurp","some","some-fn","some?","sort","sort-by","sorted-map","sorted-map-by","sorted-set","sorted-set-by","sorted?","special-symbol?","spit","split-at","split-with","str","string?","struct","struct-map","subs","subseq","subvec","supers","swap!","swap-vals!","symbol","symbol?","tagged-literal","tagged-literal?","take","take-last","take-nth","take-while","tap>","test","the-ns","thread-bound?","to-array","to-array-2d","trampoline","transduce","transient","tree-seq","true?","type","unchecked-add","unchecked-add-int","unchecked-byte","unchecked-char","unchecked-dec","unchecked-dec-int","unchecked-divide-int","unchecked-double","unchecked-float","unchecked-inc","unchecked-inc-int","unchecked-int","unchecked-long","unchecked-multiply","unchecked-multiply-int","unchecked-negate","unchecked-negate-int","unchecked-remainder-int","unchecked-short","unchecked-subtract","unchecked-subtract-int","underive","unreduced","unsigned-bit-shift-right","update","update-in","update-keys","update-proxy","update-vals","uri?","use","uuid?","val","vals","var-get","var-set","var?","vary-meta","vec","vector","vector-of","vector?","volatile!","volatile?","vreset!","with-bindings*","with-meta","with-redefs-fn","xml-seq","zero?","zipmap"], + \ 'clojureMacro': ["->","->>","..","amap","and","areduce","as->","assert","binding","bound-fn","clojure.core/->","clojure.core/->>","clojure.core/..","clojure.core/amap","clojure.core/and","clojure.core/areduce","clojure.core/as->","clojure.core/assert","clojure.core/binding","clojure.core/bound-fn","clojure.core/comment","clojure.core/declare","clojure.core/delay","clojure.core/dosync","clojure.core/doto","clojure.core/extend-protocol","clojure.core/extend-type","clojure.core/for","clojure.core/future","clojure.core/gen-class","clojure.core/gen-interface","clojure.core/import","clojure.core/io!","clojure.core/lazy-cat","clojure.core/lazy-seq","clojure.core/locking","clojure.core/memfn","clojure.core/ns","clojure.core/or","clojure.core/proxy","clojure.core/proxy-super","clojure.core/pvalues","clojure.core/refer-clojure","clojure.core/reify","clojure.core/some->","clojure.core/some->>","clojure.core/sync","clojure.core/time","clojure.core/vswap!","clojure.core/with-bindings","clojure.core/with-in-str","clojure.core/with-loading-context","clojure.core/with-local-vars","clojure.core/with-open","clojure.core/with-out-str","clojure.core/with-precision","clojure.core/with-redefs","comment","declare","delay","dosync","doto","extend-protocol","extend-type","for","future","gen-class","gen-interface","import","io!","lazy-cat","lazy-seq","locking","memfn","ns","or","proxy","proxy-super","pvalues","refer-clojure","reify","some->","some->>","sync","time","vswap!","with-bindings","with-in-str","with-loading-context","with-local-vars","with-open","with-out-str","with-precision","with-redefs"], + \ 'clojureRepeat': ["clojure.core/doseq","clojure.core/dotimes","clojure.core/loop","clojure.core/while","doseq","dotimes","loop","loop*","recur","while"], + \ 'clojureSpecial': ["&",".","clojure.core/fn","clojure.core/import*","clojure.core/let","clojure.core/letfn","do","fn","fn*","let","let*","letfn","letfn*","monitor-enter","monitor-exit","new","quote","reify*","set!","var"], + \ 'clojureVariable': ["*1","*2","*3","*agent*","*allow-unresolved-vars*","*assert*","*clojure-version*","*command-line-args*","*compile-files*","*compile-path*","*compiler-options*","*data-readers*","*default-data-reader-fn*","*e","*err*","*file*","*flush-on-newline*","*fn-loader*","*in*","*math-context*","*ns*","*out*","*print-dup*","*print-length*","*print-level*","*print-meta*","*print-namespace-maps*","*print-readably*","*read-eval*","*reader-resolver*","*source-path*","*suppress-read*","*unchecked-math*","*use-context-classloader*","*verbose-defrecords*","*warn-on-reflection*","EMPTY-NODE","Inst","char-escape-string","char-name-string","clojure.core/*1","clojure.core/*2","clojure.core/*3","clojure.core/*agent*","clojure.core/*allow-unresolved-vars*","clojure.core/*assert*","clojure.core/*clojure-version*","clojure.core/*command-line-args*","clojure.core/*compile-files*","clojure.core/*compile-path*","clojure.core/*compiler-options*","clojure.core/*data-readers*","clojure.core/*default-data-reader-fn*","clojure.core/*e","clojure.core/*err*","clojure.core/*file*","clojure.core/*flush-on-newline*","clojure.core/*fn-loader*","clojure.core/*in*","clojure.core/*math-context*","clojure.core/*ns*","clojure.core/*out*","clojure.core/*print-dup*","clojure.core/*print-length*","clojure.core/*print-level*","clojure.core/*print-meta*","clojure.core/*print-namespace-maps*","clojure.core/*print-readably*","clojure.core/*read-eval*","clojure.core/*reader-resolver*","clojure.core/*source-path*","clojure.core/*suppress-read*","clojure.core/*unchecked-math*","clojure.core/*use-context-classloader*","clojure.core/*verbose-defrecords*","clojure.core/*warn-on-reflection*","clojure.core/EMPTY-NODE","clojure.core/Inst","clojure.core/char-escape-string","clojure.core/char-name-string","clojure.core/default-data-readers","clojure.core/primitives-classnames","clojure.core/print-dup","clojure.core/print-method","clojure.core/unquote","clojure.core/unquote-splicing","default-data-readers","primitives-classnames","print-dup","print-method","unquote","unquote-splicing"] + \ } function! s:syntax_keyword(dict) for key in keys(a:dict) @@ -81,8 +81,6 @@ syntax match clojureSymbol "\v%([a-zA-Z!$&*_+=|<.>?-]|[^\x00-\x7F])+%(:?%([a-zA- " NB. Correct matching of radix literals was removed for better performance. syntax match clojureNumber "\v<[-+]?%(%([2-9]|[12]\d|3[0-6])[rR][[:alnum:]]+|%(0\o*|0x\x+|[1-9]\d*)N?|%(0|[1-9]\d*|%(0|[1-9]\d*)\.\d*)%(M|[eE][-+]?\d+)?|%(0|[1-9]\d*)/%(0|[1-9]\d*))>" -syntax match clojureVarArg "&" - syntax match clojureQuote "\v['`]" syntax match clojureUnquote "\v\~\@?" syntax match clojureMeta "\^" @@ -97,8 +95,8 @@ syntax region clojureRegexpQuoted start=/\\Q/ms=e+1 skip=/\\\\\|\\"/ end=/\\E/me syntax region clojureRegexpQuote start=/\\Q/ skip=/\\\\\|\\"/ end=/\\E/ end=/"/me=s-1 contains=clojureRegexpQuoted keepend contained " -*- CHARACTER PROPERTY CLASSES -*- -" Generated from https://github.com/clojure-vim/clojure.vim/blob/62b215f079ce0f3834fd295c7a7f6bd8cc54bcc3/clj/src/vim_clojure_static/generate.clj -" Java version 17 +" Generated from https://github.com/clojure-vim/clojure.vim/blob/fd280e33e84c88e97860930557dba3ff80b1a82d/clj/src/vim_clojure_static/generate.clj +" Java version 17.0.2 syntax match clojureRegexpPosixCharClass "\v\\[pP]\{%(Cntrl|A%(l%(pha|num)|SCII)|Space|Graph|Upper|P%(rint|unct)|Blank|XDigit|Digit|Lower)\}" contained display syntax match clojureRegexpJavaCharClass "\v\\[pP]\{java%(Whitespace|JavaIdentifier%(Part|Start)|SpaceChar|Mirrored|TitleCase|I%(SOControl|de%(ographic|ntifierIgnorable))|D%(efined|igit)|U%(pperCase|nicodeIdentifier%(Part|Start))|L%(etter%(OrDigit)?|owerCase)|Alphabetic)\}" contained display syntax match clojureRegexpUnicodeCharClass "\v\\[pP]\{\cIs%(l%(owercase|etter)|hex%(digit|_digit)|w%(hite%(_space|space)|ord)|noncharacter%(_code_point|codepoint)|p%(rint|unctuation)|ideographic|graph|a%(l%(num|phabetic)|ssigned)|uppercase|join%(control|_control)|titlecase|blank|digit|control)\}" contained display @@ -127,7 +125,7 @@ syntax match clojureRegexpMod "\v\(@<=\?%(\<?[=!]|\>)" contained display syntax match clojureRegexpMod "\v\(@<=\?\<[[:alpha:]]+\>" contained display syntax region clojureRegexpGroup start="(" skip=/\\\\\|\\)/ end=")" matchgroup=clojureRegexpGroup contained contains=clojureRegexpMod,clojureRegexpQuantifier,clojureRegexpBoundary,clojureRegexpEscape,@clojureRegexpCharClasses -syntax region clojureRegexp start=/\#"/ skip=/\\\\\|\\"/ end=/"/ contains=@clojureRegexpCharClasses,clojureRegexpEscape,clojureRegexpQuote,clojureRegexpBoundary,clojureRegexpQuantifier,clojureRegexpOr,clojureRegexpBackRef,clojureRegexpGroup keepend +syntax region clojureRegexp matchgroup=clojureRegexpDelimiter start=/\#"/ skip=/\\\\\|\\"/ end=/"/ contains=@clojureRegexpCharClasses,clojureRegexpEscape,clojureRegexpQuote,clojureRegexpBoundary,clojureRegexpQuantifier,clojureRegexpOr,clojureRegexpBackRef,clojureRegexpGroup keepend syntax keyword clojureCommentTodo contained FIXME XXX TODO BUG NOTE HACK FIXME: XXX: TODO: BUG: NOTE: HACK: @@ -149,8 +147,8 @@ if exists('g:clojure_discard_macro') && g:clojure_discard_macro endif " -*- TOP CLUSTER -*- -" Generated from https://github.com/clojure-vim/clojure.vim/blob/62b215f079ce0f3834fd295c7a7f6bd8cc54bcc3/clj/src/vim_clojure_static/generate.clj -syntax cluster clojureTop contains=@Spell,clojureAnonArg,clojureBoolean,clojureCharacter,clojureComment,clojureCond,clojureConstant,clojureDefine,clojureDeref,clojureDiscard,clojureDispatch,clojureError,clojureException,clojureFunc,clojureKeyword,clojureMacro,clojureMap,clojureMeta,clojureNumber,clojureQuote,clojureRegexp,clojureRepeat,clojureSexp,clojureSpecial,clojureString,clojureSymbol,clojureUnquote,clojureVarArg,clojureVariable,clojureVector +" Generated from https://github.com/clojure-vim/clojure.vim/blob/fd280e33e84c88e97860930557dba3ff80b1a82d/clj/src/vim_clojure_static/generate.clj +syntax cluster clojureTop contains=@Spell,clojureAnonArg,clojureBoolean,clojureCharacter,clojureComment,clojureCond,clojureConstant,clojureDefine,clojureDeref,clojureDiscard,clojureDispatch,clojureError,clojureException,clojureFunc,clojureKeyword,clojureMacro,clojureMap,clojureMeta,clojureNumber,clojureQuote,clojureRegexp,clojureRepeat,clojureSexp,clojureSpecial,clojureString,clojureSymbol,clojureUnquote,clojureVariable,clojureVector syntax region clojureSexp matchgroup=clojureParen start="(" end=")" contains=@clojureTop fold syntax region clojureVector matchgroup=clojureParen start="\[" end="]" contains=@clojureTop fold @@ -171,6 +169,7 @@ highlight default link clojureStringDelimiter String highlight default link clojureStringEscape Character highlight default link clojureRegexp Constant +highlight default link clojureRegexpDelimiter Constant highlight default link clojureRegexpEscape Character highlight default link clojureRegexpCharClass SpecialChar highlight default link clojureRegexpPosixCharClass clojureRegexpCharClass @@ -195,7 +194,6 @@ highlight default link clojureMacro Macro highlight default link clojureRepeat Repeat highlight default link clojureSpecial Special -highlight default link clojureVarArg Special highlight default link clojureQuote SpecialChar highlight default link clojureUnquote SpecialChar highlight default link clojureMeta SpecialChar diff --git a/runtime/syntax/css.vim b/runtime/syntax/css.vim index 67ad1ea335..564dc151bc 100644 --- a/runtime/syntax/css.vim +++ b/runtime/syntax/css.vim @@ -7,7 +7,7 @@ " Nikolai Weibull (Add CSS2 support) " URL: https://github.com/vim-language-dept/css-syntax.vim " Maintainer: Jay Sitter <jay@jaysitter.com> -" Last Change: 2021 Oct 15 +" Last Change: 2021 Oct 20 " quit when a syntax file was already loaded if !exists("main_syntax") @@ -116,7 +116,7 @@ syn keyword cssColor contained ActiveBorder ActiveCaption AppWorkspace ButtonFac syn case ignore syn match cssImportant contained "!\s*important\>" -syn match cssCustomProp contained "--[a-zA-Z0-9-_]*" +syn match cssCustomProp contained "\<--[a-zA-Z0-9-_]*\>" syn match cssColor contained "\<transparent\>" syn match cssColor contained "\<currentColor\>" @@ -126,6 +126,7 @@ syn match cssColor contained "#\x\{6\}\>" contains=cssUnitDecorators syn match cssColor contained "#\x\{8\}\>" contains=cssUnitDecorators syn region cssURL contained matchgroup=cssFunctionName start="\<\(uri\|url\|local\|format\)\s*(" end=")" contains=cssStringQ,cssStringQQ oneline +syn region cssMathGroup contained matchgroup=cssMathParens start="(" end=")" containedin=cssFunction,cssMathGroup contains=cssCustomProp,cssValue.*,cssFunction,cssColor,cssStringQ,cssStringQQ oneline syn region cssFunction contained matchgroup=cssFunctionName start="\<\(var\|calc\)\s*(" end=")" contains=cssCustomProp,cssValue.*,cssFunction,cssColor,cssStringQ,cssStringQQ oneline syn region cssFunction contained matchgroup=cssFunctionName start="\<\(rgb\|clip\|attr\|counter\|rect\|cubic-bezier\|steps\)\s*(" end=")" oneline contains=cssValueInteger,cssValueNumber,cssValueLength,cssFunctionComma syn region cssFunction contained matchgroup=cssFunctionName start="\<\(rgba\|hsl\|hsla\|color-stop\|from\|to\)\s*(" end=")" oneline contains=cssColor,cssValueInteger,cssValueNumber,cssValueLength,cssFunctionComma,cssFunction @@ -395,9 +396,9 @@ syn match cssUIAttr contained '\<preserve-3d\>' syn match cssIEUIAttr contained '\<bicubic\>' " Webkit/iOS specific properties -syn match cssUIProp contained '\<tap-highlight-color\|user-select\|touch-callout\>' +syn match cssUIProp contained '\<\(tap-highlight-color\|user-select\|touch-callout\)\>' " IE specific properties -syn match cssIEUIProp contained '\<interpolation-mode\|zoom\|filter\>' +syn match cssIEUIProp contained '\<\(interpolation-mode\|zoom\|filter\)\>' " Webkit/Firebox specific properties/attributes syn keyword cssUIProp contained appearance @@ -423,11 +424,15 @@ syn keyword cssAuralAttr contained male female child code digits continuous syn match cssMobileTextProp contained "\<text-size-adjust\>" syn keyword cssMediaProp contained width height orientation scan -syn match cssMediaProp contained /\(\(max\|min\)-\)\=\(\(device\)-\)\=aspect-ratio/ -syn match cssMediaProp contained /\(\(max\|min\)-\)\=device-pixel-ratio/ -syn match cssMediaProp contained /\(\(max\|min\)-\)\=device-\(height\|width\)/ -syn match cssMediaProp contained /\(\(max\|min\)-\)\=\(height\|width\|resolution\|monochrome\|color\(-index\)\=\)/ +syn keyword cssMediaProp contained any-hover any-pointer color-gamut grid hover +syn keyword cssMediaProp contained overflow-block overflow-inline pointer update +syn match cssMediaProp contained /\<\(\(max\|min\)-\)\=\(\(device\)-\)\=aspect-ratio\>/ +syn match cssMediaProp contained /\<\(\(max\|min\)-\)\=device-pixel-ratio\>/ +syn match cssMediaProp contained /\<\(\(max\|min\)-\)\=device-\(height\|width\)\>/ +syn match cssMediaProp contained /\<\(\(max\|min\)-\)\=\(height\|width\|resolution\|monochrome\|color\(-index\)\=\)\>/ syn keyword cssMediaAttr contained portrait landscape progressive interlace +syn keyword cssMediaAttr contained coarse fast fine hover infinite p3 paged +syn keyword cssMediaAttr contained rec2020 scroll slow srgb syn match cssKeyFrameProp contained /\(\d\+\(\.\d\+\)\?%\|\(\<from\|to\>\)\)/ nextgroup=cssDefinition syn match cssPageMarginProp /@\(\(top\|left\|right\|bottom\)-\(left\|center\|right\|middle\|bottom\)\)\(-corner\)\=/ contained nextgroup=cssDefinition syn keyword cssPageProp contained content size @@ -445,17 +450,17 @@ syn match cssBraceError "}" syn match cssAttrComma "," " Pseudo class -" http://www.w3.org/TR/css3-selectors/ +" https://www.w3.org/TR/selectors-4/ syn match cssPseudoClass ":[A-Za-z0-9_-]*" contains=cssNoise,cssPseudoClassId,cssUnicodeEscape,cssVendor,cssPseudoClassFn syn keyword cssPseudoClassId contained link visited active hover before after left right -syn keyword cssPseudoClassId contained root empty target enable disabled checked invalid +syn keyword cssPseudoClassId contained root empty target enabled disabled checked invalid syn match cssPseudoClassId contained "\<first-\(line\|letter\)\>" syn match cssPseudoClassId contained "\<\(first\|last\|only\)-\(of-type\|child\)\>" -syn region cssPseudoClassFn contained matchgroup=cssFunctionName start="\<\(not\|lang\|\(nth\|nth-last\)-\(of-type\|child\)\)(" end=")" contains=cssStringQ,cssStringQQ +syn match cssPseudoClassId contained "\<focus\(-within\|-visible\)\=\>" +syn region cssPseudoClassFn contained matchgroup=cssFunctionName start="\<\(not\|is\|lang\|\(nth\|nth-last\)-\(of-type\|child\)\)(" end=")" contains=cssStringQ,cssStringQQ,cssTagName,cssAttributeSelector,cssClassName,cssIdentifier " ------------------------------------ " Vendor specific properties syn match cssPseudoClassId contained "\<selection\>" -syn match cssPseudoClassId contained "\<focus\(-inner\)\=\>" syn match cssPseudoClassId contained "\<\(input-\)\=placeholder\>" " Misc highlight groups diff --git a/runtime/syntax/debchangelog.vim b/runtime/syntax/debchangelog.vim index 79352c0827..8d09af0886 100644 --- a/runtime/syntax/debchangelog.vim +++ b/runtime/syntax/debchangelog.vim @@ -3,7 +3,7 @@ " Maintainer: Debian Vim Maintainers " Former Maintainers: Gerfried Fuchs <alfie@ist.org> " Wichert Akkerman <wakkerma@debian.org> -" Last Change: 2021 Oct 19 +" Last Change: 2022 Mar 28 " URL: https://salsa.debian.org/vim-team/vim-debian/blob/master/syntax/debchangelog.vim " Standard syntax initialization @@ -24,7 +24,7 @@ let s:supported = [ \ 'jessie', 'stretch', 'buster', 'bullseye', 'bookworm', \ 'trixie', 'sid', 'rc-buggy', \ - \ 'trusty', 'xenial', 'bionic', 'focal', 'hirsute', 'impish', 'jammy', + \ 'trusty', 'xenial', 'bionic', 'focal', 'impish', 'jammy', \ 'devel' \ ] let s:unsupported = [ @@ -35,7 +35,7 @@ let s:unsupported = [ \ 'gutsy', 'hardy', 'intrepid', 'jaunty', 'karmic', 'lucid', \ 'maverick', 'natty', 'oneiric', 'precise', 'quantal', 'raring', 'saucy', \ 'utopic', 'vivid', 'wily', 'yakkety', 'zesty', 'artful', 'cosmic', - \ 'disco', 'eoan', 'groovy' + \ 'disco', 'eoan', 'hirsute', 'groovy' \ ] let &cpo=s:cpo diff --git a/runtime/syntax/debcontrol.vim b/runtime/syntax/debcontrol.vim index 25fc252de6..8b65ece4ca 100644 --- a/runtime/syntax/debcontrol.vim +++ b/runtime/syntax/debcontrol.vim @@ -3,7 +3,7 @@ " Maintainer: Debian Vim Maintainers " Former Maintainers: Gerfried Fuchs <alfie@ist.org> " Wichert Akkerman <wakkerma@debian.org> -" Last Change: 2020 Oct 26 +" Last Change: 2021 Nov 26 " URL: https://salsa.debian.org/vim-team/vim-debian/blob/master/syntax/debcontrol.vim " Standard syntax initialization @@ -91,6 +91,11 @@ syn case ignore " Handle all fields from deb-src-control(5) +" Catch-all for the legal fields +syn region debcontrolField matchgroup=debcontrolKey start="^\%(\%(XSBC-Original-\)\=Maintainer\|Standards-Version\|Bugs\|Origin\|X[SB]-Python-Version\|\%(XS-\)\=Vcs-Mtn\|\%(XS-\)\=Testsuite\%(-Triggers\)\=\|Build-Profiles\|Tag\|Subarchitecture\|Kernel-Version\|Installer-Menu-Item\): " end="$" contains=debcontrolVariable,debcontrolEmail oneline +syn region debcontrolMultiField matchgroup=debcontrolKey start="^\%(Build-\%(Conflicts\|Depends\)\%(-Arch\|-Indep\)\=\|\%(Pre-\)\=Depends\|Recommends\|Suggests\|Breaks\|Enhances\|Replaces\|Conflicts\|Provides\|Built-Using\|Uploaders\|X[SBC]\{0,3\}\%(Private-\)\=-[-a-zA-Z0-9]\+\): *" skip="^[ \t]" end="^$"me=s-1 end="^[^ \t#]"me=s-1 contains=debcontrolEmail,debcontrolVariable,debcontrolComment +syn region debcontrolMultiFieldSpell matchgroup=debcontrolKey start="^Description: *" skip="^[ \t]" end="^$"me=s-1 end="^[^ \t#]"me=s-1 contains=debcontrolEmail,debcontrolVariable,debcontrolComment,@Spell + " Fields for which we do strict syntax checking syn region debcontrolStrictField matchgroup=debcontrolKey start="^Architecture: *" end="$" contains=debcontrolArchitecture,debcontrolSpace oneline syn region debcontrolStrictField matchgroup=debcontrolKey start="^Multi-Arch: *" end="$" contains=debcontrolMultiArch oneline @@ -99,20 +104,15 @@ syn region debcontrolStrictField matchgroup=debcontrolKey start="^Priority: *" e syn region debcontrolStrictField matchgroup=debcontrolKey start="^Section: *" end="$" contains=debcontrolSection oneline syn region debcontrolStrictField matchgroup=debcontrolKey start="^\%(XC-\)\=Package-Type: *" end="$" contains=debcontrolPackageType oneline syn region debcontrolStrictField matchgroup=debcontrolKey start="^Homepage: *" end="$" contains=debcontrolHTTPUrl oneline keepend -syn region debcontrolStrictField matchgroup=debcontrolKey start="^\%(XS-\)\=Vcs-\%(Browser\|Arch\|Bzr\|Darcs\|Hg\): *" end="$" contains=debcontrolHTTPUrl oneline keepend -syn region debcontrolStrictField matchgroup=debcontrolKey start="^\%(XS-\)\=Vcs-Svn: *" end="$" contains=debcontrolVcsSvn,debcontrolHTTPUrl oneline keepend -syn region debcontrolStrictField matchgroup=debcontrolKey start="^\%(XS-\)\=Vcs-Cvs: *" end="$" contains=debcontrolVcsCvs oneline keepend -syn region debcontrolStrictField matchgroup=debcontrolKey start="^\%(XS-\)\=Vcs-Git: *" end="$" contains=debcontrolVcsGit oneline keepend +syn region debcontrolStrictField matchgroup=debcontrolKey start="^\%(XS-[-a-zA-Z0-9]\+-\)\=Vcs-\%(Browser\|Arch\|Bzr\|Darcs\|Hg\): *" end="$" contains=debcontrolHTTPUrl oneline keepend +syn region debcontrolStrictField matchgroup=debcontrolKey start="^\%(XS-[-a-zA-Z0-9]\+-\)\=Vcs-Svn: *" end="$" contains=debcontrolVcsSvn,debcontrolHTTPUrl oneline keepend +syn region debcontrolStrictField matchgroup=debcontrolKey start="^\%(XS-[-a-zA-Z0-9]\+-\)\=Vcs-Cvs: *" end="$" contains=debcontrolVcsCvs oneline keepend +syn region debcontrolStrictField matchgroup=debcontrolKey start="^\%(XS-[-a-zA-Z0-9]\+-\)\=Vcs-Git: *" end="$" contains=debcontrolVcsGit oneline keepend syn region debcontrolStrictField matchgroup=debcontrolKey start="^Rules-Requires-Root: *" end="$" contains=debcontrolR3 oneline syn region debcontrolStrictField matchgroup=debcontrolKey start="^\%(Build-\)\=Essential: *" end="$" contains=debcontrolYesNo oneline syn region debcontrolStrictField matchgroup=debcontrolDeprecatedKey start="^\%(XS-\)\=DM-Upload-Allowed: *" end="$" contains=debcontrolDmUpload oneline -" Catch-all for the other legal fields -syn region debcontrolField matchgroup=debcontrolKey start="^\%(\%(XSBC-Original-\)\=Maintainer\|Standards-Version\|Bugs\|Origin\|X[SB]-Python-Version\|\%(XS-\)\=Vcs-Mtn\|\%(XS-\)\=Testsuite\%(-Triggers\)\=\|Build-Profiles\|Tag\|Subarchitecture\|Kernel-Version\|Installer-Menu-Item\): " end="$" contains=debcontrolVariable,debcontrolEmail oneline -syn region debcontrolMultiField matchgroup=debcontrolKey start="^\%(Build-\%(Conflicts\|Depends\)\%(-Arch\|-Indep\)\=\|\%(Pre-\)\=Depends\|Recommends\|Suggests\|Breaks\|Enhances\|Replaces\|Conflicts\|Provides\|Built-Using\|Uploaders\|X[SBC]\{0,3\}\%(Private-\)\=-[-a-zA-Z0-9]\+\): *" skip="^[ \t]" end="^$"me=s-1 end="^[^ \t#]"me=s-1 contains=debcontrolEmail,debcontrolVariable,debcontrolComment -syn region debcontrolMultiFieldSpell matchgroup=debcontrolKey start="^Description: *" skip="^[ \t]" end="^$"me=s-1 end="^[^ \t#]"me=s-1 contains=debcontrolEmail,debcontrolVariable,debcontrolComment,@Spell - " Associate our matches and regions with pretty colours hi def link debcontrolKey Keyword hi def link debcontrolField Normal diff --git a/runtime/syntax/debsources.vim b/runtime/syntax/debsources.vim index 4b4c497f18..d79ce4b573 100644 --- a/runtime/syntax/debsources.vim +++ b/runtime/syntax/debsources.vim @@ -2,7 +2,7 @@ " Language: Debian sources.list " Maintainer: Debian Vim Maintainers " Former Maintainer: Matthijs Mohlmann <matthijs@cacholong.nl> -" Last Change: 2021 Oct 19 +" Last Change: 2022 Mar 28 " URL: https://salsa.debian.org/vim-team/vim-debian/blob/master/syntax/debsources.vim " Standard syntax initialization @@ -26,7 +26,7 @@ let s:supported = [ \ 'jessie', 'stretch', 'buster', 'bullseye', 'bookworm', \ 'trixie', 'sid', 'rc-buggy', \ - \ 'trusty', 'xenial', 'bionic', 'focal', 'hirsute', 'impish', 'jammy', + \ 'trusty', 'xenial', 'bionic', 'focal', 'impish', 'jammy', \ 'devel' \ ] let s:unsupported = [ @@ -37,7 +37,7 @@ let s:unsupported = [ \ 'gutsy', 'hardy', 'intrepid', 'jaunty', 'karmic', 'lucid', \ 'maverick', 'natty', 'oneiric', 'precise', 'quantal', 'raring', 'saucy', \ 'utopic', 'vivid', 'wily', 'yakkety', 'zesty', 'artful', 'cosmic', - \ 'disco', 'eoan', 'groovy' + \ 'disco', 'eoan', 'hirsute', 'groovy' \ ] let &cpo=s:cpo diff --git a/runtime/syntax/dep3patch.vim b/runtime/syntax/dep3patch.vim new file mode 100644 index 0000000000..cb0eda8931 --- /dev/null +++ b/runtime/syntax/dep3patch.vim @@ -0,0 +1,57 @@ +" Vim syntax file +" Language: Debian DEP3 Patch headers +" Maintainer: Gabriel Filion <gabster@lelutin.ca> +" Last Change: 2022 Apr 06 +" URL: https://salsa.debian.org/vim-team/vim-debian/blob/master/syntax/dep3patch.vim +" +" Specification of the DEP3 patch header format is available at: +" https://dep-team.pages.debian.net/deps/dep3/ + +" Standard syntax initialization +if exists('b:current_syntax') + finish +endif + +runtime! syntax/diff.vim +unlet! b:current_syntax + +let s:cpo_save = &cpo +set cpo&vim + +syn region dep3patchHeaders start="\%^" end="^\%(---\)\@=" contains=dep3patchKey,dep3patchMultiField + +syn case ignore + +syn region dep3patchMultiField matchgroup=dep3patchKey start="^\%(Description\|Subject\)\ze: *" skip="^[ \t]" end="^$"me=s-1 end="^[^ \t#]"me=s-1 contained contains=@Spell +syn region dep3patchMultiField matchgroup=dep3patchKey start="^Origin\ze: *" end="$" contained contains=dep3patchHTTPUrl,dep3patchCommitID,dep3patchOriginCategory oneline keepend +syn region dep3patchMultiField matchgroup=dep3patchKey start="^Bug\%(-[[:graph:]]\+\)\?\ze: *" end="$" contained contains=dep3patchHTTPUrl oneline keepend +syn region dep3patchMultiField matchgroup=dep3patchKey start="^Forwarded\ze: *" end="$" contained contains=dep3patchHTTPUrl,dep3patchForwardedShort oneline keepend +syn region dep3patchMultiField matchgroup=dep3patchKey start="^\%(Author\|From\)\ze: *" end="$" contained contains=dep3patchEmail oneline keepend +syn region dep3patchMultiField matchgroup=dep3patchKey start="^\%(Reviewed-by\|Acked-by\)\ze: *" end="$" contained contains=dep3patchEmail oneline keepend +syn region dep3patchMultiField matchgroup=dep3patchKey start="^Last-Update\ze: *" end="$" contained contains=dep3patchISODate oneline keepend +syn region dep3patchMultiField matchgroup=dep3patchKey start="^Applied-Upstream\ze: *" end="$" contained contains=dep3patchHTTPUrl,dep3patchCommitID oneline keepend + +syn match dep3patchHTTPUrl contained "\vhttps?://[[:alnum:]][-[:alnum:]]*[[:alnum:]]?(\.[[:alnum:]][-[:alnum:]]*[[:alnum:]]?)*\.[[:alpha:]][-[:alnum:]]*[[:alpha:]]?(:\d+)?(/[^[:space:]]*)?$" +syn match dep3patchCommitID contained "commit:[[:alnum:]]\+" +syn match dep3patchOriginCategory contained "\%(upstream\|backport\|vendor\|other\), " +syn match dep3patchForwardedShort contained "\%(yes\|no\|not-needed\), " +syn match dep3patchEmail "[_=[:alnum:]\.+-]\+@[[:alnum:]\./\-]\+" +syn match dep3patchEmail "<.\{-}>" +syn match dep3patchISODate "[[:digit:]]\{4}-[[:digit:]]\{2}-[[:digit:]]\{2}" + +" Associate our matches and regions with pretty colours +hi def link dep3patchKey Keyword +hi def link dep3patchOriginCategory Keyword +hi def link dep3patchForwardedShort Keyword +hi def link dep3patchMultiField Normal +hi def link dep3patchHTTPUrl Identifier +hi def link dep3patchCommitID Identifier +hi def link dep3patchEmail Identifier +hi def link dep3patchISODate Identifier + +let b:current_syntax = 'dep3patch' + +let &cpo = s:cpo_save +unlet s:cpo_save + +" vim: ts=8 sw=2 diff --git a/runtime/syntax/django.vim b/runtime/syntax/django.vim index d3ca4de0e2..76b47d2e59 100644 --- a/runtime/syntax/django.vim +++ b/runtime/syntax/django.vim @@ -1,7 +1,7 @@ " Vim syntax file " Language: Django template " Maintainer: Dave Hodder <dmh@dmh.org.uk> -" Last Change: 2014 Jul 13 +" Last Change: 2021 Nov 29 " quit when a syntax file was already loaded if exists("b:current_syntax") @@ -31,6 +31,7 @@ syn keyword djangoStatement contained closecomment widthratio url with endwith syn keyword djangoStatement contained get_current_language trans noop blocktrans syn keyword djangoStatement contained endblocktrans get_available_languages syn keyword djangoStatement contained get_current_language_bidi plural +syn keyword djangoStatement contained translate blocktranslate endblocktranslate " Django templete built-in filters syn keyword djangoFilter contained add addslashes capfirst center cut date diff --git a/runtime/syntax/dtd.vim b/runtime/syntax/dtd.vim index ef0592e1d1..58f07c98dd 100644 --- a/runtime/syntax/dtd.vim +++ b/runtime/syntax/dtd.vim @@ -45,7 +45,7 @@ if !exists("dtd_no_tag_errors") syn region dtdError contained start=+<!+lc=2 end=+>+ endif -" if this is a html like comment hightlight also +" if this is a html like comment highlight also " the opening <! and the closing > as Comment. syn region dtdComment start=+<![ \t]*--+ end=+-->+ contains=dtdTodo,@Spell @@ -99,8 +99,8 @@ syn match dtdEntity "&[^; \t]*;" contains=dtdEntityPunct syn match dtdEntityPunct contained "[&.;]" " Strings are between quotes -syn region dtdString start=+"+ skip=+\\\\\|\\"+ end=+"+ contains=dtdAttrDef,dtdAttrType,dtdEnum,dtdParamEntityInst,dtdEntity,dtdCard -syn region dtdString start=+'+ skip=+\\\\\|\\'+ end=+'+ contains=dtdAttrDef,dtdAttrType,dtdEnum,dtdParamEntityInst,dtdEntity,dtdCard +syn region dtdString start=+"+ skip=+\\\\\|\\"+ end=+"+ contains=dtdAttrDef,dtdAttrType,dtdParamEntityInst,dtdEntity,dtdCard +syn region dtdString start=+'+ skip=+\\\\\|\\'+ end=+'+ contains=dtdAttrDef,dtdAttrType,dtdParamEntityInst,dtdEntity,dtdCard " Enumeration of elements or data between parenthesis " diff --git a/runtime/syntax/eruby.vim b/runtime/syntax/eruby.vim index 6bb24fe562..3d1bf715db 100644 --- a/runtime/syntax/eruby.vim +++ b/runtime/syntax/eruby.vim @@ -3,9 +3,9 @@ " Maintainer: Tim Pope <vimNOSPAM@tpope.org> " URL: https://github.com/vim-ruby/vim-ruby " Release Coordinator: Doug Kearns <dougkearns@gmail.com> -" Last Change: 2018 Jul 04 +" Last Change: 2022 Mar 18 -if &syntax !~# '\<eruby\>' || get(b:, 'current_syntax') =~# '\<eruby\>' +if exists("b:current_syntax") finish endif @@ -19,8 +19,6 @@ endif if &filetype =~ '^eruby\.' let b:eruby_subtype = matchstr(&filetype,'^eruby\.\zs\w\+') -elseif &filetype =~ '^.*\.eruby\>' - let b:eruby_subtype = matchstr(&filetype,'^.\{-\}\ze\.eruby\>') elseif !exists("b:eruby_subtype") && main_syntax == 'eruby' let s:lines = getline(1)."\n".getline(2)."\n".getline(3)."\n".getline(4)."\n".getline(5)."\n".getline("$") let b:eruby_subtype = matchstr(s:lines,'eruby_subtype=\zs\w\+') @@ -54,10 +52,10 @@ if !b:eruby_nest_level let b:eruby_nest_level = 1 endif -if get(b:, 'eruby_subtype', '') !~# '^\%(eruby\)\=$' && &syntax =~# '^eruby\>' +if exists("b:eruby_subtype") && b:eruby_subtype != '' && b:eruby_subtype !=? 'eruby' exe "runtime! syntax/".b:eruby_subtype.".vim" + unlet! b:current_syntax endif -unlet! b:current_syntax syn include @rubyTop syntax/ruby.vim syn cluster erubyRegions contains=erubyOneLiner,erubyBlock,erubyExpression,erubyComment @@ -72,7 +70,7 @@ exe 'syn region erubyComment matchgroup=erubyDelimiter start="<%\{1,'.b:erub hi def link erubyDelimiter PreProc hi def link erubyComment Comment -let b:current_syntax = matchstr(&syntax, '^.*\<eruby\>') +let b:current_syntax = 'eruby' if main_syntax == 'eruby' unlet main_syntax 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 new file mode 100644 index 0000000000..a2f50e50b8 --- /dev/null +++ b/runtime/syntax/i3config.vim @@ -0,0 +1,257 @@ +" Vim syntax file +" Language: i3 config file +" Original Author: Mohamed Boughaba <mohamed dot bgb at gmail dot com> +" Maintainer: Quentin Hibon (github user hiqua) +" Version: 0.4 +" Last Change: 2022 Jan 15 + +" References: +" http://i3wm.org/docs/userguide.html#configuring +" http://vimdoc.sourceforge.net/htmldoc/syntax.html +" +" +" Quit when a syntax file was already loaded +if exists("b:current_syntax") + finish +endif + +scriptencoding utf-8 + +" Error +syn match i3ConfigError /.*/ + +" Todo +syn keyword i3ConfigTodo TODO FIXME XXX contained + +" Comment +" Comments are started with a # and can only be used at the beginning of a line +syn match i3ConfigComment /^\s*#.*$/ contains=i3ConfigTodo + +" Font +" A FreeType font description is composed by: +" a font family, a style, a weight, a variant, a stretch and a size. +syn match i3ConfigFontSeparator /,/ contained +syn match i3ConfigFontSeparator /:/ contained +syn keyword i3ConfigFontKeyword font contained +syn match i3ConfigFontNamespace /\w\+:/ contained contains=i3ConfigFontSeparator +syn match i3ConfigFontContent /-\?\w\+\(-\+\|\s\+\|,\)/ contained contains=i3ConfigFontNamespace,i3ConfigFontSeparator,i3ConfigFontKeyword +syn match i3ConfigFontSize /\s\=\d\+\(px\)\?\s\?$/ contained +syn match i3ConfigFont /^\s*font\s\+.*$/ contains=i3ConfigFontContent,i3ConfigFontSeparator,i3ConfigFontSize,i3ConfigFontNamespace +syn match i3ConfigFont /^\s*font\s\+.*\(\\\_.*\)\?$/ contains=i3ConfigFontContent,i3ConfigFontSeparator,i3ConfigFontSize,i3ConfigFontNamespace +syn match i3ConfigFont /^\s*font\s\+.*\(\\\_.*\)\?[^\\]\+$/ contains=i3ConfigFontContent,i3ConfigFontSeparator,i3ConfigFontSize,i3ConfigFontNamespace +syn match i3ConfigFont /^\s*font\s\+\(\(.*\\\_.*\)\|\(.*[^\\]\+$\)\)/ contains=i3ConfigFontContent,i3ConfigFontSeparator,i3ConfigFontSize,i3ConfigFontNamespace + +" variables +syn match i3ConfigString /\(['"]\)\(.\{-}\)\1/ contained +syn match i3ConfigColor /#\w\{6}/ contained +syn match i3ConfigVariableModifier /+/ contained +syn match i3ConfigVariableAndModifier /+\w\+/ contained contains=i3ConfigVariableModifier +syn match i3ConfigVariable /\$\w\+\(\(-\w\+\)\+\)\?\(\s\|+\)\?/ contains=i3ConfigVariableModifier,i3ConfigVariableAndModifier +syn keyword i3ConfigInitializeKeyword set contained +syn match i3ConfigInitialize /^\s*set\s\+.*$/ contains=i3ConfigVariable,i3ConfigInitializeKeyword,i3ConfigColor,i3ConfigString + +" Gaps +syn keyword i3ConfigGapStyleKeyword inner outer horizontal vertical top right bottom left current all set plus minus toggle up down contained +syn match i3ConfigGapStyle /^\s*\(gaps\)\s\+\(inner\|outer\|horizontal\|vertical\|left\|top\|right\|bottom\)\(\s\+\(current\|all\)\)\?\(\s\+\(set\|plus\|minus\|toggle\)\)\?\(\s\+\(-\?\d\+\|\$.*\)\)$/ contains=i3ConfigGapStyleKeyword,i3ConfigNumber,i3ConfigVariable +syn keyword i3ConfigSmartGapKeyword on inverse_outer contained +syn match i3ConfigSmartGap /^\s*smart_gaps\s\+\(on\|inverse_outer\)\s\?$/ contains=i3ConfigSmartGapKeyword +syn keyword i3ConfigSmartBorderKeyword on no_gaps contained +syn match i3ConfigSmartBorder /^\s*smart_borders\s\+\(on\|no_gaps\)\s\?$/ contains=i3ConfigSmartBorderKeyword + +" Keyboard bindings +syn keyword i3ConfigAction toggle fullscreen restart key import kill shrink grow contained +syn keyword i3ConfigAction focus move grow height width split layout resize restore reload mute unmute exit mode workspace container to contained +syn match i3ConfigModifier /\w\++\w\+\(\(+\w\+\)\+\)\?/ contained contains=i3ConfigVariableModifier +syn match i3ConfigNumber /\s\d\+/ contained +syn match i3ConfigUnit /\sp\(pt\|x\)/ contained +syn match i3ConfigUnitOr /\sor/ contained +syn keyword i3ConfigBindKeyword bindsym bindcode exec gaps border contained +syn match i3ConfigBindArgument /--\w\+\(\(-\w\+\)\+\)\?\s/ contained +syn match i3ConfigBind /^\s*\(bindsym\|bindcode\)\s\+.*$/ contains=i3ConfigVariable,i3ConfigBindKeyword,i3ConfigVariableAndModifier,i3ConfigNumber,i3ConfigUnit,i3ConfigUnitOr,i3ConfigBindArgument,i3ConfigModifier,i3ConfigAction,i3ConfigString,i3ConfigGapStyleKeyword,i3ConfigBorderStyleKeyword + +" Floating +syn keyword i3ConfigSizeSpecial x contained +syn match i3ConfigNegativeSize /-/ contained +syn match i3ConfigSize /-\?\d\+\s\?x\s\?-\?\d\+/ contained contains=i3ConfigSizeSpecial,i3ConfigNumber,i3ConfigNegativeSize +syn match i3ConfigFloating /^\s*floating_modifier\s\+\$\w\+\d\?/ contains=i3ConfigVariable +syn match i3ConfigFloating /^\s*floating_\(maximum\|minimum\)_size\s\+-\?\d\+\s\?x\s\?-\?\d\+/ contains=i3ConfigSize + +" Orientation +syn keyword i3ConfigOrientationKeyword vertical horizontal auto contained +syn match i3ConfigOrientation /^\s*default_orientation\s\+\(vertical\|horizontal\|auto\)\s\?$/ contains=i3ConfigOrientationKeyword + +" Layout +syn keyword i3ConfigLayoutKeyword default stacking tabbed contained +syn match i3ConfigLayout /^\s*workspace_layout\s\+\(default\|stacking\|tabbed\)\s\?$/ contains=i3ConfigLayoutKeyword + +" Border style +syn keyword i3ConfigBorderStyleKeyword none normal pixel contained +syn match i3ConfigBorderStyle /^\s*\(new_window\|new_float\|default_border\|default_floating_border\)\s\+\(none\|\(normal\|pixel\)\(\s\+\d\+\)\?\(\s\+\$\w\+\(\(-\w\+\)\+\)\?\(\s\|+\)\?\)\?\)\s\?$/ contains=i3ConfigBorderStyleKeyword,i3ConfigNumber,i3ConfigVariable + +" Hide borders and edges +syn keyword i3ConfigEdgeKeyword none vertical horizontal both smart smart_no_gaps contained +syn match i3ConfigEdge /^\s*hide_edge_borders\s\+\(none\|vertical\|horizontal\|both\|smart\|smart_no_gaps\)\s\?$/ contains=i3ConfigEdgeKeyword + +" Arbitrary commands for specific windows (for_window) +syn keyword i3ConfigCommandKeyword for_window contained +syn region i3ConfigWindowStringSpecial start=+"+ skip=+\\"+ end=+"+ contained contains=i3ConfigString +syn region i3ConfigWindowCommandSpecial start="\[" end="\]" contained contains=i3ConfigWindowStringSpacial,i3ConfigString +syn match i3ConfigArbitraryCommand /^\s*for_window\s\+.*$/ contains=i3ConfigWindowCommandSpecial,i3ConfigCommandKeyword,i3ConfigBorderStyleKeyword,i3ConfigLayoutKeyword,i3ConfigOrientationKeyword,Size,i3ConfigNumber + +" Disable focus open opening +syn keyword i3ConfigNoFocusKeyword no_focus contained +syn match i3ConfigDisableFocus /^\s*no_focus\s\+.*$/ contains=i3ConfigWindowCommandSpecial,i3ConfigNoFocusKeyword + +" Move client to specific workspace automatically +syn keyword i3ConfigAssignKeyword assign contained +syn match i3ConfigAssignSpecial /→/ contained +syn match i3ConfigAssign /^\s*assign\s\+.*$/ contains=i3ConfigAssignKeyword,i3ConfigWindowCommandSpecial,i3ConfigAssignSpecial + +" X resources +syn keyword i3ConfigResourceKeyword set_from_resource contained +syn match i3ConfigResource /^\s*set_from_resource\s\+.*$/ contains=i3ConfigResourceKeyword,i3ConfigWindowCommandSpecial,i3ConfigColor,i3ConfigVariable + +" Auto start applications +syn keyword i3ConfigExecKeyword exec exec_always contained +syn match i3ConfigNoStartupId /--no-startup-id/ contained " We are not using i3ConfigBindArgument as only no-startup-id is supported here +syn match i3ConfigExec /^\s*exec\(_always\)\?\s\+.*$/ contains=i3ConfigExecKeyword,i3ConfigNoStartupId,i3ConfigString + +" Automatically putting workspaces on specific screens +syn keyword i3ConfigWorkspaceKeyword workspace contained +syn keyword i3ConfigOutput output contained +syn match i3ConfigWorkspace /^\s*workspace\s\+.*$/ contains=i3ConfigWorkspaceKeyword,i3ConfigNumber,i3ConfigString,i3ConfigOutput + +" Changing colors +syn keyword i3ConfigClientColorKeyword client focused focused_inactive unfocused urgent placeholder background contained +syn match i3ConfigClientColor /^\s*client.\w\+\s\+.*$/ contains=i3ConfigClientColorKeyword,i3ConfigColor,i3ConfigVariable + +syn keyword i3ConfigTitleAlignKeyword left center right contained +syn match i3ConfigTitleAlign /^\s*title_align\s\+.*$/ contains=i3ConfigTitleAlignKeyword + +" Interprocess communication +syn match i3ConfigInterprocessKeyword /ipc-socket/ contained +syn match i3ConfigInterprocess /^\s*ipc-socket\s\+.*$/ contains=i3ConfigInterprocessKeyword + +" Mouse warping +syn keyword i3ConfigMouseWarpingKeyword mouse_warping contained +syn keyword i3ConfigMouseWarpingType output none contained +syn match i3ConfigMouseWarping /^\s*mouse_warping\s\+\(output\|none\)\s\?$/ contains=i3ConfigMouseWarpingKeyword,i3ConfigMouseWarpingType + +" Focus follows mouse +syn keyword i3ConfigFocusFollowsMouseKeyword focus_follows_mouse contained +syn keyword i3ConfigFocusFollowsMouseType yes no contained +syn match i3ConfigFocusFollowsMouse /^\s*focus_follows_mouse\s\+\(yes\|no\)\s\?$/ contains=i3ConfigFocusFollowsMouseKeyword,i3ConfigFocusFollowsMouseType + +" Popups during fullscreen mode +syn keyword i3ConfigPopupOnFullscreenKeyword popup_during_fullscreen contained +syn keyword i3ConfigPopuponFullscreenType smart ignore leave_fullscreen contained +syn match i3ConfigPopupOnFullscreen /^\s*popup_during_fullscreen\s\+\w\+\s\?$/ contains=i3ConfigPopupOnFullscreenKeyword,i3ConfigPopupOnFullscreenType + +" Focus wrapping +syn keyword i3ConfigFocusWrappingKeyword force_focus_wrapping focus_wrapping contained +syn keyword i3ConfigFocusWrappingType yes no contained +syn match i3ConfigFocusWrapping /^\s*\(force_\)\?focus_wrapping\s\+\(yes\|no\)\s\?$/ contains=i3ConfigFocusWrappingType,i3ConfigFocusWrappingKeyword + +" Forcing Xinerama +syn keyword i3ConfigForceXineramaKeyword force_xinerama contained +syn match i3ConfigForceXinerama /^\s*force_xinerama\s\+\(yes\|no\)\s\?$/ contains=i3ConfigFocusWrappingType,i3ConfigForceXineramaKeyword + +" Automatic back-and-forth when switching to the current workspace +syn keyword i3ConfigAutomaticSwitchKeyword workspace_auto_back_and_forth contained +syn match i3ConfigAutomaticSwitch /^\s*workspace_auto_back_and_forth\s\+\(yes\|no\)\s\?$/ contains=i3ConfigFocusWrappingType,i3ConfigAutomaticSwitchKeyword + +" Delay urgency hint +syn keyword i3ConfigTimeUnit ms contained +syn keyword i3ConfigDelayUrgencyKeyword force_display_urgency_hint contained +syn match i3ConfigDelayUrgency /^\s*force_display_urgency_hint\s\+\d\+\s\+ms\s\?$/ contains=i3ConfigFocusWrappingType,i3ConfigDelayUrgencyKeyword,i3ConfigNumber,i3ConfigTimeUnit + +" Focus on window activation +syn keyword i3ConfigFocusOnActivationKeyword focus_on_window_activation contained +syn keyword i3ConfigFocusOnActivationType smart urgent focus none contained +syn match i3ConfigFocusOnActivation /^\s*focus_on_window_activation\s\+\(smart\|urgent\|focus\|none\)\s\?$/ contains=i3ConfigFocusOnActivationKeyword,i3ConfigFocusOnActivationType + +" Automatic back-and-forth when switching to the current workspace +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_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 +syn region i3ConfigLineCont start=/^.*\\$/ end=/^.*$/ contains=i3ConfigBlockKeyword,i3ConfigString,i3ConfigBind,i3ConfigComment,i3ConfigFont,i3ConfigFocusWrappingType,i3ConfigColor,i3ConfigVariable transparent keepend extend + +" Define the highlighting. +hi def link i3ConfigError Error +hi def link i3ConfigTodo Todo +hi def link i3ConfigComment Comment +hi def link i3ConfigFontContent Type +hi def link i3ConfigFocusOnActivationType Type +hi def link i3ConfigPopupOnFullscreenType Type +hi def link i3ConfigOrientationKeyword Type +hi def link i3ConfigMouseWarpingType Type +hi def link i3ConfigFocusFollowsMouseType Type +hi def link i3ConfigGapStyleKeyword Type +hi def link i3ConfigTitleAlignKeyword Type +hi def link i3ConfigSmartGapKeyword Type +hi def link i3ConfigSmartBorderKeyword Type +hi def link i3ConfigLayoutKeyword Type +hi def link i3ConfigBorderStyleKeyword Type +hi def link i3ConfigEdgeKeyword Type +hi def link i3ConfigAction Type +hi def link i3ConfigCommand Type +hi def link i3ConfigOutput Type +hi def link i3ConfigWindowCommandSpecial Type +hi def link i3ConfigFocusWrappingType Type +hi def link i3ConfigUnitOr Type +hi def link i3ConfigFontSize Constant +hi def link i3ConfigColor Constant +hi def link i3ConfigNumber Constant +hi def link i3ConfigUnit Constant +hi def link i3ConfigVariableAndModifier Constant +hi def link i3ConfigTimeUnit Constant +hi def link i3ConfigModifier Constant +hi def link i3ConfigString Constant +hi def link i3ConfigNegativeSize Constant +hi def link i3ConfigFontSeparator Special +hi def link i3ConfigVariableModifier Special +hi def link i3ConfigSizeSpecial Special +hi def link i3ConfigWindowSpecial Special +hi def link i3ConfigAssignSpecial Special +hi def link i3ConfigFontNamespace PreProc +hi def link i3ConfigBindArgument PreProc +hi def link i3ConfigNoStartupId PreProc +hi def link i3ConfigFontKeyword Identifier +hi def link i3ConfigBindKeyword Identifier +hi def link i3ConfigOrientation Identifier +hi def link i3ConfigGapStyle Identifier +hi def link i3ConfigTitleAlign Identifier +hi def link i3ConfigSmartGap Identifier +hi def link i3ConfigSmartBorder Identifier +hi def link i3ConfigLayout Identifier +hi def link i3ConfigBorderStyle Identifier +hi def link i3ConfigEdge Identifier +hi def link i3ConfigFloating Identifier +hi def link i3ConfigCommandKeyword Identifier +hi def link i3ConfigNoFocusKeyword Identifier +hi def link i3ConfigInitializeKeyword Identifier +hi def link i3ConfigAssignKeyword Identifier +hi def link i3ConfigResourceKeyword Identifier +hi def link i3ConfigExecKeyword Identifier +hi def link i3ConfigWorkspaceKeyword Identifier +hi def link i3ConfigClientColorKeyword Identifier +hi def link i3ConfigInterprocessKeyword Identifier +hi def link i3ConfigMouseWarpingKeyword Identifier +hi def link i3ConfigFocusFollowsMouseKeyword Identifier +hi def link i3ConfigPopupOnFullscreenKeyword Identifier +hi def link i3ConfigFocusWrappingKeyword Identifier +hi def link i3ConfigForceXineramaKeyword Identifier +hi def link i3ConfigAutomaticSwitchKeyword Identifier +hi def link i3ConfigDelayUrgencyKeyword Identifier +hi def link i3ConfigFocusOnActivationKeyword Identifier +hi def link i3ConfigDrawingMarksKeyword Identifier +hi def link i3ConfigBlockKeyword Identifier +hi def link i3ConfigVariable Statement +hi def link i3ConfigArbitraryCommand Type + +let b:current_syntax = "i3config" diff --git a/runtime/syntax/indent.vim b/runtime/syntax/indent.vim index ddeae67e0d..b2a1a0c85f 100644 --- a/runtime/syntax/indent.vim +++ b/runtime/syntax/indent.vim @@ -1,7 +1,8 @@ " Vim syntax file -" Language: indent(1) configuration file -" Previous Maintainer: Nikolai Weibull <now@bitwi.se> -" Latest Revision: 2010-01-23 +" Language: indent(1) configuration file +" Maintainer: Doug Kearns <dougkearns@gmail.com> +" Previous Maintainer: Nikolai Weibull <now@bitwi.se> +" Last Change: 2021 Nov 17 " indent_is_bsd: If exists, will change somewhat to match BSD implementation " " TODO: is the deny-all (a la lilo.vim nice or no?)... @@ -27,7 +28,7 @@ syn region indentComment start='//' skip='\\$' end='$' \ contains=indentTodo,@Spell if !exists("indent_is_bsd") - syn match indentOptions '-i\|--indentation-level\|-il\|--indent-level' + syn match indentOptions '-i\|--indent-level\|-il\|--indent-label' \ nextgroup=indentNumber skipwhite skipempty endif syn match indentOptions '-\%(bli\|c\%([bl]i\|[dip]\)\=\|di\=\|ip\=\|lc\=\|pp\=i\|sbi\|ts\|-\%(brace-indent\|comment-indentation\|case-brace-indentation\|declaration-comment-column\|continuation-indentation\|case-indentation\|else-endif-column\|line-comments-indentation\|declaration-indentation\|indent-level\|parameter-indentation\|line-length\|comment-line-length\|paren-indentation\|preprocessor-indentation\|struct-brace-indentation\|tab-size\)\)' diff --git a/runtime/syntax/liquid.vim b/runtime/syntax/liquid.vim index 295a91775e..966b60f6f8 100644 --- a/runtime/syntax/liquid.vim +++ b/runtime/syntax/liquid.vim @@ -2,7 +2,7 @@ " Language: Liquid " Maintainer: Tim Pope <vimNOSPAM@tpope.org> " Filenames: *.liquid -" Last Change: 2013 May 30 +" Last Change: 2022 Mar 15 if exists('b:current_syntax') finish @@ -68,10 +68,10 @@ if !exists('s:subtype') unlet s:subtype endif -syn region liquidStatement matchgroup=liquidDelimiter start="{%" end="%}" contains=@liquidStatement containedin=ALLBUT,@liquidExempt keepend -syn region liquidExpression matchgroup=liquidDelimiter start="{{" end="}}" contains=@liquidExpression containedin=ALLBUT,@liquidExempt keepend -syn region liquidComment matchgroup=liquidDelimiter start="{%\s*comment\s*%}" end="{%\s*endcomment\s*%}" contains=liquidTodo,@Spell containedin=ALLBUT,@liquidExempt keepend -syn region liquidRaw matchgroup=liquidDelimiter start="{%\s*raw\s*%}" end="{%\s*endraw\s*%}" contains=TOP,@liquidExempt containedin=ALLBUT,@liquidExempt keepend +syn region liquidStatement matchgroup=liquidDelimiter start="{%-\=" end="-\=%}" contains=@liquidStatement containedin=ALLBUT,@liquidExempt keepend +syn region liquidExpression matchgroup=liquidDelimiter start="{{-\=" end="-\=}}" contains=@liquidExpression containedin=ALLBUT,@liquidExempt keepend +syn region liquidComment matchgroup=liquidDelimiter start="{%-\=\s*comment\s*-\=%}" end="{%-\=\s*endcomment\s*-\=%}" contains=liquidTodo,@Spell containedin=ALLBUT,@liquidExempt keepend +syn region liquidRaw matchgroup=liquidDelimiter start="{%-\=\s*raw\s*-\=%}" end="{%-\=\s*endraw\s*-\=%}" contains=TOP,@liquidExempt containedin=ALLBUT,@liquidExempt keepend syn cluster liquidExempt contains=liquidStatement,liquidExpression,liquidComment,liquidRaw,@liquidStatement,liquidYamlHead syn cluster liquidStatement contains=liquidConditional,liquidRepeat,liquidKeyword,@liquidExpression @@ -79,11 +79,11 @@ syn cluster liquidExpression contains=liquidOperator,liquidString,liquidNumber,l syn keyword liquidKeyword highlight nextgroup=liquidTypeHighlight skipwhite contained syn keyword liquidKeyword endhighlight contained -syn region liquidHighlight start="{%\s*highlight\s\+\w\+\s*%}" end="{% endhighlight %}" keepend +syn region liquidHighlight start="{%-\=\s*highlight\s\+\w\+\s*-\=%}" end="{%-\= endhighlight -\=%}" keepend for s:type in g:liquid_highlight_types exe 'syn match liquidTypeHighlight "\<'.matchstr(s:type,'[^=]*').'\>" contained' - exe 'syn region liquidHighlight'.substitute(matchstr(s:type,'[^=]*$'),'\..*','','').' start="{%\s*highlight\s\+'.matchstr(s:type,'[^=]*').'\s*%}" end="{% endhighlight %}" keepend contains=@liquidHighlight'.substitute(matchstr(s:type,'[^=]*$'),'\.','','g') + exe 'syn region liquidHighlight'.substitute(matchstr(s:type,'[^=]*$'),'\..*','','').' start="{%-\=\s*highlight\s\+'.matchstr(s:type,'[^=]*').'\s*-\=%}" end="{%-\= endhighlight -\=%}" keepend contains=@liquidHighlight'.substitute(matchstr(s:type,'[^=]*$'),'\.','','g') endfor unlet! s:type @@ -92,18 +92,18 @@ syn region liquidString matchgroup=liquidQuote start=+'+ end=+'+ contained syn match liquidNumber "-\=\<\d\+\>" contained syn match liquidFloat "-\=\<\d\+\>\.\.\@!\%(\d\+\>\)\=" contained syn keyword liquidBoolean true false contained -syn keyword liquidNull null nil contained +syn keyword liquidNull null nil blank contained syn match liquidEmpty "\<empty\>" contained syn keyword liquidOperator and or not contained syn match liquidPipe '|' contained skipwhite nextgroup=liquidFilter -syn keyword liquidFilter date capitalize downcase upcase first last join sort size strip_html strip_newlines newline_to_br replace replace_first remove remove_first truncate truncatewords prepend append minus plus times divided_by contained +syn keyword liquidFilter date capitalize downcase upcase escape escape_once first last join sort size where uniq strip_html strip_newlines newline_to_br replace replace_first remove remove_first slice split strip truncate truncatewords prepend append url_encode url_decode abs at_most at_least ceil divided_by floor minus plus round times modulo contained syn keyword liquidConditional if elsif else endif unless endunless case when endcase ifchanged endifchanged contained -syn keyword liquidRepeat for endfor tablerow endtablerow in contained -syn match liquidRepeat "\%({%\s*\)\@<=empty\>" contained -syn keyword liquidKeyword assign cycle include with contained +syn keyword liquidRepeat for endfor tablerow endtablerow in break continue limit offset reversed contained +syn match liquidRepeat "\%({%-\=\s*\)\@<=empty\>" contained +syn keyword liquidKeyword assign capture endcapture increasement decreasement cycle include with render contained syn keyword liquidForloop forloop nextgroup=liquidForloopDot contained syn match liquidForloopDot "\." nextgroup=liquidForloopAttribute contained diff --git a/runtime/syntax/lua.vim b/runtime/syntax/lua.vim index f313c14e7a..b398e2e5c6 100644 --- a/runtime/syntax/lua.vim +++ b/runtime/syntax/lua.vim @@ -2,7 +2,7 @@ " Language: Lua 4.0, Lua 5.0, Lua 5.1 and Lua 5.2 " Maintainer: Marcus Aurelius Farias <masserahguard-lua 'at' yahoo com> " First Author: Carlos Augusto Teixeira Mendes <cmendes 'at' inf puc-rio br> -" Last Change: 2012 Aug 12 +" Last Change: 2022 Mar 31 " Options: lua_version = 4 or 5 " lua_subversion = 0 (4.0, 5.0) or 1 (5.1) or 2 (5.2) " default 5.2 @@ -319,6 +319,15 @@ elseif lua_version == 5 syn match luaFunc /\<debug\.upvalueid\>/ syn match luaFunc /\<debug\.upvaluejoin\>/ endif + if lua_subversion >= 3 + "https://www.lua.org/manual/5.3/manual.html#6.5 + syn match luaFunc /\<utf8\.char\>/ + syn match luaFunc /\<utf8\.charpattern\>/ + syn match luaFunc /\<utf8\.codes\>/ + syn match luaFunc /\<utf8\.codepoint\>/ + syn match luaFunc /\<utf8\.len\>/ + syn match luaFunc /\<utf8\.offset\>/ + endif endif " Define the default highlighting. diff --git a/runtime/syntax/neomuttrc.vim b/runtime/syntax/neomuttrc.vim index bd73de49ea..421b11ffa3 100644 --- a/runtime/syntax/neomuttrc.vim +++ b/runtime/syntax/neomuttrc.vim @@ -2,10 +2,10 @@ " Language: NeoMutt setup files " Maintainer: Richard Russon <rich@flatcap.org> " Previous Maintainer: Guillaume Brogi <gui-gui@netcourrier.com> -" Last Change: 2020-06-21 +" Last Change: 2022-04-08 " Original version based on syntax/muttrc.vim -" This file covers NeoMutt 2020-06-19 +" This file covers NeoMutt 2022-04-08 " quit when a syntax file was already loaded if exists("b:current_syntax") @@ -115,6 +115,8 @@ syntax region muttrcIndexFormatStr contained skipwhite keepend start=+"+ sk syntax region muttrcIndexFormatStr contained skipwhite keepend start=+'+ skip=+\\'+ end=+'+ contains=muttrcIndexFormatEscapes,muttrcIndexFormatConditionals,muttrcFormatErrors,muttrcTimeEscapes nextgroup=muttrcVPrefix,muttrcVarBool,muttrcVarQuad,muttrcVarNum,muttrcVarStr syntax region muttrcMixFormatStr contained skipwhite keepend start=+"+ skip=+\\"+ end=+"+ contains=muttrcMixFormatEscapes,muttrcMixFormatConditionals,muttrcFormatErrors nextgroup=muttrcVPrefix,muttrcVarBool,muttrcVarQuad,muttrcVarNum,muttrcVarStr syntax region muttrcMixFormatStr contained skipwhite keepend start=+'+ skip=+\\'+ end=+'+ contains=muttrcMixFormatEscapes,muttrcMixFormatConditionals,muttrcFormatErrors nextgroup=muttrcVPrefix,muttrcVarBool,muttrcVarQuad,muttrcVarNum,muttrcVarStr +syntax region muttrcPatternFormatStr contained skipwhite keepend start=+"+ skip=+\\"+ end=+"+ contains=muttrcPatternFormatEscapes,muttrcPatternFormatConditionals,muttrcFormatErrors nextgroup=muttrcVPrefix,muttrcVarBool,muttrcVarQuad,muttrcVarNum,muttrcVarStr +syntax region muttrcPatternFormatStr contained skipwhite keepend start=+'+ skip=+\\'+ end=+'+ contains=muttrcPatternFormatEscapes,muttrcPatternFormatConditionals,muttrcFormatErrors nextgroup=muttrcVPrefix,muttrcVarBool,muttrcVarQuad,muttrcVarNum,muttrcVarStr syntax region muttrcPGPCmdFormatStr contained skipwhite keepend start=+"+ skip=+\\"+ end=+"+ contains=muttrcPGPCmdFormatEscapes,muttrcPGPCmdFormatConditionals,muttrcVariable,muttrcFormatErrors nextgroup=muttrcVPrefix,muttrcVarBool,muttrcVarQuad,muttrcVarNum,muttrcVarStr syntax region muttrcPGPCmdFormatStr contained skipwhite keepend start=+'+ skip=+\\'+ end=+'+ contains=muttrcPGPCmdFormatEscapes,muttrcPGPCmdFormatConditionals,muttrcVariable,muttrcFormatErrors nextgroup=muttrcVPrefix,muttrcVarBool,muttrcVarQuad,muttrcVarNum,muttrcVarStr syntax region muttrcPGPFormatStr contained skipwhite keepend start=+"+ skip=+\\"+ end=+"+ contains=muttrcPGPFormatEscapes,muttrcPGPFormatConditionals,muttrcFormatErrors,muttrcPGPTimeEscapes nextgroup=muttrcVPrefix,muttrcVarBool,muttrcVarQuad,muttrcVarNum,muttrcVarStr @@ -144,35 +146,37 @@ function! s:escapesConditionals(baseName, sequence, padding, conditional) endif endfunction -" CHECKED 2020-06-21 -" Ref: alias_format_str() in alias/dlgalias.c +" CHECKED 2022-04-08 +" Ref: alias_format_str() in alias/dlg_alias.c call s:escapesConditionals('AliasFormat', '[acfnrt]', 1, 0) -" Ref: attach_format_str() in recvattach.c +" Ref: attach_format_str() in attach/dlg_attach.c call s:escapesConditionals('AttachFormat', '[CcDdeFfIMmnQsTtuX]', 1, 1) -" Ref: compose_format_str() in compose.c +" Ref: compose_format_str() in compose/cbar.c call s:escapesConditionals('ComposeFormat', '[ahlv]', 1, 1) -" Ref: folder_format_str() in browser.c +" Ref: folder_format_str() in browser/browser.c call s:escapesConditionals('FolderFormat', '[CDdFfgilmNnstu]', 1, 0) -" Ref: group_index_format_str() in browser.c +" Ref: group_index_format_str() in nntp/browse.c call s:escapesConditionals('GroupIndexFormat', '[CdfMNns]', 1, 1) " Ref: index_format_str() in hdrline.c call s:escapesConditionals('IndexFormat', '[AaBbCDdEefgHIiJKLlMmNnOPqRrSsTtuvWXxYyZ(<[{]\|@\i\+@\|G[a-zA-Z]\+\|Fp\=\|z[cst]\|cr\=', 1, 1) " Ref: mix_format_str() in remailer.c call s:escapesConditionals('MixFormat', '[acns]', 1, 0) +" Ref: pattern_format_str() in pattern/dlg_pattern.c +call s:escapesConditionals('PatternFormat', '[den]', 1, 0) " Ref: pgp_command_format_str() in ncrypt/pgpinvoke.c call s:escapesConditionals('PGPCmdFormat', '[afprs]', 0, 1) -" Ref: crypt_format_str() in ncrypt/crypt_gpgme.c -" Ref: pgp_entry_format_str() in ncrypt/pgpkey.c +" Ref: crypt_format_str() in ncrypt/dlg_gpgme.c +" Ref: pgp_entry_format_str() in ncrypt/dlg_pgp.c " Note: crypt_format_str() supports 'p', but pgp_entry_fmt() does not call s:escapesConditionals('PGPFormat', '[AaCcFfKkLlnptu[]', 0, 0) -" Ref: query_format_str() in alias/dlgquery.c +" Ref: query_format_str() in alias/dlg_query.c call s:escapesConditionals('QueryFormat', '[acent]', 1, 1) -" Ref: sidebar_format_str() in sidebar.c +" Ref: sidebar_format_str() in sidebar/window.c call s:escapesConditionals('SidebarFormat', '[!BDdFLNnorStZ]', 1, 1) " Ref: smime_command_format_str() in ncrypt/smime.c call s:escapesConditionals('SmimeFormat', '[aCcdfiks]', 0, 1) " Ref: status_format_str() in status.c -call s:escapesConditionals('StatusFormat', '[bDdFfhLlMmnoPpRrSstuVv]', 1, 1) +call s:escapesConditionals('StatusFormat', '[bDdFfhLlMmnoPpRrSsTtuVv]', 1, 1) syntax region muttrcPGPTimeEscapes contained start=+%\[+ end=+\]+ contains=muttrcStrftimeEscapes syntax region muttrcTimeEscapes contained start=+%(+ end=+)+ contains=muttrcStrftimeEscapes @@ -187,6 +191,7 @@ syntax match muttrcVarEqualsFolderFmt contained skipwhite "=" nextgroup=mutt syntax match muttrcVarEqualsGrpIdxFmt contained skipwhite "=" nextgroup=muttrcGroupIndexFormatStr syntax match muttrcVarEqualsIdxFmt contained skipwhite "=" nextgroup=muttrcIndexFormatStr syntax match muttrcVarEqualsMixFmt contained skipwhite "=" nextgroup=muttrcMixFormatStr +syntax match muttrcVarEqualsPatternFmt contained skipwhite "=" nextgroup=muttrcPatternFormatStr syntax match muttrcVarEqualsPGPCmdFmt contained skipwhite "=" nextgroup=muttrcPGPCmdFormatStr syntax match muttrcVarEqualsPGPFmt contained skipwhite "=" nextgroup=muttrcPGPFormatStr syntax match muttrcVarEqualsQueryFmt contained skipwhite "=" nextgroup=muttrcQueryFormatStr @@ -197,9 +202,9 @@ syntax match muttrcVarEqualsStrftimeFmt contained skipwhite "=" nextgroup=mutt syntax match muttrcVPrefix contained /[?&]/ nextgroup=muttrcVarBool,muttrcVarQuad,muttrcVarNum,muttrcVarStr -" CHECKED 2020-06-21 -" List of the different screens in mutt (see Menus in keymap.c) -syntax keyword muttrcMenu contained alias attach browser compose editor generic index key_select_pgp key_select_smime mix pager pgp postpone query smime +" CHECKED 2022-04-08 +" List of the different screens in NeoMutt (see MenuNames in menu/type.c) +syntax keyword muttrcMenu contained alias attach autocrypt browser compose editor generic index key_select_pgp key_select_smime mix pager pgp postpone query smime syntax match muttrcMenuList "\S\+" contained contains=muttrcMenu syntax match muttrcMenuCommas /,/ contained @@ -234,12 +239,12 @@ syntax match muttrcEscapedVariable contained "\\\$[a-zA-Z_-]\+" syntax match muttrcBadAction contained "[^<>]\+" contains=muttrcEmail syntax match muttrcAction contained "<[^>]\{-}>" contains=muttrcBadAction,muttrcFunction,muttrcKeyName -" CHECKED 2020-06-21 -" First, functions that take regular expressions: +" CHECKED 2022-04-08 +" First, hooks that take regular expressions: syntax match muttrcRXHookNot contained /!\s*/ skipwhite nextgroup=muttrcRXHookString,muttrcRXHookStringNL syntax match muttrcRXHooks /\<\%(account\|append\|close\|crypt\|folder\|mbox\|open\|pgp\)-hook\>/ skipwhite nextgroup=muttrcRXHookNot,muttrcRXHookString,muttrcRXHookStringNL -" Now, functions that take patterns +" Now, hooks that take patterns syntax match muttrcPatHookNot contained /!\s*/ skipwhite nextgroup=muttrcPattern syntax match muttrcPatHooks /\<\%(charset\|iconv\|index-format\)-hook\>/ skipwhite nextgroup=muttrcPatHookNot,muttrcPattern syntax match muttrcPatHooks /\<\%(message\|reply\|send\|send2\|save\|fcc\|fcc-save\)-hook\>/ skipwhite nextgroup=muttrcPatHookNot,muttrcOptPattern @@ -295,10 +300,10 @@ syntax match muttrcAliasNL contained /\s*\\$/ skipwhite skipnl nextgroup=muttrc syntax match muttrcUnAliasKey contained "\s*\w\+\s*" skipwhite nextgroup=muttrcUnAliasKey,muttrcUnAliasNL syntax match muttrcUnAliasNL contained /\s*\\$/ skipwhite skipnl nextgroup=muttrcUnAliasKey,muttrcUnAliasNL -" CHECKED 2020-06-21 -" List of letters in Flags in pattern.c +" CHECKED 2022-04-08 +" List of letters in Flags in pattern/flags.c " Parameter: none -syntax match muttrcSimplePat contained "!\?\^\?[~][ADEFGgklNOPpQRSTuUvV#$=]" +syntax match muttrcSimplePat contained "!\?\^\?[~][ADEFGgklNOPpQRSTUuVv#$=]" " Parameter: range syntax match muttrcSimplePat contained "!\?\^\?[~][mnXz]\s*\%([<>-][0-9]\+[kM]\?\|[0-9]\+[kM]\?[-]\%([0-9]\+[kM]\?\)\?\)" " Parameter: date @@ -306,7 +311,7 @@ syntax match muttrcSimplePat contained "!\?\^\?[~][dr]\s*\%(\%(-\?[0-9]\{1,2}\%( " Parameter: regex syntax match muttrcSimplePat contained "!\?\^\?[~][BbCcefHhIiLMstwxYy]\s*" nextgroup=muttrcSimplePatRXContainer " Parameter: pattern -syntax match muttrcSimplePat contained "!\?\^\?[%][bBcCefhHiLstxy]\s*" nextgroup=muttrcSimplePatString +syntax match muttrcSimplePat contained "!\?\^\?[%][BbCcefHhiLstxy]\s*" nextgroup=muttrcSimplePatString " Parameter: pattern syntax match muttrcSimplePat contained "!\?\^\?[=][bcCefhHiLstxy]\s*" nextgroup=muttrcSimplePatString syntax region muttrcSimplePat contained keepend start=+!\?\^\?[~](+ end=+)+ contains=muttrcSimplePat @@ -369,8 +374,8 @@ syntax keyword muttrcMonoAttrib contained bold none normal reverse standout unde syntax keyword muttrcMono contained mono skipwhite nextgroup=muttrcColorField,muttrcColorCompose syntax match muttrcMonoLine "^\s*mono\s\+\S\+" skipwhite nextgroup=muttrcMonoAttrib contains=muttrcMono -" CHECKED 2020-06-21 -" List of fields in Fields in color.c +" CHECKED 2022-04-08 +" List of fields in ColorFields in color/commmand.c syntax keyword muttrcColorField skipwhite contained \ attachment attach_headers body bold error hdrdefault header index index_author \ index_collapsed index_date index_flags index_label index_number index_size index_subject @@ -383,8 +388,8 @@ syntax match muttrcColorField contained "\<quoted\d\=\>" syntax match muttrcColorCompose skipwhite contained /\s*compose\s*/ nextgroup=muttrcColorComposeField -" CHECKED 2020-06-21 -" List of fields in ComposeFields in color.c +" CHECKED 2022-04-08 +" List of fields in ComposeColorFields in color/command.c syntax keyword muttrcColorComposeField skipwhite contained \ header security_both security_encrypt security_none security_sign \ nextgroup=muttrcColorFG,muttrcColorFGNL @@ -411,20 +416,21 @@ function! s:boolQuadGen(type, vars, deprecated) endfunction -" CHECKED 2020-06-21 +" CHECKED 2022-04-08 " List of DT_BOOL in MuttVars in mutt_config.c call s:boolQuadGen('Bool', [ - \ 'abort_backspace', 'allow_8bit', 'allow_ansi', 'arrow_cursor', 'ascii_chars', 'askbcc', - \ 'askcc', 'ask_follow_up', 'ask_x_comment_to', 'attach_save_without_prompting', - \ 'attach_split', 'autocrypt', 'autocrypt_reply', 'autoedit', 'auto_subscribe', 'auto_tag', + \ 'abort_backspace', 'allow_8bit', 'allow_ansi', 'arrow_cursor', 'ascii_chars', 'ask_bcc', + \ 'ask_cc', 'ask_follow_up', 'ask_x_comment_to', 'attach_save_without_prompting', + \ 'attach_split', 'autocrypt', 'autocrypt_reply', 'auto_edit', 'auto_subscribe', 'auto_tag', \ 'beep', 'beep_new', 'bounce_delivered', 'braille_friendly', \ 'browser_abbreviate_mailboxes', 'change_folder_next', 'check_mbox_size', 'check_new', - \ 'collapse_all', 'collapse_flagged', 'collapse_unread', 'confirmappend', 'confirmcreate', - \ 'crypt_autoencrypt', 'crypt_autopgp', 'crypt_autosign', 'crypt_autosmime', - \ 'crypt_confirmhook', 'crypt_opportunistic_encrypt', + \ 'collapse_all', 'collapse_flagged', 'collapse_unread', 'compose_show_user_headers', + \ 'confirm_append', 'confirm_create', 'copy_decode_weed', 'count_alternatives', + \ 'crypt_auto_encrypt', 'crypt_auto_pgp', 'crypt_auto_sign', 'crypt_auto_smime', + \ 'crypt_confirm_hook', 'crypt_opportunistic_encrypt', \ 'crypt_opportunistic_encrypt_strong_keys', 'crypt_protected_headers_read', - \ 'crypt_protected_headers_save', 'crypt_protected_headers_write', 'crypt_replyencrypt', - \ 'crypt_replysign', 'crypt_replysignencrypted', 'crypt_timestamp', 'crypt_use_gpgme', + \ 'crypt_protected_headers_save', 'crypt_protected_headers_write', 'crypt_reply_encrypt', + \ 'crypt_reply_sign', 'crypt_reply_sign_encrypted', 'crypt_timestamp', 'crypt_use_gpgme', \ 'crypt_use_pka', 'delete_untag', 'digest_collapse', 'duplicate_threads', 'edit_headers', \ 'encode_from', 'fast_reply', 'fcc_before_send', 'fcc_clear', 'flag_safe', 'followup_to', \ 'force_name', 'forward_decode', 'forward_decrypt', 'forward_quote', 'forward_references', @@ -433,45 +439,52 @@ call s:boolQuadGen('Bool', [ \ 'history_remove_dups', 'honor_disposition', 'idn_decode', 'idn_encode', \ 'ignore_list_reply_to', 'imap_check_subscribed', 'imap_condstore', 'imap_deflate', \ 'imap_idle', 'imap_list_subscribed', 'imap_passive', 'imap_peek', 'imap_qresync', - \ 'imap_rfc5161', 'imap_servernoise', 'implicit_autoview', 'include_encrypted', - \ 'include_onlyfirst', 'keep_flagged', 'mailcap_sanitize', 'maildir_check_cur', - \ 'maildir_header_cache_verify', 'maildir_trash', 'mail_check_recent', 'mail_check_stats', - \ 'markers', 'mark_old', 'menu_move_off', 'menu_scroll', 'message_cache_clean', 'meta_key', - \ 'metoo', 'mh_purge', 'mime_forward_decode', 'mime_subject', 'mime_type_query_first', - \ 'narrow_tree', 'nm_record', 'nntp_listgroup', 'nntp_load_description', 'pager_stop', - \ 'pgp_autoinline', 'pgp_auto_decode', 'pgp_check_exit', 'pgp_check_gpg_decrypt_status_fd', - \ 'pgp_ignore_subkeys', 'pgp_long_ids', 'pgp_replyinline', 'pgp_retainable_sigs', + \ 'imap_rfc5161', 'imap_server_noise', 'implicit_autoview', 'include_encrypted', + \ 'include_only_first', 'keep_flagged', 'local_date_header', 'mailcap_sanitize', + \ 'maildir_check_cur', 'maildir_header_cache_verify', 'maildir_trash', 'mail_check_recent', + \ 'mail_check_stats', 'markers', 'mark_old', 'menu_move_off', 'menu_scroll', + \ 'message_cache_clean', 'meta_key', 'me_too', 'mh_purge', 'mime_forward_decode', + \ 'mime_type_query_first', 'narrow_tree', 'nm_query_window_enable', 'nm_record', + \ 'nntp_listgroup', 'nntp_load_description', 'pager_stop', 'pgp_auto_decode', + \ 'pgp_auto_inline', 'pgp_check_exit', 'pgp_check_gpg_decrypt_status_fd', + \ 'pgp_ignore_subkeys', 'pgp_long_ids', 'pgp_reply_inline', 'pgp_retainable_sigs', \ 'pgp_self_encrypt', 'pgp_show_unusable', 'pgp_strict_enc', 'pgp_use_gpg_agent', - \ 'pipe_decode', 'pipe_split', 'pop_auth_try_all', 'pop_last', 'postpone_encrypt', - \ 'print_decode', 'print_split', 'prompt_after', 'read_only', 'reflow_space_quotes', - \ 'reflow_text', 'reply_self', 'reply_with_xorig', 'resolve', 'resume_draft_files', - \ 'resume_edited_draft_files', 'reverse_alias', 'reverse_name', 'reverse_realname', - \ 'rfc2047_parameters', 'save_address', 'save_empty', 'save_name', 'save_unsubscribed', - \ 'score', 'show_new_news', 'show_only_unread', 'sidebar_folder_indent', - \ 'sidebar_new_mail_only', 'sidebar_next_new_wrap', 'sidebar_non_empty_mailbox_only', - \ 'sidebar_on_right', 'sidebar_short_path', 'sidebar_visible', 'sig_dashes', 'sig_on_top', - \ 'size_show_bytes', 'size_show_fractions', 'size_show_mb', 'size_units_on_left', - \ 'smart_wrap', 'smime_ask_cert_label', 'smime_decrypt_use_default_key', 'smime_is_default', - \ 'smime_self_encrypt', 'sort_re', 'ssl_force_tls', 'ssl_usesystemcerts', 'ssl_use_sslv2', - \ 'ssl_use_sslv3', 'ssl_use_tlsv1', 'ssl_use_tlsv1_1', 'ssl_use_tlsv1_2', 'ssl_use_tlsv1_3', + \ 'pipe_decode', 'pipe_decode_weed', 'pipe_split', 'pop_auth_try_all', 'pop_last', + \ 'postpone_encrypt', 'print_decode', 'print_decode_weed', 'print_split', 'prompt_after', + \ 'read_only', 'reflow_space_quotes', 'reflow_text', 'reply_self', 'reply_with_xorig', + \ 'resolve', 'resume_draft_files', 'resume_edited_draft_files', 'reverse_alias', + \ 'reverse_name', 'reverse_real_name', 'rfc2047_parameters', 'save_address', 'save_empty', + \ 'save_name', 'save_unsubscribed', 'score', 'show_new_news', 'show_only_unread', + \ 'sidebar_folder_indent', 'sidebar_new_mail_only', 'sidebar_next_new_wrap', + \ 'sidebar_non_empty_mailbox_only', 'sidebar_on_right', 'sidebar_short_path', + \ 'sidebar_visible', 'sig_dashes', 'sig_on_top', 'size_show_bytes', 'size_show_fractions', + \ 'size_show_mb', 'size_units_on_left', 'smart_wrap', 'smime_ask_cert_label', + \ 'smime_decrypt_use_default_key', 'smime_is_default', 'smime_self_encrypt', 'sort_re', + \ 'ssl_force_tls', 'ssl_use_sslv2', 'ssl_use_sslv3', 'ssl_use_system_certs', + \ 'ssl_use_tlsv1', 'ssl_use_tlsv1_1', 'ssl_use_tlsv1_2', 'ssl_use_tlsv1_3', \ 'ssl_verify_dates', 'ssl_verify_host', 'ssl_verify_partial_chains', 'status_on_top', \ 'strict_threads', 'suspend', 'text_flowed', 'thorough_search', 'thread_received', 'tilde', - \ 'ts_enabled', 'uncollapse_jump', 'uncollapse_new', 'user_agent', 'use_8bitmime', - \ 'use_domain', 'use_envelope_from', 'use_from', 'use_ipv6', 'virtual_spoolfile', - \ 'wait_key', 'weed', 'wrap_search', 'write_bcc', 'x_comment_to' + \ 'ts_enabled', 'tunnel_is_secure', 'uncollapse_jump', 'uncollapse_new', 'user_agent', + \ 'use_8bit_mime', 'use_domain', 'use_envelope_from', 'use_from', 'use_ipv6', + \ 'virtual_spool_file', 'wait_key', 'weed', 'wrap_search', 'write_bcc', 'x_comment_to' \ ], 0) -" CHECKED 2020-06-21 +" CHECKED 2022-04-08 " Deprecated Bools " List of DT_SYNONYM or DT_DEPRECATED Bools in MuttVars in mutt_config.c call s:boolQuadGen('Bool', [ - \ 'edit_hdrs', 'envelope_from', 'forw_decode', 'forw_decrypt', 'forw_quote', - \ 'header_cache_compress', 'ignore_linear_white_space', 'pgp_autoencrypt', 'pgp_autosign', - \ 'pgp_auto_traditional', 'pgp_create_traditional', 'pgp_replyencrypt', 'pgp_replysign', - \ 'pgp_replysignencrypted', 'xterm_set_titles' + \ 'askbcc', 'askcc', 'autoedit', 'confirmappend', 'confirmcreate', 'crypt_autoencrypt', + \ 'crypt_autopgp', 'crypt_autosign', 'crypt_autosmime', 'crypt_confirmhook', + \ 'crypt_replyencrypt', 'crypt_replysign', 'crypt_replysignencrypted', 'edit_hdrs', + \ 'envelope_from', 'forw_decode', 'forw_decrypt', 'forw_quote', 'header_cache_compress', + \ 'ignore_linear_white_space', 'imap_servernoise', 'include_onlyfirst', 'metoo', + \ 'mime_subject', 'pgp_autoencrypt', 'pgp_autoinline', 'pgp_autosign', + \ 'pgp_auto_traditional', 'pgp_create_traditional', 'pgp_replyencrypt', 'pgp_replyinline', + \ 'pgp_replysign', 'pgp_replysignencrypted', 'reverse_realname', 'ssl_usesystemcerts', + \ 'use_8bitmime', 'virtual_spoolfile', 'xterm_set_titles' \ ], 1) -" CHECKED 2020-06-21 +" CHECKED 2022-04-08 " List of DT_QUAD in MuttVars in mutt_config.c call s:boolQuadGen('Quad', [ \ 'abort_noattach', 'abort_nosubject', 'abort_unmodified', 'bounce', 'catchup_newsgroup', @@ -481,31 +494,32 @@ call s:boolQuadGen('Quad', [ \ 'post_moderated', 'print', 'quit', 'recall', 'reply_to', 'ssl_starttls', \ ], 0) -" CHECKED 2020-06-21 +" CHECKED 2022-04-08 " Deprecated Quads " List of DT_SYNONYM or DT_DEPRECATED Quads in MuttVars in mutt_config.c call s:boolQuadGen('Quad', [ \ 'mime_fwd', 'pgp_encrypt_self', 'pgp_verify_sig', 'smime_encrypt_self' \ ], 1) -" CHECKED 2020-06-21 +" CHECKED 2022-04-08 " List of DT_NUMBER or DT_LONG in MuttVars in mutt_config.c syntax keyword muttrcVarNum skipwhite contained - \ connect_timeout debug_level header_cache_compress_level history - \ imap_fetch_chunk_size imap_keepalive imap_pipeline_depth imap_poll_timeout mail_check - \ mail_check_stats_interval menu_context net_inc nm_db_limit nm_open_timeout - \ nm_query_window_current_position nm_query_window_duration nntp_context nntp_poll - \ pager_context pager_index_lines pgp_timeout pop_checkinterval read_inc reflow_wrap - \ save_history score_threshold_delete score_threshold_flag score_threshold_read - \ search_context sendmail_wait sidebar_component_depth sidebar_width skip_quoted_offset - \ sleep_time smime_timeout ssl_min_dh_prime_bits timeout time_inc toggle_quoted_show_levels - \ wrap wrap_headers write_inc + \ connect_timeout debug_level header_cache_compress_level history imap_fetch_chunk_size + \ imap_keepalive imap_pipeline_depth imap_poll_timeout mail_check mail_check_stats_interval + \ menu_context net_inc nm_db_limit nm_open_timeout nm_query_window_current_position + \ nm_query_window_duration nntp_context nntp_poll pager_context pager_index_lines + \ pager_read_delay pager_skip_quoted_context pgp_timeout pop_check_interval read_inc + \ reflow_wrap save_history score_threshold_delete score_threshold_flag score_threshold_read + \ search_context sendmail_wait sidebar_component_depth sidebar_width sleep_time + \ smime_timeout ssl_min_dh_prime_bits timeout time_inc toggle_quoted_show_levels wrap + \ wrap_headers write_inc \ nextgroup=muttrcSetNumAssignment,muttrcVPrefix,muttrcVarBool,muttrcVarQuad,muttrcVarNum,muttrcVarStr +" CHECKED 2022-04-08 +" Deprecated Numbers syntax keyword muttrcVarDeprecatedNum contained skipwhite - \ header_cache_pagesize wrapmargin - \ nextgroup=muttrcSetNumAssignment,muttrcVPrefix,muttrcVarBool,muttrcVarQuad,muttrcVarNum,muttrcVarStr + \ header_cache_pagesize pop_checkinterval skip_quoted_offset -" CHECKED 2020-06-21 +" CHECKED 2022-04-08 " List of DT_STRING in MuttVars in mutt_config.c " Special cases first, and all the rest at the end " Formats themselves must be updated in their respective groups @@ -517,19 +531,19 @@ syntax keyword muttrcVarStr contained skipwhite compose_format nextgroup=muttrcV syntax keyword muttrcVarStr contained skipwhite folder_format vfolder_format nextgroup=muttrcVarEqualsFolderFmt syntax keyword muttrcVarStr contained skipwhite attribution forward_format index_format message_format pager_format nextgroup=muttrcVarEqualsIdxFmt syntax keyword muttrcVarStr contained skipwhite mix_entry_format nextgroup=muttrcVarEqualsMixFmt +syntax keyword muttrcVarStr contained skipwhite pattern_format nextgroup=muttrcVarEqualsPatternFmt syntax keyword muttrcVarStr contained skipwhite - \ pgp_clearsign_command pgp_decode_command pgp_decrypt_command - \ pgp_encrypt_only_command pgp_encrypt_sign_command pgp_export_command pgp_getkeys_command - \ pgp_import_command pgp_list_pubring_command pgp_list_secring_command - \ pgp_sign_command pgp_verify_command pgp_verify_key_command + \ pgp_clear_sign_command pgp_decode_command pgp_decrypt_command pgp_encrypt_only_command + \ pgp_encrypt_sign_command pgp_export_command pgp_get_keys_command pgp_import_command + \ pgp_list_pubring_command pgp_list_secring_command pgp_sign_command pgp_verify_command + \ pgp_verify_key_command \ nextgroup=muttrcVarEqualsPGPCmdFmt syntax keyword muttrcVarStr contained skipwhite pgp_entry_format nextgroup=muttrcVarEqualsPGPFmt syntax keyword muttrcVarStr contained skipwhite query_format nextgroup=muttrcVarEqualsQueryFmt syntax keyword muttrcVarStr contained skipwhite \ smime_decrypt_command smime_encrypt_command smime_get_cert_command - \ smime_get_cert_email_command smime_get_signer_cert_command - \ smime_import_cert_command smime_pk7out_command smime_sign_command - \ smime_verify_command smime_verify_opaque_command + \ smime_get_cert_email_command smime_get_signer_cert_command smime_import_cert_command + \ smime_pk7out_command smime_sign_command smime_verify_command smime_verify_opaque_command \ nextgroup=muttrcVarEqualsSmimeFmt syntax keyword muttrcVarStr contained skipwhite status_format ts_icon_format ts_status_format nextgroup=muttrcVarEqualsStatusFmt syntax keyword muttrcVarStr contained skipwhite date_format nextgroup=muttrcVarEqualsStrftimeFmt @@ -538,64 +552,66 @@ syntax keyword muttrcVarStr contained skipwhite sidebar_format nextgroup=muttrcV syntax keyword muttrcVarStr contained skipwhite \ abort_key arrow_string assumed_charset attach_charset attach_sep attribution_locale \ autocrypt_acct_format charset config_charset content_type crypt_protected_headers_subject - \ default_hook dsn_notify dsn_return empty_subject escape forward_attribution_intro - \ forward_attribution_trailer header_cache_backend header_cache_compress_method hidden_tags - \ hostname imap_authenticators imap_delim_chars imap_headers imap_login imap_pass imap_user - \ indent_string mailcap_path mark_macro_prefix mh_seq_flagged mh_seq_replied mh_seq_unseen - \ newsgroups_charset news_server nm_default_url nm_exclude_tags nm_flagged_tag nm_query_type - \ nm_query_window_current_search nm_query_window_timebase nm_record_tags nm_replied_tag - \ nm_unread_tag nntp_authenticators nntp_pass nntp_user pgp_default_key pgp_sign_as pipe_sep - \ pop_authenticators pop_host pop_pass pop_user postpone_encrypt_as post_indent_string - \ preconnect preferred_languages realname send_charset show_multipart_alternative - \ sidebar_delim_chars sidebar_divider_char sidebar_indent_string simple_search - \ smime_default_key smime_encrypt_with smime_sign_as smime_sign_digest_alg - \ smtp_authenticators smtp_pass smtp_url smtp_user spam_separator ssl_ciphers + \ default_hook dsn_notify dsn_return empty_subject forward_attribution_intro + \ forward_attribution_trailer greeting header_cache_backend header_cache_compress_method + \ hidden_tags hostname imap_authenticators imap_delim_chars imap_headers imap_login + \ imap_pass imap_user indent_string mailcap_path mark_macro_prefix mh_seq_flagged + \ mh_seq_replied mh_seq_unseen newsgroups_charset news_server nm_default_url nm_exclude_tags + \ nm_flagged_tag nm_query_type nm_query_window_current_search nm_query_window_or_terms + \ nm_query_window_timebase nm_record_tags nm_replied_tag nm_unread_tag nntp_authenticators + \ nntp_pass nntp_user pgp_default_key pgp_sign_as pipe_sep pop_authenticators pop_host + \ pop_pass pop_user postpone_encrypt_as post_indent_string preconnect preferred_languages + \ real_name send_charset show_multipart_alternative sidebar_delim_chars sidebar_divider_char + \ sidebar_indent_string simple_search smime_default_key smime_encrypt_with smime_sign_as + \ smime_sign_digest_alg smtp_authenticators smtp_pass smtp_url smtp_user spam_separator + \ ssl_ciphers \ nextgroup=muttrcSetStrAssignment,muttrcVPrefix,muttrcVarBool,muttrcVarQuad,muttrcVarNum,muttrcVarStr " Deprecated strings syntax keyword muttrcVarDeprecatedStr - \ abort_noattach_regexp attach_keyword forw_format hdr_format indent_str msg_format - \ nm_default_uri pgp_self_encrypt_as post_indent_str print_cmd quote_regexp reply_regexp - \ smime_self_encrypt_as xterm_icon xterm_title + \ abort_noattach_regexp attach_keyword escape forw_format hdr_format indent_str msg_format + \ nm_default_uri pgp_clearsign_command pgp_getkeys_command pgp_self_encrypt_as + \ post_indent_str print_cmd quote_regexp realname reply_regexp smime_self_encrypt_as + \ spoolfile visual xterm_icon xterm_title -" CHECKED 2020-06-21 +" CHECKED 2022-04-08 " List of DT_ADDRESS syntax keyword muttrcVarStr contained skipwhite envelope_from_address from nextgroup=muttrcSetStrAssignment,muttrcVPrefix,muttrcVarBool,muttrcVarQuad,muttrcVarNum,muttrcVarStr " List of DT_ENUM -syntax keyword muttrcVarStr contained skipwhite mbox_type nextgroup=muttrcSetStrAssignment,muttrcVPrefix,muttrcVarBool,muttrcVarQuad,muttrcVarNum,muttrcVarStr +syntax keyword muttrcVarStr contained skipwhite mbox_type use_threads nextgroup=muttrcSetStrAssignment,muttrcVPrefix,muttrcVarBool,muttrcVarQuad,muttrcVarNum,muttrcVarStr " List of DT_MBTABLE syntax keyword muttrcVarStr contained skipwhite crypt_chars flag_chars from_chars status_chars to_chars nextgroup=muttrcSetStrAssignment,muttrcVPrefix,muttrcVarBool,muttrcVarQuad,muttrcVarNum,muttrcVarStr -" CHECKED 2020-06-21 -" List of DT_PATH +" CHECKED 2022-04-08 +" List of DT_PATH or DT_MAILBOX syntax keyword muttrcVarStr contained skipwhite \ alias_file attach_save_dir autocrypt_dir certificate_file debug_file \ entropy_file folder header_cache history_file mbox message_cachedir newsrc \ news_cache_dir postponed record signature smime_ca_location - \ smime_certificates smime_keys spoolfile ssl_ca_certificates_file - \ ssl_client_cert tmpdir trash + \ smime_certificates smime_keys spool_file ssl_ca_certificates_file ssl_client_cert + \ tmpdir trash \ nextgroup=muttrcSetStrAssignment,muttrcVPrefix,muttrcVarBool,muttrcVarQuad,muttrcVarNum,muttrcVarStr " List of DT_COMMAND (excluding pgp_*_command and smime_*_command) syntax keyword muttrcVarStr contained skipwhite \ display_filter editor inews ispell mixmaster new_mail_command pager - \ print_command query_command sendmail shell visual external_search_command + \ print_command query_command sendmail shell external_search_command \ imap_oauth_refresh_command pop_oauth_refresh_command \ mime_type_query_command smtp_oauth_refresh_command tunnel \ nextgroup=muttrcSetStrAssignment,muttrcVPrefix,muttrcVarBool,muttrcVarQuad,muttrcVarNum,muttrcVarStr -" CHECKED 2020-06-21 +" CHECKED 2022-04-08 " List of DT_REGEX syntax keyword muttrcVarStr contained skipwhite - \ abort_noattach_regex gecos_mask mask pgp_decryption_okay pgp_good_sign - \ quote_regex reply_regex smileys + \ abort_noattach_regex gecos_mask mask pgp_decryption_okay pgp_good_sign quote_regex + \ reply_regex smileys \ nextgroup=muttrcSetStrAssignment,muttrcVPrefix,muttrcVarBool,muttrcVarQuad,muttrcVarNum,muttrcVarStr " List of DT_SORT syntax keyword muttrcVarStr contained skipwhite \ pgp_sort_keys sidebar_sort_method sort sort_alias sort_aux sort_browser \ nextgroup=muttrcSetStrAssignment,muttrcVPrefix,muttrcVarBool,muttrcVarQuad,muttrcVarNum,muttrcVarStr -" CHECKED 2020-06-21 -" List of commands in Commands in mutt_config.c +" CHECKED 2022-04-08 +" List of commands in mutt_commands in mutt_commands.c " Remember to remove hooks, they have already been dealt with syntax keyword muttrcCommand skipwhite alias nextgroup=muttrcAliasGroupDef,muttrcAliasKey,muttrcAliasNL syntax keyword muttrcCommand skipwhite bind nextgroup=muttrcBindMenuList,muttrcBindMenuListNL @@ -607,14 +623,12 @@ syntax keyword muttrcCommand skipwhite spam nextgroup=muttrcSpamPattern syntax keyword muttrcCommand skipwhite unalias nextgroup=muttrcUnAliasKey,muttrcUnAliasNL syntax keyword muttrcCommand skipwhite unhook nextgroup=muttrcHooks syntax keyword muttrcCommand skipwhite - \ alternative_order attachments auto_view finish hdr_order ifdef ifndef - \ ignore lua lua-source mailboxes mailto_allow mime_lookup my_hdr push score - \ setenv sidebar_whitelist source subjectrx subscribe-to tag-formats - \ tag-transforms unalternative_order unattachments unauto_view uncolor - \ unhdr_order unignore unmailboxes unmailto_allow unmime_lookup unmono - \ unmy_hdr unscore unsetenv unsidebar_whitelist unsubjectrx unsubscribe-from - \ unvirtual-mailboxes virtual-mailboxes named-mailboxes - \ echo unbind unmacro + \ alternative_order attachments auto_view cd echo finish hdr_order ifdef ifndef ignore lua + \ lua-source mailboxes mailto_allow mime_lookup my_hdr named-mailboxes push score setenv + \ sidebar_whitelist source subjectrx subscribe-to tag-formats tag-transforms + \ unalternative_order unattachments unauto_view unbind uncolor unhdr_order unignore unmacro + \ unmailboxes unmailto_allow unmime_lookup unmono unmy_hdr unscore unsetenv + \ unsidebar_whitelist unsubjectrx unsubscribe-from unvirtual-mailboxes virtual-mailboxes function! s:genFunctions(functions) for f in a:functions @@ -622,66 +636,68 @@ function! s:genFunctions(functions) endfor endfunction -" CHECKED 2020-06-21 +" CHECKED 2022-04-08 " List of functions in functions.c " Note: 'noop' is included but is elsewhere in the source call s:genFunctions(['noop', - \ 'accept', 'append', 'attach-file', 'attach-key', 'attach-message', 'attach-news-message', - \ 'autocrypt-acct-menu', 'autocrypt-menu', 'backspace', 'backward-char', 'backward-word', - \ 'bol', 'bottom-page', 'bottom', 'bounce-message', 'break-thread', 'buffy-cycle', - \ 'buffy-list', 'capitalize-word', 'catchup', 'chain-next', 'chain-prev', 'change-dir', - \ 'change-folder-readonly', 'change-folder', 'change-newsgroup-readonly', - \ 'change-newsgroup', 'change-vfolder', 'check-new', 'check-stats', + \ 'accept', 'alias-dialog', 'append', 'attach-file', 'attach-key', 'attach-message', + \ 'attach-news-message', 'autocrypt-acct-menu', 'autocrypt-menu', 'backspace', + \ 'backward-char', 'backward-word', 'bol', 'bottom', 'bottom-page', 'bounce-message', + \ 'break-thread', 'buffy-cycle', 'buffy-list', 'capitalize-word', 'catchup', 'chain-next', + \ 'chain-prev', 'change-dir', 'change-folder', 'change-folder-readonly', 'change-newsgroup', + \ 'change-newsgroup-readonly', 'change-vfolder', 'check-new', 'check-stats', \ 'check-traditional-pgp', 'clear-flag', 'collapse-all', 'collapse-parts', - \ 'collapse-thread', 'complete-query', 'complete', 'compose-to-sender', 'copy-file', + \ 'collapse-thread', 'complete', 'complete-query', 'compose-to-sender', 'copy-file', \ 'copy-message', 'create-account', 'create-alias', 'create-mailbox', 'current-bottom', \ 'current-middle', 'current-top', 'decode-copy', 'decode-save', 'decrypt-copy', - \ 'decrypt-save', 'delete-account', 'delete-char', 'delete-entry', 'delete-mailbox', - \ 'delete-message', 'delete-pattern', 'delete-subthread', 'delete-thread', 'delete', + \ 'decrypt-save', 'delete', 'delete-account', 'delete-char', 'delete-entry', + \ 'delete-mailbox', 'delete-message', 'delete-pattern', 'delete-subthread', 'delete-thread', \ 'descend-directory', 'detach-file', 'display-address', 'display-filename', - \ 'display-message', 'display-toggle-weed', 'downcase-word', 'edit-bcc', 'edit-cc', - \ 'edit-description', 'edit-encoding', 'edit-fcc', 'edit-file', 'edit-followup-to', - \ 'edit-from', 'edit-headers', 'edit-label', 'edit-language', 'edit-message', 'edit-mime', - \ 'edit-newsgroups', 'edit-or-view-raw-message', 'edit-raw-message', 'edit-reply-to', - \ 'edit-subject', 'edit-to', 'edit-type', 'edit-x-comment-to', 'edit', 'end-cond', - \ 'enter-command', 'enter-mask', 'entire-thread', 'eol', 'exit', 'extract-keys', - \ 'fetch-mail', 'filter-entry', 'first-entry', 'flag-message', 'followup-message', - \ 'forget-passphrase', 'forward-char', 'forward-message', 'forward-to-group', - \ 'forward-word', 'get-attachment', 'get-children', 'get-message', 'get-parent', - \ 'goto-folder', 'goto-parent', 'group-alternatives', 'group-chat-reply', - \ 'group-multilingual', 'group-reply', 'half-down', 'half-up', 'help', 'history-down', - \ 'history-search', 'history-up', 'imap-fetch-mail', 'imap-logout-all', 'insert', 'ispell', - \ 'jump', 'kill-eol', 'kill-eow', 'kill-line', 'kill-word', 'last-entry', - \ 'limit-current-thread', 'limit', 'link-threads', 'list-reply', 'mail-key', - \ 'mailbox-cycle', 'mailbox-list', 'mail', 'mark-as-new', 'mark-message', 'middle-page', - \ 'mix', 'modify-labels-then-hide', 'modify-labels', 'modify-tags-then-hide', - \ 'modify-tags', 'move-down', 'move-up', 'new-mime', 'next-entry', 'next-line', - \ 'next-new-then-unread', 'next-new', 'next-page', 'next-subthread', 'next-thread', - \ 'next-undeleted', 'next-unread-mailbox', 'next-unread', 'parent-message', 'pgp-menu', - \ 'pipe-entry', 'pipe-message', 'post-message', 'postpone-message', 'previous-entry', - \ 'previous-line', 'previous-new-then-unread', 'previous-new', 'previous-page', - \ 'previous-subthread', 'previous-thread', 'previous-undeleted', 'previous-unread', - \ 'print-entry', 'print-message', 'purge-message', 'purge-thread', 'quasi-delete', - \ 'query-append', 'query', 'quit', 'quote-char', 'read-subthread', 'read-thread', - \ 'recall-message', 'reconstruct-thread', 'redraw-screen', 'refresh', 'reload-active', - \ 'rename-attachment', 'rename-file', 'rename-mailbox', 'reply', 'resend-message', - \ 'root-message', 'save-entry', 'save-message', 'search-next', 'search-opposite', - \ 'search-reverse', 'search-toggle', 'search', 'select-entry', 'select-new', + \ 'display-message', 'display-toggle-weed', 'downcase-word', 'edit', 'edit-bcc', 'edit-cc', + \ 'edit-content-id', 'edit-description', 'edit-encoding', 'edit-fcc', 'edit-file', + \ 'edit-followup-to', 'edit-from', 'edit-headers', 'edit-label', 'edit-language', + \ 'edit-message', 'edit-mime', 'edit-newsgroups', 'edit-or-view-raw-message', + \ 'edit-raw-message', 'edit-reply-to', 'edit-subject', 'edit-to', 'edit-type', + \ 'edit-x-comment-to', 'end-cond', 'enter-command', 'enter-mask', 'entire-thread', 'eol', + \ 'error-history', 'exit', 'extract-keys', 'fetch-mail', 'filter-entry', 'first-entry', + \ 'flag-message', 'followup-message', 'forget-passphrase', 'forward-char', + \ 'forward-message', 'forward-to-group', 'forward-word', 'get-attachment', 'get-children', + \ 'get-message', 'get-parent', 'goto-folder', 'goto-parent', 'group-alternatives', + \ 'group-chat-reply', 'group-multilingual', 'group-related', 'group-reply', 'half-down', + \ 'half-up', 'help', 'history-down', 'history-search', 'history-up', 'imap-fetch-mail', + \ 'imap-logout-all', 'insert', 'ispell', 'jump', 'kill-eol', 'kill-eow', 'kill-line', + \ 'kill-word', 'last-entry', 'limit', 'limit-current-thread', 'link-threads', 'list-reply', + \ 'list-subscribe', 'list-unsubscribe', 'mail', 'mail-key', 'mailbox-cycle', 'mailbox-list', + \ 'mark-as-new', 'mark-message', 'middle-page', 'mix', 'modify-labels', + \ 'modify-labels-then-hide', 'modify-tags', 'modify-tags-then-hide', 'move-down', 'move-up', + \ 'new-mime', 'next-entry', 'next-line', 'next-new', 'next-new-then-unread', 'next-page', + \ 'next-subthread', 'next-thread', 'next-undeleted', 'next-unread', 'next-unread-mailbox', + \ 'parent-message', 'pgp-menu', 'pipe-entry', 'pipe-message', 'post-message', + \ 'postpone-message', 'previous-entry', 'previous-line', 'previous-new', + \ 'previous-new-then-unread', 'previous-page', 'previous-subthread', 'previous-thread', + \ 'previous-undeleted', 'previous-unread', 'print-entry', 'print-message', 'purge-message', + \ 'purge-thread', 'quasi-delete', 'query', 'query-append', 'quit', 'quote-char', + \ 'read-subthread', 'read-thread', 'recall-message', 'reconstruct-thread', 'redraw-screen', + \ 'refresh', 'reload-active', 'rename-attachment', 'rename-file', 'rename-mailbox', 'reply', + \ 'resend-message', 'root-message', 'save-entry', 'save-message', 'search', 'search-next', + \ 'search-opposite', 'search-reverse', 'search-toggle', 'select-entry', 'select-new', \ 'send-message', 'set-flag', 'shell-escape', 'show-limit', 'show-log-messages', - \ 'show-version', 'sidebar-next-new', 'sidebar-first', 'sidebar-last', 'sidebar-next', - \ 'sidebar-open', 'sidebar-page-down', 'sidebar-page-up', 'sidebar-prev-new', - \ 'sidebar-prev', 'sidebar-toggle-virtual', 'sidebar-toggle-visible', 'skip-quoted', - \ 'smime-menu', 'sort-mailbox', 'sort-reverse', 'sort', 'subscribe-pattern', - \ 'sync-mailbox', 'tag-entry', 'tag-message', 'tag-pattern', 'tag-prefix-cond', - \ 'tag-prefix', 'tag-subthread', 'tag-thread', 'toggle-active', 'toggle-disposition', - \ 'toggle-mailboxes', 'toggle-new', 'toggle-prefer-encrypt', 'toggle-quoted', - \ 'toggle-read', 'toggle-recode', 'toggle-subscribed', 'toggle-unlink', 'toggle-write', - \ 'top-page', 'top', 'transpose-chars', 'uncatchup', 'undelete-entry', 'undelete-message', - \ 'undelete-pattern', 'undelete-subthread', 'undelete-thread', 'unsubscribe-pattern', - \ 'untag-pattern', 'upcase-word', 'update-encoding', 'verify-key', - \ 'vfolder-from-query-readonly', 'vfolder-from-query', 'vfolder-window-backward', - \ 'vfolder-window-forward', 'view-attachments', 'view-attach', 'view-file', 'view-mailcap', - \ 'view-name', 'view-raw-message', 'view-text', 'what-key', 'write-fcc' + \ 'show-version', 'sidebar-first', 'sidebar-last', 'sidebar-next', 'sidebar-next-new', + \ 'sidebar-open', 'sidebar-page-down', 'sidebar-page-up', 'sidebar-prev', + \ 'sidebar-prev-new', 'sidebar-toggle-virtual', 'sidebar-toggle-visible', 'skip-headers', + \ 'skip-quoted', 'smime-menu', 'sort', 'sort-alias', 'sort-alias-reverse', 'sort-mailbox', + \ 'sort-reverse', 'subscribe', 'subscribe-pattern', 'sync-mailbox', 'tag-entry', + \ 'tag-message', 'tag-pattern', 'tag-prefix', 'tag-prefix-cond', 'tag-subthread', + \ 'tag-thread', 'toggle-active', 'toggle-disposition', 'toggle-mailboxes', 'toggle-new', + \ 'toggle-prefer-encrypt', 'toggle-quoted', 'toggle-read', 'toggle-recode', + \ 'toggle-subscribed', 'toggle-unlink', 'toggle-write', 'top', 'top-page', + \ 'transpose-chars', 'uncatchup', 'undelete-entry', 'undelete-message', 'undelete-pattern', + \ 'undelete-subthread', 'undelete-thread', 'ungroup-attachment', 'unsubscribe', + \ 'unsubscribe-pattern', 'untag-pattern', 'upcase-word', 'update-encoding', 'verify-key', + \ 'vfolder-from-query', 'vfolder-from-query-readonly', 'vfolder-window-backward', + \ 'vfolder-window-forward', 'vfolder-window-reset', 'view-attach', 'view-attachments', + \ 'view-file', 'view-mailcap', 'view-name', 'view-pager', 'view-raw-message', 'view-text', + \ 'what-key', 'write-fcc' \ ]) " Define the default highlighting. @@ -758,6 +774,7 @@ highlight def link muttrcFolderFormatEscapes muttrcEscape highlight def link muttrcGroupIndexFormatEscapes muttrcEscape highlight def link muttrcIndexFormatEscapes muttrcEscape highlight def link muttrcMixFormatEscapes muttrcEscape +highlight def link muttrcPatternFormatEscapes muttrcEscape highlight def link muttrcPGPCmdFormatEscapes muttrcEscape highlight def link muttrcPGPFormatEscapes muttrcEscape highlight def link muttrcPGPTimeEscapes muttrcEscape @@ -774,6 +791,7 @@ highlight def link muttrcComposeFormatConditionals muttrcFormatConditionals2 highlight def link muttrcFolderFormatConditionals muttrcFormatConditionals2 highlight def link muttrcIndexFormatConditionals muttrcFormatConditionals2 highlight def link muttrcMixFormatConditionals muttrcFormatConditionals2 +highlight def link muttrcPatternFormatConditionals muttrcFormatConditionals2 highlight def link muttrcPGPCmdFormatConditionals muttrcFormatConditionals2 highlight def link muttrcPGPFormatConditionals muttrcFormatConditionals2 highlight def link muttrcSmimeFormatConditionals muttrcFormatConditionals2 @@ -789,6 +807,7 @@ highlight def link muttrcFolderFormatStr muttrcString highlight def link muttrcGroupIndexFormatStr muttrcString highlight def link muttrcIndexFormatStr muttrcString highlight def link muttrcMixFormatStr muttrcString +highlight def link muttrcPatternFormatStr muttrcString highlight def link muttrcPGPCmdFormatStr muttrcString highlight def link muttrcPGPFormatStr muttrcString highlight def link muttrcQueryFormatStr muttrcString diff --git a/runtime/syntax/python.vim b/runtime/syntax/python.vim index 3427aa06c8..2293163a5b 100644 --- a/runtime/syntax/python.vim +++ b/runtime/syntax/python.vim @@ -1,7 +1,7 @@ " Vim syntax file " Language: Python " Maintainer: Zvezdan Petkovic <zpetkovic@acm.org> -" Last Change: 2021 Feb 15 +" Last Change: 2021 Dec 10 " Credits: Neil Schemenauer <nas@python.ca> " Dmitry Vasiliev " @@ -77,13 +77,14 @@ endif " " The list can be checked using: " -" python3 -c 'import keyword, pprint; pprint.pprint(keyword.kwlist, compact=True)' +" python3 -c 'import keyword, pprint; pprint.pprint(keyword.kwlist + keyword.softkwlist, compact=True)' " syn keyword pythonStatement False None True syn keyword pythonStatement as assert break continue del global syn keyword pythonStatement lambda nonlocal pass return with yield syn keyword pythonStatement class def nextgroup=pythonFunction skipwhite syn keyword pythonConditional elif else if +syn keyword pythonConditional case match syn keyword pythonRepeat for while syn keyword pythonOperator and in is not or syn keyword pythonException except finally raise try 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/query.lua b/runtime/syntax/query.lua new file mode 100644 index 0000000000..e24ff65360 --- /dev/null +++ b/runtime/syntax/query.lua @@ -0,0 +1,6 @@ +-- Neovim syntax file +-- Language: Tree-sitter query +-- Last Change: 2022 Apr 13 + +-- it's a lisp! +vim.cmd [[ runtime! syntax/lisp.vim ]] 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/ruby.vim b/runtime/syntax/ruby.vim index 13d6d9efd8..c951fcfe1d 100644 --- a/runtime/syntax/ruby.vim +++ b/runtime/syntax/ruby.vim @@ -3,7 +3,7 @@ " Maintainer: Doug Kearns <dougkearns@gmail.com> " URL: https://github.com/vim-ruby/vim-ruby " Release Coordinator: Doug Kearns <dougkearns@gmail.com> -" Last Change: 2021 Jun 06 +" Last Change: 2021 Nov 03 " ---------------------------------------------------------------------------- " " Previous Maintainer: Mirko Nasato @@ -66,7 +66,7 @@ endfunction com! -nargs=* SynFold call s:run_syntax_fold(<q-args>) " Not-Top Cluster {{{1 -syn cluster rubyNotTop contains=@rubyCommentNotTop,@rubyStringNotTop,@rubyRegexpSpecial,@rubyDeclaration,@rubyExceptionHandler,@rubyClassOperator,rubyConditional,rubyModuleName,rubyClassName,rubySymbolDelimiter,rubyParentheses,@Spell +syn cluster rubyNotTop contains=@rubyCommentNotTop,@rubyStringNotTop,@rubyRegexpSpecial,@rubyDeclaration,@rubyExceptionHandler,@rubyClassOperator,rubyConditional,rubyModuleName,rubyClassName,rubySymbolDelimiter,rubyDoubleQuoteSymbolDelimiter,rubySingleQuoteSymbolDelimiter,rubyParentheses,@Spell " Whitespace Errors {{{1 if exists("ruby_space_errors") @@ -364,6 +364,9 @@ if !exists("b:ruby_no_expensive") && !exists("ruby_no_expensive") SynFold 'class' syn region rubyClassBlock start="\<class\>" matchgroup=rubyClass skip="\<end:" end="\<end\>" contains=ALLBUT,@rubyNotTop SynFold 'module' syn region rubyModuleBlock start="\<module\>" matchgroup=rubyModule skip="\<end:" end="\<end\>" contains=ALLBUT,@rubyNotTop + " endless def + syn match rubyDefine "\<def\s\+\ze[^[:space:];#(]\+\%(\s\+\|\s*(.*)\s*\)=" nextgroup=rubyMethodDeclaration skipwhite + " modifiers syn match rubyLineContinuation "\\$" nextgroup=@rubyModifier skipwhite skipnl syn match rubyConditionalModifier "\<\%(if\|unless\)\>" @@ -430,9 +433,10 @@ endif " Comments and Documentation {{{1 syn match rubySharpBang "\%^#!.*" display syn keyword rubyTodo FIXME NOTE TODO OPTIMIZE HACK REVIEW XXX todo contained -syn match rubyEncoding "[[:alnum:]-]\+" contained display +syn match rubyEncoding "[[:alnum:]-_]\+" contained display syn match rubyMagicComment "\c\%<3l#\s*\zs\%(coding\|encoding\):" contained nextgroup=rubyEncoding skipwhite syn match rubyMagicComment "\c\%<10l#\s*\zs\%(frozen_string_literal\|warn_indent\|warn_past_scope\):" contained nextgroup=rubyBoolean skipwhite +syn match rubyMagicComment "\c\%<10l#\s*\zs\%(shareable_constant_value\):" contained nextgroup=rubyEncoding skipwhite syn match rubyComment "#.*" contains=@rubyCommentSpecial,rubySpaceError,@Spell syn cluster rubyCommentSpecial contains=rubySharpBang,rubyTodo,rubyMagicComment @@ -465,6 +469,10 @@ syn match rubyDefinedOperator "\%#=1\<defined?" display syn match rubySymbol "\%(\w\|[^\x00-\x7F]\)\%(\w\|[^\x00-\x7F]\)*[?!]\=::\@!"he=e-1 contained containedin=rubyBlockParameterList,rubyCurlyBlock syn match rubySymbol "[]})\"':]\@1<!\<\%(\h\|[^\x00-\x7F]\)\%(\w\|[^\x00-\x7F]\)*[!?]\=:[[:space:],;]\@="he=e-1 syn match rubySymbol "[[:space:],{(]\%(\h\|[^\x00-\x7F]\)\%(\w\|[^\x00-\x7F]\)*[!?]\=:[[:space:],;]\@="hs=s+1,he=e-1 +syn match rubySingleQuoteSymbolDelimiter "'" contained +syn match rubySymbol "'\%(\\.\|[^']\)*'::\@!"he=e-1 contains=rubyQuoteEscape,rubyBackslashEscape,rubySingleQuoteSymbolDelimiter +syn match rubyDoubleQuoteSymbolDelimiter "\"" contained +syn match rubySymbol "\"\%(\\.\|[^\"]\)*\"::\@!"he=e-1 contains=@rubyStringSpecial,rubyDoubleQuoteSymbolDelimiter " __END__ Directive {{{1 SynFold '__END__' syn region rubyData matchgroup=rubyDataDirective start="^__END__$" end="\%$" @@ -565,6 +573,8 @@ hi def link rubyHeredocDelimiter rubyStringDelimiter hi def link rubyPercentRegexpDelimiter rubyRegexpDelimiter hi def link rubyPercentStringDelimiter rubyStringDelimiter hi def link rubyPercentSymbolDelimiter rubySymbolDelimiter +hi def link rubyDoubleQuoteSymbolDelimiter rubySymbolDelimiter +hi def link rubySingleQuoteSymbolDelimiter rubySymbolDelimiter hi def link rubyRegexpDelimiter rubyStringDelimiter hi def link rubySymbolDelimiter rubySymbol hi def link rubyString String diff --git a/runtime/syntax/sass.vim b/runtime/syntax/sass.vim index b51a0ae26b..8f41aba4f7 100644 --- a/runtime/syntax/sass.vim +++ b/runtime/syntax/sass.vim @@ -2,7 +2,7 @@ " Language: Sass " Maintainer: Tim Pope <vimNOSPAM@tpope.org> " Filenames: *.sass -" Last Change: 2019 Dec 05 +" Last Change: 2022 Mar 15 if exists("b:current_syntax") finish @@ -58,6 +58,7 @@ syn match sassAmpersand "&" " TODO: Arithmetic (including strings and concatenation) syn region sassMediaQuery matchgroup=sassMedia start="@media" end="[{};]\@=\|$" contains=sassMediaOperators +syn region sassKeyframe matchgroup=cssAtKeyword start=/@\(-[a-z]\+-\)\=keyframes\>/ end=";\|$" contains=cssVendor,cssComment nextgroup=cssDefinition syn keyword sassMediaOperators and not only contained syn region sassCharset start="@charset" end=";\|$" contains=scssComment,cssStringQ,cssStringQQ,cssURL,cssUnicodeEscape,cssMediaType syn region sassInclude start="@import" end=";\|$" contains=scssComment,cssStringQ,cssStringQQ,cssURL,cssUnicodeEscape,cssMediaType 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/sml.vim b/runtime/syntax/sml.vim index 53ff12a859..8f1af3f9bd 100644 --- a/runtime/syntax/sml.vim +++ b/runtime/syntax/sml.vim @@ -1,9 +1,10 @@ " Vim syntax file " Language: SML " Filenames: *.sml *.sig -" Maintainers: Markus Mottl <markus.mottl@gmail.com> -" Fabrizio Zeno Cornelli <zeno@filibusta.crema.unimi.it> -" Last Change: 2021 Oct 04 +" Maintainer: Markus Mottl <markus.mottl@gmail.com> +" Previous Maintainer: Fabrizio Zeno Cornelli +" <zeno@filibusta.crema.unimi.it> (invalid) +" Last Change: 2022 Apr 01 " 2015 Aug 31 - Fixed opening of modules (Ramana Kumar) " 2006 Oct 23 - Fixed character highlighting bug (MM) diff --git a/runtime/syntax/squirrel.vim b/runtime/syntax/squirrel.vim new file mode 100644 index 0000000000..81d59cc986 --- /dev/null +++ b/runtime/syntax/squirrel.vim @@ -0,0 +1,50 @@ +" Vim syntax file +" Language: squirrel +" Current Maintainer: Matt Dunford (zenmatic@gmail.com) +" URL: https://github.com/zenmatic/vim-syntax-squirrel +" Last Change: 2021 Nov 28 + +" http://squirrel-lang.org/ + +" quit when a syntax file was already loaded +if exists("b:current_syntax") + finish +endif + +" inform C syntax that the file was included from cpp.vim +let b:filetype_in_cpp_family = 1 + +" Read the C syntax to start with +runtime! syntax/c.vim +unlet b:current_syntax + +" squirrel extensions +syn keyword squirrelStatement delete this in yield resume base clone +syn keyword squirrelAccess local +syn keyword cConstant null +syn keyword squirrelModifier static +syn keyword squirrelType bool instanceof typeof +syn keyword squirrelExceptions throw try catch +syn keyword squirrelStructure class function extends constructor +syn keyword squirrelBoolean true false +syn keyword squirrelRepeat foreach + +syn region squirrelMultiString start='@"' end='"$' end='";$'me=e-1 + +syn match squirrelShComment "^\s*#.*$" + +" Default highlighting +hi def link squirrelAccess squirrelStatement +hi def link squirrelExceptions Exception +hi def link squirrelStatement Statement +hi def link squirrelModifier Type +hi def link squirrelType Type +hi def link squirrelStructure Structure +hi def link squirrelBoolean Boolean +hi def link squirrelMultiString String +hi def link squirrelRepeat cRepeat +hi def link squirrelShComment Comment + +let b:current_syntax = "squirrel" + +" vim: ts=8 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/syntax/texinfo.vim b/runtime/syntax/texinfo.vim index a4b7689707..79a4dfe821 100644 --- a/runtime/syntax/texinfo.vim +++ b/runtime/syntax/texinfo.vim @@ -1,396 +1,46 @@ " Vim syntax file -" Language: Texinfo (macro package for TeX) -" Maintainer: Sandor Kopanyi <sandor.kopanyi@mailbox.hu> -" URL: <-> -" Last Change: 2004 Jun 23 -" -" the file follows the Texinfo manual structure; this file is based -" on manual for Texinfo version 4.0, 28 September 1999 -" since @ can have special meanings, everything is 'match'-ed and 'region'-ed -" (including @ in 'iskeyword' option has unexpected effects) +" Language: Texinfo (documentation format) +" Maintainer: Robert Dodier <robert.dodier@gmail.com> +" Latest Revision: 2021-12-15 -" quit when a syntax file was already loaded if exists("b:current_syntax") finish endif -if !exists("main_syntax") - let main_syntax = 'texinfo' -endif - -"in Texinfo can be real big things, like tables; sync for that -syn sync lines=200 - -"some general stuff -"syn match texinfoError "\S" contained TODO -syn match texinfoIdent "\k\+" contained "IDENTifier -syn match texinfoAssignment "\k\+\s*=\s*\k\+\s*$" contained "assigment statement ( var = val ) -syn match texinfoSinglePar "\k\+\s*$" contained "single parameter (used for several @-commands) -syn match texinfoIndexPar "\k\k\s*$" contained "param. used for different *index commands (+ @documentlanguage command) - - -"marking words and phrases (chap. 9 in Texinfo manual) -"(almost) everything appears as 'contained' too; is for tables (@table) - -"this chapter is at the beginning of this file to avoid overwritings - -syn match texinfoSpecialChar "@acronym" contained -syn region texinfoBrcPrmAtCmd matchgroup=texinfoAtCmd start="@acronym{" end="}" contains=texinfoSpecialChar,texinfoBrcPrmAtCmd -syn match texinfoSpecialChar "@b" contained -syn region texinfoBrcPrmAtCmd matchgroup=texinfoAtCmd start="@b{" end="}" contains=texinfoSpecialChar,texinfoBrcPrmAtCmd -syn match texinfoSpecialChar "@cite" contained -syn region texinfoBrcPrmAtCmd matchgroup=texinfoAtCmd start="@cite{" end="}" contains=texinfoSpecialChar,texinfoBrcPrmAtCmd -syn match texinfoSpecialChar "@code" contained -syn region texinfoBrcPrmAtCmd matchgroup=texinfoAtCmd start="@code{" end="}" contains=texinfoSpecialChar,texinfoBrcPrmAtCmd -syn match texinfoSpecialChar "@command" contained -syn region texinfoBrcPrmAtCmd matchgroup=texinfoAtCmd start="@command{" end="}" contains=texinfoSpecialChar,texinfoBrcPrmAtCmd -syn match texinfoSpecialChar "@dfn" contained -syn region texinfoBrcPrmAtCmd matchgroup=texinfoAtCmd start="@dfn{" end="}" contains=texinfoSpecialChar,texinfoBrcPrmAtCmd -syn match texinfoSpecialChar "@email" contained -syn region texinfoBrcPrmAtCmd matchgroup=texinfoAtCmd start="@email{" end="}" contains=texinfoSpecialChar,texinfoBrcPrmAtCmd -syn match texinfoSpecialChar "@emph" contained -syn region texinfoBrcPrmAtCmd matchgroup=texinfoAtCmd start="@emph{" end="}" contains=texinfoSpecialChar,texinfoBrcPrmAtCmd -syn match texinfoSpecialChar "@env" contained -syn region texinfoBrcPrmAtCmd matchgroup=texinfoAtCmd start="@env{" end="}" contains=texinfoSpecialChar,texinfoBrcPrmAtCmd -syn match texinfoSpecialChar "@file" contained -syn region texinfoBrcPrmAtCmd matchgroup=texinfoAtCmd start="@file{" end="}" contains=texinfoSpecialChar,texinfoBrcPrmAtCmd -syn match texinfoSpecialChar "@i" contained -syn region texinfoBrcPrmAtCmd matchgroup=texinfoAtCmd start="@i{" end="}" contains=texinfoSpecialChar,texinfoBrcPrmAtCmd -syn match texinfoSpecialChar "@kbd" contained -syn region texinfoBrcPrmAtCmd matchgroup=texinfoAtCmd start="@kbd{" end="}" contains=texinfoSpecialChar,texinfoBrcPrmAtCmd -syn match texinfoSpecialChar "@key" contained -syn region texinfoBrcPrmAtCmd matchgroup=texinfoAtCmd start="@key{" end="}" contains=texinfoSpecialChar -syn match texinfoSpecialChar "@option" contained -syn region texinfoBrcPrmAtCmd matchgroup=texinfoAtCmd start="@option{" end="}" contains=texinfoSpecialChar -syn match texinfoSpecialChar "@r" contained -syn region texinfoBrcPrmAtCmd matchgroup=texinfoAtCmd start="@r{" end="}" contains=texinfoSpecialChar,texinfoBrcPrmAtCmd -syn match texinfoSpecialChar "@samp" contained -syn region texinfoBrcPrmAtCmd matchgroup=texinfoAtCmd start="@samp{" end="}" contains=texinfoSpecialChar,texinfoBrcPrmAtCmd -syn match texinfoSpecialChar "@sc" contained -syn region texinfoBrcPrmAtCmd matchgroup=texinfoAtCmd start="@sc{" end="}" contains=texinfoSpecialChar,texinfoBrcPrmAtCmd -syn match texinfoSpecialChar "@strong" contained -syn region texinfoBrcPrmAtCmd matchgroup=texinfoAtCmd start="@strong{" end="}" contains=texinfoSpecialChar,texinfoBrcPrmAtCmd -syn match texinfoSpecialChar "@t" contained -syn region texinfoBrcPrmAtCmd matchgroup=texinfoAtCmd start="@t{" end="}" contains=texinfoSpecialChar,texinfoBrcPrmAtCmd -syn match texinfoSpecialChar "@url" contained -syn region texinfoBrcPrmAtCmd matchgroup=texinfoAtCmd start="@url{" end="}" contains=texinfoSpecialChar,texinfoBrcPrmAtCmd -syn match texinfoSpecialChar "@var" contained -syn region texinfoBrcPrmAtCmd matchgroup=texinfoAtCmd start="@var{" end="}" contains=texinfoSpecialChar,texinfoBrcPrmAtCmd -syn match texinfoAtCmd "^@kbdinputstyle" nextgroup=texinfoSinglePar skipwhite - - -"overview of Texinfo (chap. 1 in Texinfo manual) -syn match texinfoComment "@c .*" -syn match texinfoComment "@c$" -syn match texinfoComment "@comment .*" -syn region texinfoMltlnAtCmd matchgroup=texinfoComment start="^@ignore\s*$" end="^@end ignore\s*$" contains=ALL - - -"beginning a Texinfo file (chap. 3 in Texinfo manual) -syn region texinfoPrmAtCmd matchgroup=texinfoAtCmd start="@center " skip="\\$" end="$" contains=texinfoSpecialChar,texinfoBrcPrmAtCmd oneline -syn region texinfoMltlnDMAtCmd matchgroup=texinfoAtCmd start="^@detailmenu\s*$" end="^@end detailmenu\s*$" contains=texinfoSpecialChar,texinfoBrcPrmAtCmd -syn region texinfoPrmAtCmd matchgroup=texinfoAtCmd start="^@setfilename " skip="\\$" end="$" contains=texinfoSpecialChar oneline -syn region texinfoPrmAtCmd matchgroup=texinfoAtCmd start="^@settitle " skip="\\$" end="$" contains=texinfoSpecialChar oneline -syn region texinfoPrmAtCmd matchgroup=texinfoAtCmd start="^@shorttitlepage " skip="\\$" end="$" contains=texinfoSpecialChar oneline -syn region texinfoPrmAtCmd matchgroup=texinfoAtCmd start="^@title " skip="\\$" end="$" contains=texinfoSpecialChar oneline -syn region texinfoBrcPrmAtCmd matchgroup=texinfoAtCmd start="@titlefont{" end="}" contains=texinfoSpecialChar,texinfoBrcPrmAtCmd -syn region texinfoMltlnAtCmd matchgroup=texinfoAtCmd start="^@titlepage\s*$" end="^@end titlepage\s*$" contains=texinfoSpecialChar,texinfoBrcPrmAtCmd,texinfoMltlnDMAtCmd,texinfoAtCmd,texinfoPrmAtCmd,texinfoMltlnAtCmd -syn region texinfoPrmAtCmd matchgroup=texinfoAtCmd start="^@vskip " skip="\\$" end="$" contains=texinfoSpecialChar oneline -syn match texinfoAtCmd "^@exampleindent" nextgroup=texinfoSinglePar skipwhite -syn match texinfoAtCmd "^@headings" nextgroup=texinfoSinglePar skipwhite -syn match texinfoAtCmd "^\\input" nextgroup=texinfoSinglePar skipwhite -syn match texinfoAtCmd "^@paragraphindent" nextgroup=texinfoSinglePar skipwhite -syn match texinfoAtCmd "^@setchapternewpage" nextgroup=texinfoSinglePar skipwhite - - -"ending a Texinfo file (chap. 4 in Texinfo manual) -syn region texinfoPrmAtCmd matchgroup=texinfoAtCmd start="@author " skip="\\$" end="$" contains=texinfoSpecialChar oneline -"all below @bye should be comment TODO -syn match texinfoAtCmd "^@bye\s*$" -syn match texinfoAtCmd "^@contents\s*$" -syn match texinfoAtCmd "^@printindex" nextgroup=texinfoIndexPar skipwhite -syn match texinfoAtCmd "^@setcontentsaftertitlepage\s*$" -syn match texinfoAtCmd "^@setshortcontentsaftertitlepage\s*$" -syn match texinfoAtCmd "^@shortcontents\s*$" -syn match texinfoAtCmd "^@summarycontents\s*$" - - -"chapter structuring (chap. 5 in Texinfo manual) -syn region texinfoPrmAtCmd matchgroup=texinfoAtCmd start="^@appendix" skip="\\$" end="$" contains=texinfoSpecialChar oneline -syn region texinfoPrmAtCmd matchgroup=texinfoAtCmd start="^@appendixsec" skip="\\$" end="$" contains=texinfoSpecialChar oneline -syn region texinfoPrmAtCmd matchgroup=texinfoAtCmd start="^@appendixsection" skip="\\$" end="$" contains=texinfoSpecialChar oneline -syn region texinfoPrmAtCmd matchgroup=texinfoAtCmd start="^@appendixsubsec" skip="\\$" end="$" contains=texinfoSpecialChar oneline -syn region texinfoPrmAtCmd matchgroup=texinfoAtCmd start="^@appendixsubsubsec" skip="\\$" end="$" contains=texinfoSpecialChar oneline -syn region texinfoPrmAtCmd matchgroup=texinfoAtCmd start="^@centerchap" skip="\\$" end="$" contains=texinfoSpecialChar oneline -syn region texinfoPrmAtCmd matchgroup=texinfoAtCmd start="^@chapheading" skip="\\$" end="$" contains=texinfoSpecialChar oneline -syn region texinfoPrmAtCmd matchgroup=texinfoAtCmd start="^@chapter" skip="\\$" end="$" contains=texinfoSpecialChar oneline -syn region texinfoPrmAtCmd matchgroup=texinfoAtCmd start="^@heading" skip="\\$" end="$" contains=texinfoSpecialChar oneline -syn region texinfoPrmAtCmd matchgroup=texinfoAtCmd start="^@majorheading" skip="\\$" end="$" contains=texinfoSpecialChar oneline -syn region texinfoPrmAtCmd matchgroup=texinfoAtCmd start="^@section" skip="\\$" end="$" contains=texinfoSpecialChar oneline -syn region texinfoPrmAtCmd matchgroup=texinfoAtCmd start="^@subheading " skip="\\$" end="$" contains=texinfoSpecialChar oneline -syn region texinfoPrmAtCmd matchgroup=texinfoAtCmd start="^@subsection" skip="\\$" end="$" contains=texinfoSpecialChar oneline -syn region texinfoPrmAtCmd matchgroup=texinfoAtCmd start="^@subsubheading" skip="\\$" end="$" contains=texinfoSpecialChar oneline -syn region texinfoPrmAtCmd matchgroup=texinfoAtCmd start="^@subsubsection" skip="\\$" end="$" contains=texinfoSpecialChar oneline -syn region texinfoPrmAtCmd matchgroup=texinfoAtCmd start="^@subtitle" skip="\\$" end="$" contains=texinfoSpecialChar,texinfoBrcPrmAtCmd oneline -syn region texinfoPrmAtCmd matchgroup=texinfoAtCmd start="^@unnumbered" skip="\\$" end="$" contains=texinfoSpecialChar oneline -syn region texinfoPrmAtCmd matchgroup=texinfoAtCmd start="^@unnumberedsec" skip="\\$" end="$" contains=texinfoSpecialChar oneline -syn region texinfoPrmAtCmd matchgroup=texinfoAtCmd start="^@unnumberedsubsec" skip="\\$" end="$" contains=texinfoSpecialChar oneline -syn region texinfoPrmAtCmd matchgroup=texinfoAtCmd start="^@unnumberedsubsubsec" skip="\\$" end="$" contains=texinfoSpecialChar oneline -syn match texinfoAtCmd "^@lowersections\s*$" -syn match texinfoAtCmd "^@raisesections\s*$" - - -"nodes (chap. 6 in Texinfo manual) -syn region texinfoBrcPrmAtCmd matchgroup=texinfoAtCmd start="@anchor{" end="}" -syn region texinfoPrmAtCmd matchgroup=texinfoAtCmd start="^@top" skip="\\$" end="$" contains=texinfoSpecialChar oneline -syn region texinfoPrmAtCmd matchgroup=texinfoAtCmd start="^@node" skip="\\$" end="$" contains=texinfoSpecialChar oneline - - -"menus (chap. 7 in Texinfo manual) -syn region texinfoMltlnAtCmd matchgroup=texinfoAtCmd start="^@menu\s*$" end="^@end menu\s*$" contains=texinfoSpecialChar,texinfoBrcPrmAtCmd,texinfoMltlnDMAtCmd - - -"cross references (chap. 8 in Texinfo manual) -syn region texinfoBrcPrmAtCmd matchgroup=texinfoAtCmd start="@inforef{" end="}" contains=texinfoSpecialChar,texinfoBrcPrmAtCmd -syn region texinfoBrcPrmAtCmd matchgroup=texinfoAtCmd start="@pxref{" end="}" contains=texinfoSpecialChar,texinfoBrcPrmAtCmd -syn region texinfoBrcPrmAtCmd matchgroup=texinfoAtCmd start="@ref{" end="}" contains=texinfoSpecialChar,texinfoBrcPrmAtCmd -syn region texinfoBrcPrmAtCmd matchgroup=texinfoAtCmd start="@uref{" end="}" contains=texinfoSpecialChar,texinfoBrcPrmAtCmd -syn region texinfoBrcPrmAtCmd matchgroup=texinfoAtCmd start="@xref{" end="}" contains=texinfoSpecialChar,texinfoBrcPrmAtCmd - - -"marking words and phrases (chap. 9 in Texinfo manual) -"(almost) everything appears as 'contained' too; is for tables (@table) - -"this chapter is at the beginning of this file to avoid overwritings - - -"quotations and examples (chap. 10 in Texinfo manual) -syn region texinfoMltlnAtCmd matchgroup=texinfoAtCmd start="^@cartouche\s*$" end="^@end cartouche\s*$" contains=ALL -syn region texinfoMltlnAtCmd matchgroup=texinfoAtCmd start="^@display\s*$" end="^@end display\s*$" contains=ALL -syn region texinfoMltlnAtCmd matchgroup=texinfoAtCmd start="^@example\s*$" end="^@end example\s*$" contains=ALL -syn region texinfoMltlnAtCmd matchgroup=texinfoAtCmd start="^@flushleft\s*$" end="^@end flushleft\s*$" contains=ALL -syn region texinfoMltlnAtCmd matchgroup=texinfoAtCmd start="^@flushright\s*$" end="^@end flushright\s*$" contains=ALL -syn region texinfoMltlnAtCmd matchgroup=texinfoAtCmd start="^@format\s*$" end="^@end format\s*$" contains=ALL -syn region texinfoMltlnAtCmd matchgroup=texinfoAtCmd start="^@lisp\s*$" end="^@end lisp\s*$" contains=ALL -syn region texinfoMltlnAtCmd matchgroup=texinfoAtCmd start="^@quotation\s*$" end="^@end quotation\s*$" contains=ALL -syn region texinfoMltlnAtCmd matchgroup=texinfoAtCmd start="^@smalldisplay\s*$" end="^@end smalldisplay\s*$" contains=ALL -syn region texinfoMltlnAtCmd matchgroup=texinfoAtCmd start="^@smallexample\s*$" end="^@end smallexample\s*$" contains=ALL -syn region texinfoMltlnAtCmd matchgroup=texinfoAtCmd start="^@smallformat\s*$" end="^@end smallformat\s*$" contains=ALL -syn region texinfoMltlnAtCmd matchgroup=texinfoAtCmd start="^@smalllisp\s*$" end="^@end smalllisp\s*$" contains=ALL -syn region texinfoPrmAtCmd matchgroup=texinfoAtCmd start="^@exdent" skip="\\$" end="$" contains=texinfoSpecialChar oneline -syn match texinfoAtCmd "^@noindent\s*$" -syn match texinfoAtCmd "^@smallbook\s*$" - - -"lists and tables (chap. 11 in Texinfo manual) -syn match texinfoAtCmd "@asis" contained -syn match texinfoAtCmd "@columnfractions" contained -syn match texinfoAtCmd "@item" contained -syn match texinfoAtCmd "@itemx" contained -syn match texinfoAtCmd "@tab" contained -syn region texinfoMltlnAtCmd matchgroup=texinfoAtCmd start="^@enumerate" end="^@end enumerate\s*$" contains=ALL -syn region texinfoMltlnAtCmd matchgroup=texinfoAtCmd start="^@ftable" end="^@end ftable\s*$" contains=ALL -syn region texinfoMltlnNAtCmd matchgroup=texinfoAtCmd start="^@itemize" end="^@end itemize\s*$" contains=ALL -syn region texinfoMltlnNAtCmd matchgroup=texinfoAtCmd start="^@multitable" end="^@end multitable\s*$" contains=ALL -syn region texinfoMltlnNAtCmd matchgroup=texinfoAtCmd start="^@table" end="^@end table\s*$" contains=ALL -syn region texinfoMltlnAtCmd matchgroup=texinfoAtCmd start="^@vtable" end="^@end vtable\s*$" contains=ALL +let s:cpo_save = &cpo +set cpo&vim +syn match texinfoControlSequence display '\(@end [a-zA-Z@]\+\|@[a-zA-Z@]\+\)' -"indices (chap. 12 in Texinfo manual) -syn region texinfoPrmAtCmd matchgroup=texinfoAtCmd start="^@\(c\|f\|k\|p\|t\|v\)index" skip="\\$" end="$" contains=texinfoSpecialChar oneline -syn region texinfoPrmAtCmd matchgroup=texinfoAtCmd start="^@..index" skip="\\$" end="$" contains=texinfoSpecialChar oneline -"@defcodeindex and @defindex is defined after chap. 15's @def* commands (otherwise those ones will overwrite these ones) -syn match texinfoSIPar "\k\k\s*\k\k\s*$" contained -syn match texinfoAtCmd "^@syncodeindex" nextgroup=texinfoSIPar skipwhite -syn match texinfoAtCmd "^@synindex" nextgroup=texinfoSIPar skipwhite +syn match texinfoComment display '^\s*\(@comment\|@c\)\>.*$' -"special insertions (chap. 13 in Texinfo manual) -syn match texinfoSpecialChar "@\(!\|?\|@\|\s\)" -syn match texinfoSpecialChar "@{" -syn match texinfoSpecialChar "@}" -"accents -syn match texinfoSpecialChar "@=." -syn match texinfoSpecialChar "@\('\|\"\|\^\|`\)[aeiouyAEIOUY]" -syn match texinfoSpecialChar "@\~[aeinouyAEINOUY]" -syn match texinfoSpecialChar "@dotaccent{.}" -syn match texinfoSpecialChar "@H{.}" -syn match texinfoSpecialChar "@,{[cC]}" -syn match texinfoSpecialChar "@AA{}" -syn match texinfoSpecialChar "@aa{}" -syn match texinfoSpecialChar "@L{}" -syn match texinfoSpecialChar "@l{}" -syn match texinfoSpecialChar "@O{}" -syn match texinfoSpecialChar "@o{}" -syn match texinfoSpecialChar "@ringaccent{.}" -syn match texinfoSpecialChar "@tieaccent{..}" -syn match texinfoSpecialChar "@u{.}" -syn match texinfoSpecialChar "@ubaraccent{.}" -syn match texinfoSpecialChar "@udotaccent{.}" -syn match texinfoSpecialChar "@v{.}" -"ligatures -syn match texinfoSpecialChar "@AE{}" -syn match texinfoSpecialChar "@ae{}" -syn match texinfoSpecialChar "@copyright{}" -syn match texinfoSpecialChar "@bullet" contained "for tables and lists -syn match texinfoSpecialChar "@bullet{}" -syn match texinfoSpecialChar "@dotless{i}" -syn match texinfoSpecialChar "@dotless{j}" -syn match texinfoSpecialChar "@dots{}" -syn match texinfoSpecialChar "@enddots{}" -syn match texinfoSpecialChar "@equiv" contained "for tables and lists -syn match texinfoSpecialChar "@equiv{}" -syn match texinfoSpecialChar "@error{}" -syn match texinfoSpecialChar "@exclamdown{}" -syn match texinfoSpecialChar "@expansion{}" -syn match texinfoSpecialChar "@minus" contained "for tables and lists -syn match texinfoSpecialChar "@minus{}" -syn match texinfoSpecialChar "@OE{}" -syn match texinfoSpecialChar "@oe{}" -syn match texinfoSpecialChar "@point" contained "for tables and lists -syn match texinfoSpecialChar "@point{}" -syn match texinfoSpecialChar "@pounds{}" -syn match texinfoSpecialChar "@print{}" -syn match texinfoSpecialChar "@questiondown{}" -syn match texinfoSpecialChar "@result" contained "for tables and lists -syn match texinfoSpecialChar "@result{}" -syn match texinfoSpecialChar "@ss{}" -syn match texinfoSpecialChar "@TeX{}" -"other -syn region texinfoBrcPrmAtCmd matchgroup=texinfoAtCmd start="@dmn{" end="}" -syn region texinfoBrcPrmAtCmd matchgroup=texinfoAtCmd start="@footnote{" end="}" contains=texinfoSpecialChar,texinfoBrcPrmAtCmd -syn region texinfoBrcPrmAtCmd matchgroup=texinfoAtCmd start="@image{" end="}" -syn region texinfoBrcPrmAtCmd matchgroup=texinfoAtCmd start="@math{" end="}" -syn match texinfoAtCmd "@footnotestyle" nextgroup=texinfoSinglePar skipwhite +syn region texinfoCode matchgroup=texinfoControlSequence start="@code{" end="}" contains=ALL +syn region texinfoVerb matchgroup=texinfoControlSequence start="@verb{" end="}" contains=ALL +syn region texinfoArgument matchgroup=texinfoBrace start="{" end="}" contains=ALLBUT -"making and preventing breaks (chap. 14 in Texinfo manual) -syn match texinfoSpecialChar "@\(\*\|-\|\.\)" -syn match texinfoAtCmd "^@need" nextgroup=texinfoSinglePar skipwhite -syn match texinfoAtCmd "^@page\s*$" -syn match texinfoAtCmd "^@sp" nextgroup=texinfoSinglePar skipwhite -syn region texinfoMltlnAtCmd matchgroup=texinfoAtCmd start="^@group\s*$" end="^@end group\s*$" contains=ALL -syn region texinfoBrcPrmAtCmd matchgroup=texinfoAtCmd start="@hyphenation{" end="}" -syn region texinfoBrcPrmAtCmd matchgroup=texinfoAtCmd start="@w{" end="}" contains=texinfoSpecialChar,texinfoBrcPrmAtCmd +syn region texinfoExample matchgroup=texinfoControlSequence start="^@example\s*$" end="^@end example\s*$" contains=ALL +syn region texinfoVerbatim matchgroup=texinfoControlSequence start="^@verbatim\s*$" end="^@end verbatim\s*$" -"definition commands (chap. 15 in Texinfo manual) -syn match texinfoMltlnAtCmdFLine "^@def\k\+" contained -syn region texinfoMltlnAtCmd matchgroup=texinfoAtCmd start="^@def\k\+" end="^@end def\k\+$" contains=ALL - -"next 2 commands are from chap. 12; must be defined after @def* commands above to overwrite them -syn match texinfoAtCmd "@defcodeindex" nextgroup=texinfoIndexPar skipwhite -syn match texinfoAtCmd "@defindex" nextgroup=texinfoIndexPar skipwhite - - -"conditionally visible text (chap. 16 in Texinfo manual) -syn match texinfoAtCmd "^@clear" nextgroup=texinfoSinglePar skipwhite -syn region texinfoMltln2AtCmd matchgroup=texinfoAtCmd start="^@html\s*$" end="^@end html\s*$" -syn region texinfoMltlnAtCmd matchgroup=texinfoAtCmd start="^@ifclear" end="^@end ifclear\s*$" contains=ALL -syn region texinfoMltlnAtCmd matchgroup=texinfoAtCmd start="^@ifhtml" end="^@end ifhtml\s*$" contains=ALL -syn region texinfoMltlnAtCmd matchgroup=texinfoAtCmd start="^@ifinfo" end="^@end ifinfo\s*$" contains=ALL -syn region texinfoMltlnAtCmd matchgroup=texinfoAtCmd start="^@ifnothtml" end="^@end ifnothtml\s*$" contains=ALL -syn region texinfoMltlnAtCmd matchgroup=texinfoAtCmd start="^@ifnotinfo" end="^@end ifnotinfo\s*$" contains=ALL -syn region texinfoMltlnAtCmd matchgroup=texinfoAtCmd start="^@ifnottex" end="^@end ifnottex\s*$" contains=ALL -syn region texinfoMltlnAtCmd matchgroup=texinfoAtCmd start="^@ifset" end="^@end ifset\s*$" contains=ALL -syn region texinfoMltlnAtCmd matchgroup=texinfoAtCmd start="^@iftex" end="^@end iftex\s*$" contains=ALL -syn region texinfoPrmAtCmd matchgroup=texinfoAtCmd start="^@set " skip="\\$" end="$" contains=texinfoSpecialChar oneline -syn region texinfoTexCmd start="\$\$" end="\$\$" contained -syn region texinfoMltlnAtCmd matchgroup=texinfoAtCmd start="^@tex" end="^@end tex\s*$" contains=texinfoTexCmd -syn region texinfoBrcPrmAtCmd matchgroup=texinfoAtCmd start="@value{" end="}" contains=texinfoSpecialChar,texinfoBrcPrmAtCmd - - -"internationalization (chap. 17 in Texinfo manual) -syn match texinfoAtCmd "@documentencoding" nextgroup=texinfoSinglePar skipwhite -syn match texinfoAtCmd "@documentlanguage" nextgroup=texinfoIndexPar skipwhite - - -"defining new texinfo commands (chap. 18 in Texinfo manual) -syn match texinfoAtCmd "@alias" nextgroup=texinfoAssignment skipwhite -syn match texinfoDIEPar "\S*\s*,\s*\S*\s*,\s*\S*\s*$" contained -syn match texinfoAtCmd "@definfoenclose" nextgroup=texinfoDIEPar skipwhite -syn region texinfoMltlnAtCmd matchgroup=texinfoAtCmd start="^@macro" end="^@end macro\s*$" contains=ALL - - -"formatting hardcopy (chap. 19 in Texinfo manual) -syn match texinfoAtCmd "^@afourlatex\s*$" -syn match texinfoAtCmd "^@afourpaper\s*$" -syn match texinfoAtCmd "^@afourwide\s*$" -syn match texinfoAtCmd "^@finalout\s*$" -syn region texinfoPrmAtCmd matchgroup=texinfoAtCmd start="^@pagesizes" end="$" oneline - - -"creating and installing Info Files (chap. 20 in Texinfo manual) -syn region texinfoPrmAtCmd matchgroup=texinfoAtCmd start="^@dircategory" skip="\\$" end="$" oneline -syn region texinfoMltlnAtCmd matchgroup=texinfoAtCmd start="^@direntry\s*$" end="^@end direntry\s*$" contains=texinfoSpecialChar -syn match texinfoAtCmd "^@novalidate\s*$" - - -"include files (appendix E in Texinfo manual) -syn match texinfoAtCmd "^@include" nextgroup=texinfoSinglePar skipwhite - - -"page headings (appendix F in Texinfo manual) -syn match texinfoHFSpecialChar "@|" contained -syn match texinfoThisAtCmd "@thischapter" contained -syn match texinfoThisAtCmd "@thischaptername" contained -syn match texinfoThisAtCmd "@thisfile" contained -syn match texinfoThisAtCmd "@thispage" contained -syn match texinfoThisAtCmd "@thistitle" contained -syn match texinfoThisAtCmd "@today{}" contained -syn region texinfoPrmAtCmd matchgroup=texinfoAtCmd start="^@evenfooting" skip="\\$" end="$" contains=texinfoSpecialChar,texinfoBrcPrmAtCmd,texinfoThisAtCmd,texinfoHFSpecialChar oneline -syn region texinfoPrmAtCmd matchgroup=texinfoAtCmd start="^@evenheading" skip="\\$" end="$" contains=texinfoSpecialChar,texinfoBrcPrmAtCmd,texinfoThisAtCmd,texinfoHFSpecialChar oneline -syn region texinfoPrmAtCmd matchgroup=texinfoAtCmd start="^@everyfooting" skip="\\$" end="$" contains=texinfoSpecialChar,texinfoBrcPrmAtCmd,texinfoThisAtCmd,texinfoHFSpecialChar oneline -syn region texinfoPrmAtCmd matchgroup=texinfoAtCmd start="^@everyheading" skip="\\$" end="$" contains=texinfoSpecialChar,texinfoBrcPrmAtCmd,texinfoThisAtCmd,texinfoHFSpecialChar oneline -syn region texinfoPrmAtCmd matchgroup=texinfoAtCmd start="^@oddfooting" skip="\\$" end="$" contains=texinfoSpecialChar,texinfoBrcPrmAtCmd,texinfoThisAtCmd,texinfoHFSpecialChar oneline -syn region texinfoPrmAtCmd matchgroup=texinfoAtCmd start="^@oddheading" skip="\\$" end="$" contains=texinfoSpecialChar,texinfoBrcPrmAtCmd,texinfoThisAtCmd,texinfoHFSpecialChar oneline - - -"refilling paragraphs (appendix H in Texinfo manual) -syn match texinfoAtCmd "@refill" - - -syn cluster texinfoAll contains=ALLBUT,texinfoThisAtCmd,texinfoHFSpecialChar -syn cluster texinfoReducedAll contains=texinfoSpecialChar,texinfoBrcPrmAtCmd -"============================================================================== -" highlighting - -" Only when an item doesn't have highlighting yet - -hi def link texinfoSpecialChar Special -hi def link texinfoHFSpecialChar Special - -hi def link texinfoError Error -hi def link texinfoIdent Identifier -hi def link texinfoAssignment Identifier -hi def link texinfoSinglePar Identifier -hi def link texinfoIndexPar Identifier -hi def link texinfoSIPar Identifier -hi def link texinfoDIEPar Identifier -hi def link texinfoTexCmd PreProc - - -hi def link texinfoAtCmd Statement "@-command -hi def link texinfoPrmAtCmd String "@-command in one line with unknown nr. of parameters - "is String because is found as a region and is 'matchgroup'-ed - "to texinfoAtCmd -hi def link texinfoBrcPrmAtCmd String "@-command with parameter(s) in braces ({}) - "is String because is found as a region and is 'matchgroup'-ed to texinfoAtCmd -hi def link texinfoMltlnAtCmdFLine texinfoAtCmd "repeated embedded First lines in @-commands -hi def link texinfoMltlnAtCmd String "@-command in multiple lines - "is String because is found as a region and is 'matchgroup'-ed to texinfoAtCmd -hi def link texinfoMltln2AtCmd PreProc "@-command in multiple lines (same as texinfoMltlnAtCmd, just with other colors) -hi def link texinfoMltlnDMAtCmd PreProc "@-command in multiple lines (same as texinfoMltlnAtCmd, just with other colors; used for @detailmenu, which can be included in @menu) -hi def link texinfoMltlnNAtCmd Normal "@-command in multiple lines (same as texinfoMltlnAtCmd, just with other colors) -hi def link texinfoThisAtCmd Statement "@-command used in headers and footers (@this... series) - -hi def link texinfoComment Comment +syn region texinfoMenu matchgroup=texinfoControlSequence start="^@menu\s*$" end="^@end menu\s*$" +if exists("g:texinfo_delimiters") + syn match texinfoDelimiter display '[][{}]' +endif +hi def link texinfoDelimiter Delimiter +hi def link texinfoComment Comment +hi def link texinfoControlSequence Identifier +hi def link texinfoBrace Operator +hi def link texinfoArgument Special +hi def link texinfoExample String +hi def link texinfoVerbatim String +hi def link texinfoVerb String +hi def link texinfoCode String +hi def link texinfoMenu String let b:current_syntax = "texinfo" -if main_syntax == 'texinfo' - unlet main_syntax -endif - -" vim: ts=8 +let &cpo = s:cpo_save +unlet s:cpo_save diff --git a/runtime/syntax/tmux.vim b/runtime/syntax/tmux.vim index 867c033cb5..042b96e872 100644 --- a/runtime/syntax/tmux.vim +++ b/runtime/syntax/tmux.vim @@ -1,5 +1,5 @@ " Language: tmux(1) configuration file -" Version: 3.2a (git-44ada9cd) +" Version: 3.3-rc (git-964deae4) " URL: https://github.com/ericpruitt/tmux.vim/ " Maintainer: Eric Pruitt <eric.pruitt@gmail.com> " License: 2-Clause BSD (http://opensource.org/licenses/BSD-2-Clause) @@ -18,40 +18,49 @@ syntax iskeyword @,48-57,_,192-255,- syntax case match syn keyword tmuxAction none any current other -syn keyword tmuxBoolean off on +syn keyword tmuxBoolean off on yes no syn keyword tmuxTodo FIXME NOTE TODO XXX contained -syn match tmuxColour /\<colour[0-9]\+/ display +syn match tmuxColour /\<colou\?r[0-9]\+\>/ display syn match tmuxKey /\(C-\|M-\|\^\)\+\S\+/ display syn match tmuxNumber /\<\d\+\>/ display syn match tmuxFlags /\s-\a\+/ display -syn match tmuxVariable /\w\+=/ display -syn match tmuxVariableExpansion /\${\=\w\+}\=/ display -syn match tmuxControl /%\(if\|elif\|else\|endif\)/ +syn match tmuxVariableExpansion /\$\({[A-Za-z_]\w*}\|[A-Za-z_]\w*\)/ display +syn match tmuxControl /^\s*%\(if\|elif\|else\|endif\)\>/ +syn match tmuxEscape /\\\(u\x\{4\}\|U\x\{8\}\|\o\{3\}\|[\\ernt$]\)/ display syn region tmuxComment start=/#/ skip=/\\\@<!\\$/ end=/$/ contains=tmuxTodo,@Spell -syn region tmuxString start=+"+ skip=+\\\\\|\\"\|\\$+ excludenl end=+"+ end='$' contains=tmuxFormatString,@Spell -syn region tmuxString start=+'+ skip=+\\\\\|\\'\|\\$+ excludenl end=+'+ end='$' contains=tmuxFormatString,@Spell +syn region tmuxString start=+"+ skip=+\\\\\|\\"\|\\$+ excludenl end=+"+ end='$' contains=tmuxFormatString,tmuxEscape,tmuxVariableExpansion,@Spell +syn region tmuxUninterpolatedString start=+'+ skip=+\\$+ excludenl end=+'+ end='$' contains=tmuxFormatString,@Spell " TODO: Figure out how escaping works inside of #(...) and #{...} blocks. syn region tmuxFormatString start=/#[#DFhHIPSTW]/ end=// contained keepend syn region tmuxFormatString start=/#{/ skip=/#{.\{-}}/ end=/}/ keepend syn region tmuxFormatString start=/#(/ skip=/#(.\{-})/ end=/)/ contained keepend +" At the time of this writing, the latest tmux release will parse a line +" reading "abc=xyz set-option ..." as an assignment followed by a command +" hence the presence of "\s" in the "end" argument. +syn region tmuxAssignment matchgroup=tmuxVariable start=/^\s*[A-Za-z_]\w*=\@=/ skip=/\\$\|\\\s/ end=/\s\|$/ contains=tmuxString,tmuxUninterpolatedString,tmuxVariableExpansion,tmuxControl,tmuxEscape + hi def link tmuxFormatString Identifier hi def link tmuxAction Boolean hi def link tmuxBoolean Boolean hi def link tmuxCommands Keyword -hi def link tmuxControl Keyword +hi def link tmuxControl PreCondit hi def link tmuxComment Comment +hi def link tmuxEscape Special +hi def link tmuxEscapeUnquoted Special hi def link tmuxKey Special hi def link tmuxNumber Number hi def link tmuxFlags Identifier hi def link tmuxOptions Function hi def link tmuxString String hi def link tmuxTodo Todo +hi def link tmuxUninterpolatedString +\ String hi def link tmuxVariable Identifier hi def link tmuxVariableExpansion Identifier @@ -61,63 +70,85 @@ hi def link tmuxVariableExpansion Identifier if get(g:, "tmux_syntax_colors", 1) for s:i in range(0, 255) let s:bg = (!s:i || s:i == 16 || (s:i > 231 && s:i < 235)) ? 15 : "none" - exec "syn match tmuxColour" . s:i . " /\\<colour" . s:i . "\\>/ display" + exec "syn match tmuxColour" . s:i . " /\\<colou\\?r" . s:i . "\\>/ display" \ " | highlight tmuxColour" . s:i . " ctermfg=" . s:i . " ctermbg=" . s:bg endfor endif syn keyword tmuxOptions -\ backspace buffer-limit command-alias copy-command default-terminal editor -\ escape-time exit-empty activity-action assume-paste-time base-index -\ bell-action default-command default-shell default-size destroy-unattached +\ activity-action after-bind-key after-capture-pane after-copy-mode +\ after-display-message after-display-panes after-kill-pane +\ after-list-buffers after-list-clients after-list-keys after-list-panes +\ after-list-sessions after-list-windows after-load-buffer after-lock-server +\ after-new-session after-new-window after-paste-buffer after-pipe-pane +\ after-queue after-refresh-client after-rename-session after-rename-window +\ after-resize-pane after-resize-window after-save-buffer +\ after-select-layout after-select-pane after-select-window after-send-keys +\ after-set-buffer after-set-environment after-set-hook after-set-option +\ after-show-environment after-show-messages after-show-options +\ after-split-window after-unbind-key aggressive-resize alert-activity +\ alert-bell alert-silence allow-passthrough allow-rename alternate-screen +\ assume-paste-time automatic-rename automatic-rename-format backspace +\ base-index bell-action buffer-limit client-active client-attached +\ client-detached client-focus-in client-focus-out client-resized +\ client-session-changed clock-mode-colour clock-mode-style command-alias +\ copy-command copy-mode-current-match-style copy-mode-mark-style +\ copy-mode-match-style cursor-colour cursor-style default-command +\ default-shell default-size default-terminal destroy-unattached \ detach-on-destroy display-panes-active-colour display-panes-colour -\ display-panes-time display-time exit-unattached extended-keys focus-events -\ history-file history-limit key-table lock-after-time lock-command -\ message-command-style message-limit message-style aggressive-resize -\ allow-rename alternate-screen automatic-rename automatic-rename-format -\ clock-mode-colour clock-mode-style copy-mode-current-match-style -\ copy-mode-mark-style copy-mode-match-style main-pane-height -\ main-pane-width mode-keys mode-style monitor-activity monitor-bell -\ monitor-silence mouse other-pane-height other-pane-width -\ pane-active-border-style pane-base-index pane-border-format -\ pane-border-lines pane-border-status pane-border-style pane-colours prefix -\ prefix2 prompt-history-limit remain-on-exit renumber-windows repeat-time -\ set-clipboard set-titles set-titles-string silence-action status status-bg -\ status-fg status-format status-interval status-justify status-keys -\ status-left status-left-length status-left-style status-position -\ status-right status-right-length status-right-style status-style -\ synchronize-panes terminal-features terminal-overrides update-environment -\ user-keys visual-activity visual-bell visual-silence window-active-style +\ display-panes-time display-time editor escape-time exit-empty +\ exit-unattached extended-keys fill-character focus-events history-file +\ history-limit key-table lock-after-time lock-command main-pane-height +\ main-pane-width message-command-style message-limit message-style +\ mode-keys mode-style monitor-activity monitor-bell monitor-silence mouse +\ other-pane-height other-pane-width pane-active-border-style +\ pane-base-index pane-border-format pane-border-indicators +\ pane-border-lines pane-border-status pane-border-style pane-colours +\ pane-died pane-exited pane-focus-in pane-focus-out pane-mode-changed +\ pane-set-clipboard pane-title-changed popup-border-lines +\ popup-border-style popup-style prefix prefix2 prompt-history-limit +\ remain-on-exit remain-on-exit-format renumber-windows repeat-time +\ scroll-on-clear session-closed session-created session-renamed +\ session-window-changed set-clipboard set-titles set-titles-string +\ silence-action status status-bg status-fg status-format status-interval +\ status-justify status-keys status-left status-left-length +\ status-left-style status-position status-right status-right-length +\ status-right-style status-style synchronize-panes terminal-features +\ terminal-overrides update-environment user-keys visual-activity +\ visual-bell visual-silence window-active-style window-layout-changed +\ window-linked window-pane-changed window-renamed window-resized \ window-size window-status-activity-style window-status-bell-style \ window-status-current-format window-status-current-style \ window-status-format window-status-last-style window-status-separator -\ window-status-style window-style word-separators wrap-search +\ window-status-style window-style window-unlinked word-separators +\ wrap-search xterm-keys syn keyword tmuxCommands \ attach attach-session bind bind-key break-pane breakp capture-pane -\ capturep choose-buffer choose-client choose-tree clear-history clearhist +\ capturep choose-buffer choose-client choose-session choose-tree +\ choose-window clear-history clear-prompt-history clearhist clearphist \ clock-mode command-prompt confirm confirm-before copy-mode customize-mode -\ detach detach-client display display-menu display-message display-panes -\ display-popup displayp find-window findw if if-shell join-pane joinp -\ kill-pane kill-server kill-session kill-window killp has has-session killw +\ delete-buffer deleteb detach detach-client display display-menu +\ display-message display-panes display-popup displayp find-window findw has +\ has-session if if-shell info join-pane joinp kill-pane kill-server +\ kill-session kill-window killp killw last last-pane last-window lastp \ link-window linkw list-buffers list-clients list-commands list-keys \ list-panes list-sessions list-windows load-buffer loadb lock lock-client -\ lock-server lock-session lockc last-pane lastp locks ls last last-window -\ lsb delete-buffer deleteb lsc lscm lsk lsp lsw menu move-pane move-window -\ clear-prompt-history clearphist movep movew new new-session new-window -\ neww next next-layout next-window nextl paste-buffer pasteb pipe-pane -\ pipep popup prev previous-layout previous-window prevl refresh -\ refresh-client rename rename-session rename-window renamew resize-pane -\ resize-window resizep resizew respawn-pane respawn-window respawnp -\ respawnw rotate-window rotatew run run-shell save-buffer saveb -\ select-layout select-pane select-window selectl selectp selectw send -\ send-keys send-prefix set set-buffer set-environment set-hook set-option +\ lock-server lock-session lockc locks ls lsb lsc lscm lsk lsp lsw menu +\ move-pane move-window movep movew new new-session new-window neww next +\ next-layout next-window nextl paste-buffer pasteb pipe-pane pipep popup +\ prev previous-layout previous-window prevl refresh refresh-client rename +\ rename-session rename-window renamew resize-pane resize-window resizep +\ resizew respawn-pane respawn-window respawnp respawnw rotate-window +\ rotatew run run-shell save-buffer saveb select-layout select-pane +\ select-window selectl selectp selectw send send-keys send-prefix +\ server-info set set-buffer set-environment set-hook set-option \ set-window-option setb setenv setw show show-buffer show-environment \ show-hooks show-messages show-options show-prompt-history \ show-window-options showb showenv showmsgs showphist showw source -\ source-file split-window splitw start start-server suspend-client suspendc -\ swap-pane swap-window swapp swapw switch-client switchc unbind unbind-key -\ unlink-window unlinkw wait wait-for +\ source-file split-pane split-window splitp splitw start start-server +\ suspend-client suspendc swap-pane swap-window swapp swapw switch-client +\ switchc unbind unbind-key unlink-window unlinkw wait wait-for let &cpo = s:original_cpo unlet! s:original_cpo s:bg s:i diff --git a/runtime/syntax/vb.vim b/runtime/syntax/vb.vim index 8ddb1efac3..607f6130ba 100644 --- a/runtime/syntax/vb.vim +++ b/runtime/syntax/vb.vim @@ -1,9 +1,11 @@ " Vim syntax file -" Language: Visual Basic -" Maintainer: Tim Chase <vb.vim@tim.thechases.com> -" Former Maintainer: Robert M. Cortopassi <cortopar@mindspring.com> -" (tried multiple times to contact, but email bounced) +" Language: Visual Basic +" Maintainer: Doug Kearns <dougkearns@gmail.com> +" Former Maintainer: Tim Chase <vb.vim@tim.thechases.com> +" Former Maintainer: Robert M. Cortopassi <cortopar@mindspring.com> +" (tried multiple times to contact, but email bounced) " Last Change: +" 2021 Nov 26 Incorporated additions from Doug Kearns " 2005 May 25 Synched with work by Thomas Barthel " 2004 May 30 Added a few keywords @@ -13,7 +15,7 @@ " quit when a syntax file was already loaded if exists("b:current_syntax") - finish + finish endif " VB is case insensitive @@ -233,7 +235,7 @@ syn keyword vbKeyword Public PublicNotCreateable OnNewProcessSingleUse syn keyword vbKeyword InSameProcessMultiUse GlobalMultiUse Resume Seek syn keyword vbKeyword Set Static Step String Time WithEvents -syn keyword vbTodo contained TODO +syn keyword vbTodo contained TODO "Datatypes syn keyword vbTypes Boolean Byte Currency Date Decimal Double Empty @@ -319,46 +321,54 @@ syn match vbNumber "\<\d\+\>" syn match vbNumber "\<\d\+\.\d*\>" "floating point number, starting with a dot syn match vbNumber "\.\d\+\>" -"syn match vbNumber "{[[:xdigit:]-]\+}\|&[hH][[:xdigit:]]\+&" -"syn match vbNumber ":[[:xdigit:]]\+" -"syn match vbNumber "[-+]\=\<\d\+\>" -syn match vbFloat "[-+]\=\<\d\+[eE][\-+]\=\d\+" -syn match vbFloat "[-+]\=\<\d\+\.\d*\([eE][\-+]\=\d\+\)\=" -syn match vbFloat "[-+]\=\<\.\d\+\([eE][\-+]\=\d\+\)\=" +"syn match vbNumber "{[[:xdigit:]-]\+}\|&[hH][[:xdigit:]]\+&" +"syn match vbNumber ":[[:xdigit:]]\+" +"syn match vbNumber "[-+]\=\<\d\+\>" +syn match vbFloat "[-+]\=\<\d\+[eE][\-+]\=\d\+" +syn match vbFloat "[-+]\=\<\d\+\.\d*\([eE][\-+]\=\d\+\)\=" +syn match vbFloat "[-+]\=\<\.\d\+\([eE][\-+]\=\d\+\)\=" -" String and Character contstants +" String and Character constants syn region vbString start=+"+ end=+"\|$+ syn region vbComment start="\(^\|\s\)REM\s" end="$" contains=vbTodo syn region vbComment start="\(^\|\s\)\'" end="$" contains=vbTodo -syn match vbLineNumber "^\d\+\(\s\|$\)" -syn match vbTypeSpecifier "[a-zA-Z0-9][\$%&!#]"ms=s+1 +syn match vbLineLabel "^\h\w\+:" +syn match vbLineNumber "^\d\+\(:\|\s\|$\)" +syn match vbTypeSpecifier "\<\a\w*[@\$%&!#]"ms=s+1 syn match vbTypeSpecifier "#[a-zA-Z0-9]"me=e-1 +" Conditional Compilation +syn match vbPreProc "^#const\>" +syn region vbPreProc matchgroup=PreProc start="^#if\>" end="\<then\>" transparent contains=TOP +syn region vbPreProc matchgroup=PreProc start="^#elseif\>" end="\<then\>" transparent contains=TOP +syn match vbPreProc "^#else\>" +syn match vbPreProc "^#end\s*if\>" " Define the default highlighting. " Only when an item doesn't have highlighting yet -hi def link vbBoolean Boolean -hi def link vbLineNumber Comment -hi def link vbComment Comment -hi def link vbConditional Conditional -hi def link vbConst Constant -hi def link vbDefine Constant -hi def link vbError Error -hi def link vbFunction Identifier -hi def link vbIdentifier Identifier -hi def link vbNumber Number -hi def link vbFloat Float -hi def link vbMethods PreProc -hi def link vbOperator Operator -hi def link vbRepeat Repeat -hi def link vbString String -hi def link vbStatement Statement -hi def link vbKeyword Statement -hi def link vbEvents Special -hi def link vbTodo Todo -hi def link vbTypes Type -hi def link vbTypeSpecifier Type - +hi def link vbBoolean Boolean +hi def link vbLineNumber Comment +hi def link vbLineLabel Comment +hi def link vbComment Comment +hi def link vbConditional Conditional +hi def link vbConst Constant +hi def link vbDefine Constant +hi def link vbError Error +hi def link vbFunction Identifier +hi def link vbIdentifier Identifier +hi def link vbNumber Number +hi def link vbFloat Float +hi def link vbMethods PreProc +hi def link vbOperator Operator +hi def link vbRepeat Repeat +hi def link vbString String +hi def link vbStatement Statement +hi def link vbKeyword Statement +hi def link vbEvents Special +hi def link vbTodo Todo +hi def link vbTypes Type +hi def link vbTypeSpecifier Type +hi def link vbPreProc PreProc let b:current_syntax = "vb" diff --git a/runtime/syntax/vim.vim b/runtime/syntax/vim.vim index f7b5ce0f63..df0572adb8 100644 --- a/runtime/syntax/vim.vim +++ b/runtime/syntax/vim.vim @@ -46,14 +46,14 @@ syn match vimTermOption contained "t_%i" syn match vimTermOption contained "t_k;" " unsupported settings: these are supported by vi but don't do anything in vim {{{2 -syn keyword vimErrSetting contained hardtabs ht w1200 w300 w9600 +syn keyword vimErrSetting contained hardtabs ht w1200 w300 w9600 "}}}2 syn case ignore " Highlight commonly used Groupnames {{{2 -syn keyword vimGroup contained Comment Constant String Character Number Boolean Float Identifier Function Statement Conditional Repeat Label Operator Keyword Exception PreProc Include Define Macro PreCondit Type StorageClass Structure Typedef Special SpecialChar Tag Delimiter SpecialComment Debug Underlined Ignore Error Todo +syn keyword vimGroup contained Comment Constant String Character Number Boolean Float Identifier Function Statement Conditional Repeat Label Operator Keyword Exception PreProc Include Define Macro PreCondit Type StorageClass Structure Typedef Special SpecialChar Tag Delimiter SpecialComment Debug Underlined Ignore Error Todo " Default highlighting groups {{{2 -syn keyword vimHLGroup contained ColorColumn Cursor CursorColumn CursorIM CursorLine CursorLineNr DiffAdd DiffChange DiffDelete DiffText Directory EndOfBuffer ErrorMsg FoldColumn Folded IncSearch LineNr MatchParen Menu ModeMsg MoreMsg NonText Normal Pmenu PmenuSbar PmenuSel PmenuThumb Question QuickFixLine Scrollbar Search SignColumn SpecialKey SpellBad SpellCap SpellLocal SpellRare StatusLine StatusLineNC TabLine TabLineFill TabLineSel Title Tooltip VertSplit Visual WarningMsg WildMenu +syn keyword vimHLGroup contained ColorColumn Cursor CursorColumn CursorIM CursorLine CursorLineFold CursorLineNr CursorLineSign DiffAdd DiffChange DiffDelete DiffText Directory EndOfBuffer ErrorMsg FoldColumn Folded IncSearch LineNr MatchParen Menu ModeMsg MoreMsg NonText Normal Pmenu PmenuSbar PmenuSel PmenuThumb Question QuickFixLine Scrollbar Search SignColumn SpecialKey SpellBad SpellCap SpellLocal SpellRare StatusLine StatusLineNC TabLine TabLineFill TabLineSel Title Tooltip VertSplit Visual WarningMsg WildMenu syn match vimHLGroup contained "Conceal" syn keyword vimOnlyHLGroup contained LineNrAbove LineNrBelow StatusLineTerm Terminal VisualNOS syn keyword nvimHLGroup contained Substitute TermCursor TermCursorNC @@ -88,10 +88,10 @@ if exists("g:vimsyn_folding") && g:vimsyn_folding =~# '[afhlmpPrt]' else com! -nargs=* VimFoldm <args> endif - if g:vimsyn_folding =~# 'p' - com! -nargs=* VimFoldp <args> fold - else - com! -nargs=* VimFoldp <args> + if g:vimsyn_folding =~# 'p' + com! -nargs=* VimFoldp <args> fold + else + com! -nargs=* VimFoldp <args> endif if g:vimsyn_folding =~# 'P' com! -nargs=* VimFoldP <args> fold @@ -148,8 +148,8 @@ syn match vimNumber '-\d\+\%(\.\d\+\%([eE][+-]\=\d\+\)\=\)\=' skipwhite nextgro syn match vimNumber '\<0[xX]\x\+' skipwhite nextgroup=vimGlobal,vimSubst,vimCommand,vimComment,vim9Comment syn match vimNumber '\%(^\|\A\)\zs#\x\{6}' skipwhite nextgroup=vimGlobal,vimSubst,vimCommand,vimComment,vim9Comment syn match vimNumber '\<0[zZ][a-zA-Z0-9.]\+' skipwhite nextgroup=vimGlobal,vimSubst,vimCommand,vimComment,vim9Comment -syn match vimNumber '0[0-7]\+' skipwhite nextgroup=vimGlobal,vimSubst,vimCommand,vimComment,vim9Comment -syn match vimNumber '0[bB][01]\+' skipwhite nextgroup=vimGlobal,vimSubst,vimCommand,vimComment,vim9Comment +syn match vimNumber '0[0-7]\+' skipwhite nextgroup=vimGlobal,vimSubst,vimCommand,vimComment,vim9Comment +syn match vimNumber '0[bB][01]\+' skipwhite nextgroup=vimGlobal,vimSubst,vimCommand,vimComment,vim9Comment " All vimCommands are contained by vimIsCommand. {{{2 syn match vimCmdSep "[:|]\+" skipwhite nextgroup=vimAddress,vimAutoCmd,vimEcho,vimIsCommand,vimExtCmd,vimFilter,vimLet,vimMap,vimMark,vimSet,vimSyntax,vimUserCmd @@ -202,7 +202,7 @@ syn keyword vimAugroupKey contained aug[roup] " Operators: {{{2 " ========= -syn cluster vimOperGroup contains=vimEnvvar,vimFunc,vimFuncVar,vimOper,vimOperParen,vimNumber,vimString,vimType,vimRegister,vimContinue,vim9Comment +syn cluster vimOperGroup contains=vimEnvvar,vimFunc,vimFuncVar,vimOper,vimOperParen,vimNumber,vimString,vimType,vimRegister,vimContinue,vim9Comment,vimVar syn match vimOper "||\|&&\|[-+.!]" skipwhite nextgroup=vimString,vimSpecFile syn match vimOper "\%#=1\(==\|!=\|>=\|<=\|=\~\|!\~\|>\|<\|=\|!\~#\)[?#]\{0,2}" skipwhite nextgroup=vimString,vimSpecFile syn match vimOper "\(\<is\|\<isnot\)[?#]\{0,2}\>" skipwhite nextgroup=vimString,vimSpecFile @@ -437,8 +437,9 @@ syn case match " User Function Highlighting: {{{2 " (following Gautam Iyer's suggestion) " ========================== -syn match vimFunc "\%(\%([sSgGbBwWtTlL]:\|<[sS][iI][dD]>\)\=\%(\w\+\.\)*\I[a-zA-Z0-9_.]*\)\ze\s*(" contains=vimFuncName,vimUserFunc,vimExecute -syn match vimUserFunc contained "\%(\%([sSgGbBwWtTlL]:\|<[sS][iI][dD]>\)\=\%(\w\+\.\)*\I[a-zA-Z0-9_.]*\)\|\<\u[a-zA-Z0-9.]*\>\|\<if\>" contains=vimNotation +syn match vimFunc "\%(\%([sSgGbBwWtTlL]:\|<[sS][iI][dD]>\)\=\%(\w\+\.\)*\I[a-zA-Z0-9_.]*\)\ze\s*(" contains=vimCommand,vimFuncEcho,vimFuncName,vimUserFunc,vimExecute +syn match vimUserFunc contained "\%(\%([sSgGbBwWtTlL]:\|<[sS][iI][dD]>\)\=\%(\w\+\.\)*\I[a-zA-Z0-9_.]*\)\|\<\u[a-zA-Z0-9.]*\>\|\<if\>" contains=vimCommand,vimNotation +syn keyword vimFuncEcho contained ec ech echo " User Command Highlighting: {{{2 "syn match vimUsrCmd '^\s*\zs\u\w*.*$' @@ -574,7 +575,7 @@ syn match vimHiBang contained "!" skipwhite nextgroup=@vimHighlightCluster syn match vimHiGroup contained "\i\+" syn case ignore -syn keyword vimHiAttrib contained none bold inverse italic nocombine reverse standout strikethrough underline undercurl +syn keyword vimHiAttrib contained none bold inverse italic nocombine reverse standout strikethrough underline underlineline undercurl underdot underdash syn keyword vimFgBgAttrib contained none bg background fg foreground syn case match syn match vimHiAttribList contained "\i\+" contains=vimHiAttrib @@ -741,10 +742,10 @@ if g:vimsyn_embed =~# 'P' && filereadable(s:pythonpath) unlet! b:current_syntax syn cluster vimFuncBodyList add=vimPythonRegion exe "syn include @vimPythonScript ".s:pythonpath - VimFoldP syn region vimPythonRegion matchgroup=vimScriptDelim start=+py\%[thon][3x]\=\s*<<\s*\z(\S*\)\ze\(\s*#.*\)\=$+ end=+^\z1\ze\(\s*".*\)\=$+ contains=@vimPythonScript - VimFoldP syn region vimPythonRegion matchgroup=vimScriptDelim start=+py\%[thon][3x]\=\s*<<\s*$+ end=+\.$+ contains=@vimPythonScript - VimFoldP syn region vimPythonRegion matchgroup=vimScriptDelim start=+Py\%[thon]2or3\s*<<\s*\z(\S*\)\ze\(\s*#.*\)\=$+ end=+^\z1\ze\(\s*".*\)\=$+ contains=@vimPythonScript - VimFoldP syn region vimPythonRegion matchgroup=vimScriptDelim start=+Py\%[thon]2or3\=\s*<<\s*$+ end=+\.$+ contains=@vimPythonScript + VimFoldP syn region vimPythonRegion matchgroup=vimScriptDelim start=+py\%[thon][3x]\=\s*<<\s*\%(trim\s*\)\=\z(\S*\)\ze\(\s*#.*\)\=$+ end=+^\z1\ze\(\s*".*\)\=$+ contains=@vimPythonScript + VimFoldP syn region vimPythonRegion matchgroup=vimScriptDelim start=+py\%[thon][3x]\=\s*<<\s*\%(trim\s*\)\=$+ end=+\.$+ contains=@vimPythonScript + VimFoldP syn region vimPythonRegion matchgroup=vimScriptDelim start=+Py\%[thon]2or3\s*<<\s*\%(trim\s*\)\=\z(\S*\)\ze\(\s*#.*\)\=$+ end=+^\z1\ze\(\s*".*\)\=$+ contains=@vimPythonScript + VimFoldP syn region vimPythonRegion matchgroup=vimScriptDelim start=+Py\%[thon]2or3\=\s*<<\s*\%(trim\s*\)\=$+ end=+\.$+ contains=@vimPythonScript syn cluster vimFuncBodyList add=vimPythonRegion else syn region vimEmbedError start=+py\%[thon]3\=\s*<<\s*\z(.*\)$+ end=+^\z1$+ @@ -875,6 +876,7 @@ if !exists("skip_vim_syntax_inits") hi def link vimError Error hi def link vimFBVar vimVar hi def link vimFgBgAttrib vimHiAttrib + hi def link vimFuncEcho vimCommand hi def link vimHiCtermul vimHiTerm hi def link vimFold Folded hi def link vimFTCmd vimCommand diff --git a/runtime/syntax/xml.vim b/runtime/syntax/xml.vim index 7c9791a7cc..d99f8b467a 100644 --- a/runtime/syntax/xml.vim +++ b/runtime/syntax/xml.vim @@ -10,6 +10,7 @@ " 20190923 - Fix xmlEndTag to match xmlTag (vim/vim#884) " 20190924 - Fix xmlAttribute property (amadeus/vim-xml@d8ce1c946) " 20191103 - Enable spell checking globally +" 20210428 - Improve syntax synchronizing " CONFIGURATION: " syntax folding can be turned on by @@ -302,9 +303,12 @@ unlet b:current_syntax " synchronizing -" TODO !!! to be improved !!! -syn sync match xmlSyncDT grouphere xmlDocType +\_.\(<!DOCTYPE\)\@=+ +syn sync match xmlSyncComment grouphere xmlComment +<!--+ +syn sync match xmlSyncComment groupthere NONE +-->+ + +" The following is slow on large documents (and the doctype is optional +" syn sync match xmlSyncDT grouphere xmlDocType +\_.\(<!DOCTYPE\)\@=+ " syn sync match xmlSyncDT groupthere NONE +]>+ if exists('g:xml_syntax_folding') @@ -313,7 +317,7 @@ if exists('g:xml_syntax_folding') syn sync match xmlSync groupthere xmlRegion +</[^ /!?<>"']\+>+ endif -syn sync minlines=100 +syn sync minlines=100 maxlines=200 " The default highlighting. @@ -354,4 +358,4 @@ let b:current_syntax = "xml" let &cpo = s:xml_cpo_save unlet s:xml_cpo_save -" vim: ts=8 +" vim: ts=4 diff --git a/runtime/syntax/zsh.vim b/runtime/syntax/zsh.vim index 819c419228..bab89b916e 100644 --- a/runtime/syntax/zsh.vim +++ b/runtime/syntax/zsh.vim @@ -41,11 +41,12 @@ if get(g:, 'zsh_fold_enable', 0) setlocal foldmethod=syntax endif +syn match zshQuoted '\\.' syn match zshPOSIXQuoted '\\[xX][0-9a-fA-F]\{1,2}' syn match zshPOSIXQuoted '\\[0-7]\{1,3}' syn match zshPOSIXQuoted '\\u[0-9a-fA-F]\{1,4}' syn match zshPOSIXQuoted '\\U[1-9a-fA-F]\{1,8}' -syn match zshQuoted '\\.' + syn region zshString matchgroup=zshStringDelimiter start=+"+ end=+"+ \ contains=zshQuoted,@zshDerefs,@zshSubst fold syn region zshString matchgroup=zshStringDelimiter start=+'+ end=+'+ fold @@ -133,217 +134,15 @@ syn keyword zshCommands alias autoload bg bindkey break bye cap cd \ zmodload zparseopts zprof zpty zrecompile \ zregexparse zsocket zstyle ztcp -" Options, generated by: echo ${(j:\n:)options[(I)*]} | sort -" Create a list of option names from zsh source dir: -" #!/bin/zsh -" topdir=/path/to/zsh-xxx -" grep '^pindex([A-Za-z_]*)$' $topdir/Doc/Zsh/options.yo | -" while read opt -" do -" echo ${${(L)opt#pindex\(}%\)} -" done - +" Options, generated by from the zsh source with the make-options.zsh script. syn case ignore - -syn match zshOptStart /^\s*\%(\%(\%(un\)\?setopt\)\|set\s+[-+]o\)/ nextgroup=zshOption skipwhite -syn match zshOption / - \ \%(\%(\<no_\?\)\?aliases\>\)\| - \ \%(\%(\<no_\?\)\?aliasfuncdef\>\)\|\%(\%(no_\?\)\?alias_func_def\>\)\| - \ \%(\%(\<no_\?\)\?allexport\>\)\|\%(\%(no_\?\)\?all_export\>\)\| - \ \%(\%(\<no_\?\)\?alwayslastprompt\>\)\|\%(\%(no_\?\)\?always_last_prompt\>\)\|\%(\%(no_\?\)\?always_lastprompt\>\)\| - \ \%(\%(\<no_\?\)\?alwaystoend\>\)\|\%(\%(no_\?\)\?always_to_end\>\)\| - \ \%(\%(\<no_\?\)\?appendcreate\>\)\|\%(\%(no_\?\)\?append_create\>\)\| - \ \%(\%(\<no_\?\)\?appendhistory\>\)\|\%(\%(no_\?\)\?append_history\>\)\| - \ \%(\%(\<no_\?\)\?autocd\>\)\|\%(\%(no_\?\)\?auto_cd\>\)\| - \ \%(\%(\<no_\?\)\?autocontinue\>\)\|\%(\%(no_\?\)\?auto_continue\>\)\| - \ \%(\%(\<no_\?\)\?autolist\>\)\|\%(\%(no_\?\)\?auto_list\>\)\| - \ \%(\%(\<no_\?\)\?automenu\>\)\|\%(\%(no_\?\)\?auto_menu\>\)\| - \ \%(\%(\<no_\?\)\?autonamedirs\>\)\|\%(\%(no_\?\)\?auto_name_dirs\>\)\| - \ \%(\%(\<no_\?\)\?autoparamkeys\>\)\|\%(\%(no_\?\)\?auto_param_keys\>\)\| - \ \%(\%(\<no_\?\)\?autoparamslash\>\)\|\%(\%(no_\?\)\?auto_param_slash\>\)\| - \ \%(\%(\<no_\?\)\?autopushd\>\)\|\%(\%(no_\?\)\?auto_pushd\>\)\| - \ \%(\%(\<no_\?\)\?autoremoveslash\>\)\|\%(\%(no_\?\)\?auto_remove_slash\>\)\| - \ \%(\%(\<no_\?\)\?autoresume\>\)\|\%(\%(no_\?\)\?auto_resume\>\)\| - \ \%(\%(\<no_\?\)\?badpattern\>\)\|\%(\%(no_\?\)\?bad_pattern\>\)\| - \ \%(\%(\<no_\?\)\?banghist\>\)\|\%(\%(no_\?\)\?bang_hist\>\)\| - \ \%(\%(\<no_\?\)\?bareglobqual\>\)\|\%(\%(no_\?\)\?bare_glob_qual\>\)\| - \ \%(\%(\<no_\?\)\?bashautolist\>\)\|\%(\%(no_\?\)\?bash_auto_list\>\)\| - \ \%(\%(\<no_\?\)\?bashrematch\>\)\|\%(\%(no_\?\)\?bash_rematch\>\)\| - \ \%(\%(\<no_\?\)\?beep\>\)\| - \ \%(\%(\<no_\?\)\?bgnice\>\)\|\%(\%(no_\?\)\?bg_nice\>\)\| - \ \%(\%(\<no_\?\)\?braceccl\>\)\|\%(\%(no_\?\)\?brace_ccl\>\)\| - \ \%(\%(\<no_\?\)\?braceexpand\>\)\|\%(\%(no_\?\)\?brace_expand\>\)\| - \ \%(\%(\<no_\?\)\?bsdecho\>\)\|\%(\%(no_\?\)\?bsd_echo\>\)\| - \ \%(\%(\<no_\?\)\?caseglob\>\)\|\%(\%(no_\?\)\?case_glob\>\)\| - \ \%(\%(\<no_\?\)\?casematch\>\)\|\%(\%(no_\?\)\?case_match\>\)\| - \ \%(\%(\<no_\?\)\?cbases\>\)\|\%(\%(no_\?\)\?c_bases\>\)\| - \ \%(\%(\<no_\?\)\?cdablevars\>\)\|\%(\%(no_\?\)\?cdable_vars\>\)\|\%(\%(no_\?\)\?cd_able_vars\>\)\| - \ \%(\%(\<no_\?\)\?cdsilent\>\)\|\%(\%(no_\?\)\?cd_silent\>\)\|\%(\%(no_\?\)\?cd_silent\>\)\| - \ \%(\%(\<no_\?\)\?chasedots\>\)\|\%(\%(no_\?\)\?chase_dots\>\)\| - \ \%(\%(\<no_\?\)\?chaselinks\>\)\|\%(\%(no_\?\)\?chase_links\>\)\| - \ \%(\%(\<no_\?\)\?checkjobs\>\)\|\%(\%(no_\?\)\?check_jobs\>\)\| - \ \%(\%(\<no_\?\)\?checkrunningjobs\>\)\|\%(\%(no_\?\)\?check_running_jobs\>\)\| - \ \%(\%(\<no_\?\)\?clobber\>\)\| - \ \%(\%(\<no_\?\)\?clobberempty\>\)\|\%(\%(no_\?\)\?clobber_empty\>\)\| - \ \%(\%(\<no_\?\)\?combiningchars\>\)\|\%(\%(no_\?\)\?combining_chars\>\)\| - \ \%(\%(\<no_\?\)\?completealiases\>\)\|\%(\%(no_\?\)\?complete_aliases\>\)\| - \ \%(\%(\<no_\?\)\?completeinword\>\)\|\%(\%(no_\?\)\?complete_in_word\>\)\| - \ \%(\%(\<no_\?\)\?continueonerror\>\)\|\%(\%(no_\?\)\?continue_on_error\>\)\| - \ \%(\%(\<no_\?\)\?correct\>\)\| - \ \%(\%(\<no_\?\)\?correctall\>\)\|\%(\%(no_\?\)\?correct_all\>\)\| - \ \%(\%(\<no_\?\)\?cprecedences\>\)\|\%(\%(no_\?\)\?c_precedences\>\)\| - \ \%(\%(\<no_\?\)\?cshjunkiehistory\>\)\|\%(\%(no_\?\)\?csh_junkie_history\>\)\| - \ \%(\%(\<no_\?\)\?cshjunkieloops\>\)\|\%(\%(no_\?\)\?csh_junkie_loops\>\)\| - \ \%(\%(\<no_\?\)\?cshjunkiequotes\>\)\|\%(\%(no_\?\)\?csh_junkie_quotes\>\)\| - \ \%(\%(\<no_\?\)\?csh_nullcmd\>\)\|\%(\%(no_\?\)\?csh_null_cmd\>\)\|\%(\%(no_\?\)\?cshnullcmd\>\)\|\%(\%(no_\?\)\?csh_null_cmd\>\)\| - \ \%(\%(\<no_\?\)\?cshnullglob\>\)\|\%(\%(no_\?\)\?csh_null_glob\>\)\| - \ \%(\%(\<no_\?\)\?debugbeforecmd\>\)\|\%(\%(no_\?\)\?debug_before_cmd\>\)\| - \ \%(\%(\<no_\?\)\?dotglob\>\)\|\%(\%(no_\?\)\?dot_glob\>\)\| - \ \%(\%(\<no_\?\)\?dvorak\>\)\| - \ \%(\%(\<no_\?\)\?emacs\>\)\| - \ \%(\%(\<no_\?\)\?equals\>\)\| - \ \%(\%(\<no_\?\)\?errexit\>\)\|\%(\%(no_\?\)\?err_exit\>\)\| - \ \%(\%(\<no_\?\)\?errreturn\>\)\|\%(\%(no_\?\)\?err_return\>\)\| - \ \%(\%(\<no_\?\)\?evallineno\>\)\|\%(\%(no_\?\)\?eval_lineno\>\)\| - \ \%(\%(\<no_\?\)\?exec\>\)\| - \ \%(\%(\<no_\?\)\?extendedglob\>\)\|\%(\%(no_\?\)\?extended_glob\>\)\| - \ \%(\%(\<no_\?\)\?extendedhistory\>\)\|\%(\%(no_\?\)\?extended_history\>\)\| - \ \%(\%(\<no_\?\)\?flowcontrol\>\)\|\%(\%(no_\?\)\?flow_control\>\)\| - \ \%(\%(\<no_\?\)\?forcefloat\>\)\|\%(\%(no_\?\)\?force_float\>\)\| - \ \%(\%(\<no_\?\)\?functionargzero\>\)\|\%(\%(no_\?\)\?function_argzero\>\)\|\%(\%(no_\?\)\?function_arg_zero\>\)\| - \ \%(\%(\<no_\?\)\?glob\>\)\| - \ \%(\%(\<no_\?\)\?globalexport\>\)\|\%(\%(no_\?\)\?global_export\>\)\| - \ \%(\%(\<no_\?\)\?globalrcs\>\)\|\%(\%(no_\?\)\?global_rcs\>\)\| - \ \%(\%(\<no_\?\)\?globassign\>\)\|\%(\%(no_\?\)\?glob_assign\>\)\| - \ \%(\%(\<no_\?\)\?globcomplete\>\)\|\%(\%(no_\?\)\?glob_complete\>\)\| - \ \%(\%(\<no_\?\)\?globdots\>\)\|\%(\%(no_\?\)\?glob_dots\>\)\| - \ \%(\%(\<no_\?\)\?glob_subst\>\)\|\%(\%(no_\?\)\?globsubst\>\)\| - \ \%(\%(\<no_\?\)\?globstarshort\>\)\|\%(\%(no_\?\)\?glob_star_short\>\)\| - \ \%(\%(\<no_\?\)\?hashall\>\)\|\%(\%(no_\?\)\?hash_all\>\)\| - \ \%(\%(\<no_\?\)\?hashcmds\>\)\|\%(\%(no_\?\)\?hash_cmds\>\)\| - \ \%(\%(\<no_\?\)\?hashdirs\>\)\|\%(\%(no_\?\)\?hash_dirs\>\)\| - \ \%(\%(\<no_\?\)\?hashexecutablesonly\>\)\|\%(\%(no_\?\)\?hash_executables_only\>\)\| - \ \%(\%(\<no_\?\)\?hashlistall\>\)\|\%(\%(no_\?\)\?hash_list_all\>\)\| - \ \%(\%(\<no_\?\)\?histallowclobber\>\)\|\%(\%(no_\?\)\?hist_allow_clobber\>\)\| - \ \%(\%(\<no_\?\)\?histappend\>\)\|\%(\%(no_\?\)\?hist_append\>\)\| - \ \%(\%(\<no_\?\)\?histbeep\>\)\|\%(\%(no_\?\)\?hist_beep\>\)\| - \ \%(\%(\<no_\?\)\?hist_expand\>\)\|\%(\%(no_\?\)\?histexpand\>\)\| - \ \%(\%(\<no_\?\)\?hist_expire_dups_first\>\)\|\%(\%(no_\?\)\?histexpiredupsfirst\>\)\| - \ \%(\%(\<no_\?\)\?histfcntllock\>\)\|\%(\%(no_\?\)\?hist_fcntl_lock\>\)\| - \ \%(\%(\<no_\?\)\?histfindnodups\>\)\|\%(\%(no_\?\)\?hist_find_no_dups\>\)\| - \ \%(\%(\<no_\?\)\?histignorealldups\>\)\|\%(\%(no_\?\)\?hist_ignore_all_dups\>\)\| - \ \%(\%(\<no_\?\)\?histignoredups\>\)\|\%(\%(no_\?\)\?hist_ignore_dups\>\)\| - \ \%(\%(\<no_\?\)\?histignorespace\>\)\|\%(\%(no_\?\)\?hist_ignore_space\>\)\| - \ \%(\%(\<no_\?\)\?histlexwords\>\)\|\%(\%(no_\?\)\?hist_lex_words\>\)\| - \ \%(\%(\<no_\?\)\?histnofunctions\>\)\|\%(\%(no_\?\)\?hist_no_functions\>\)\| - \ \%(\%(\<no_\?\)\?histnostore\>\)\|\%(\%(no_\?\)\?hist_no_store\>\)\| - \ \%(\%(\<no_\?\)\?histreduceblanks\>\)\|\%(\%(no_\?\)\?hist_reduce_blanks\>\)\| - \ \%(\%(\<no_\?\)\?histsavebycopy\>\)\|\%(\%(no_\?\)\?hist_save_by_copy\>\)\| - \ \%(\%(\<no_\?\)\?histsavenodups\>\)\|\%(\%(no_\?\)\?hist_save_no_dups\>\)\| - \ \%(\%(\<no_\?\)\?histsubstpattern\>\)\|\%(\%(no_\?\)\?hist_subst_pattern\>\)\| - \ \%(\%(\<no_\?\)\?histverify\>\)\|\%(\%(no_\?\)\?hist_verify\>\)\| - \ \%(\%(\<no_\?\)\?hup\>\)\| - \ \%(\%(\<no_\?\)\?ignorebraces\>\)\|\%(\%(no_\?\)\?ignore_braces\>\)\| - \ \%(\%(\<no_\?\)\?ignoreclosebraces\>\)\|\%(\%(no_\?\)\?ignore_close_braces\>\)\| - \ \%(\%(\<no_\?\)\?ignoreeof\>\)\|\%(\%(no_\?\)\?ignore_eof\>\)\| - \ \%(\%(\<no_\?\)\?incappendhistory\>\)\|\%(\%(no_\?\)\?inc_append_history\>\)\| - \ \%(\%(\<no_\?\)\?incappendhistorytime\>\)\|\%(\%(no_\?\)\?inc_append_history_time\>\)\| - \ \%(\%(\<no_\?\)\?interactive\>\)\| - \ \%(\%(\<no_\?\)\?interactivecomments\>\)\|\%(\%(no_\?\)\?interactive_comments\>\)\| - \ \%(\%(\<no_\?\)\?ksharrays\>\)\|\%(\%(no_\?\)\?ksh_arrays\>\)\| - \ \%(\%(\<no_\?\)\?kshautoload\>\)\|\%(\%(no_\?\)\?ksh_autoload\>\)\| - \ \%(\%(\<no_\?\)\?kshglob\>\)\|\%(\%(no_\?\)\?ksh_glob\>\)\| - \ \%(\%(\<no_\?\)\?kshoptionprint\>\)\|\%(\%(no_\?\)\?ksh_option_print\>\)\| - \ \%(\%(\<no_\?\)\?kshtypeset\>\)\|\%(\%(no_\?\)\?ksh_typeset\>\)\| - \ \%(\%(\<no_\?\)\?kshzerosubscript\>\)\|\%(\%(no_\?\)\?ksh_zero_subscript\>\)\| - \ \%(\%(\<no_\?\)\?listambiguous\>\)\|\%(\%(no_\?\)\?list_ambiguous\>\)\| - \ \%(\%(\<no_\?\)\?listbeep\>\)\|\%(\%(no_\?\)\?list_beep\>\)\| - \ \%(\%(\<no_\?\)\?listpacked\>\)\|\%(\%(no_\?\)\?list_packed\>\)\| - \ \%(\%(\<no_\?\)\?listrowsfirst\>\)\|\%(\%(no_\?\)\?list_rows_first\>\)\| - \ \%(\%(\<no_\?\)\?listtypes\>\)\|\%(\%(no_\?\)\?list_types\>\)\| - \ \%(\%(\<no_\?\)\?localloops\>\)\|\%(\%(no_\?\)\?local_loops\>\)\| - \ \%(\%(\<no_\?\)\?localoptions\>\)\|\%(\%(no_\?\)\?local_options\>\)\| - \ \%(\%(\<no_\?\)\?localpatterns\>\)\|\%(\%(no_\?\)\?local_patterns\>\)\| - \ \%(\%(\<no_\?\)\?localtraps\>\)\|\%(\%(no_\?\)\?local_traps\>\)\| - \ \%(\%(\<no_\?\)\?log\>\)\| - \ \%(\%(\<no_\?\)\?login\>\)\| - \ \%(\%(\<no_\?\)\?longlistjobs\>\)\|\%(\%(no_\?\)\?long_list_jobs\>\)\| - \ \%(\%(\<no_\?\)\?magicequalsubst\>\)\|\%(\%(no_\?\)\?magic_equal_subst\>\)\| - \ \%(\%(\<no_\?\)\?mark_dirs\>\)\| - \ \%(\%(\<no_\?\)\?mailwarn\>\)\|\%(\%(no_\?\)\?mail_warn\>\)\| - \ \%(\%(\<no_\?\)\?mailwarning\>\)\|\%(\%(no_\?\)\?mail_warning\>\)\| - \ \%(\%(\<no_\?\)\?markdirs\>\)\| - \ \%(\%(\<no_\?\)\?menucomplete\>\)\|\%(\%(no_\?\)\?menu_complete\>\)\| - \ \%(\%(\<no_\?\)\?monitor\>\)\| - \ \%(\%(\<no_\?\)\?multibyte\>\)\|\%(\%(no_\?\)\?multi_byte\>\)\| - \ \%(\%(\<no_\?\)\?multifuncdef\>\)\|\%(\%(no_\?\)\?multi_func_def\>\)\| - \ \%(\%(\<no_\?\)\?multios\>\)\|\%(\%(no_\?\)\?multi_os\>\)\| - \ \%(\%(\<no_\?\)\?nomatch\>\)\|\%(\%(no_\?\)\?no_match\>\)\| - \ \%(\%(\<no_\?\)\?notify\>\)\| - \ \%(\%(\<no_\?\)\?nullglob\>\)\|\%(\%(no_\?\)\?null_glob\>\)\| - \ \%(\%(\<no_\?\)\?numericglobsort\>\)\|\%(\%(no_\?\)\?numeric_glob_sort\>\)\| - \ \%(\%(\<no_\?\)\?octalzeroes\>\)\|\%(\%(no_\?\)\?octal_zeroes\>\)\| - \ \%(\%(\<no_\?\)\?onecmd\>\)\|\%(\%(no_\?\)\?one_cmd\>\)\| - \ \%(\%(\<no_\?\)\?overstrike\>\)\|\%(\%(no_\?\)\?over_strike\>\)\| - \ \%(\%(\<no_\?\)\?pathdirs\>\)\|\%(\%(no_\?\)\?path_dirs\>\)\| - \ \%(\%(\<no_\?\)\?pathscript\>\)\|\%(\%(no_\?\)\?path_script\>\)\| - \ \%(\%(\<no_\?\)\?physical\>\)\| - \ \%(\%(\<no_\?\)\?pipefail\>\)\|\%(\%(no_\?\)\?pipe_fail\>\)\| - \ \%(\%(\<no_\?\)\?posixaliases\>\)\|\%(\%(no_\?\)\?posix_aliases\>\)\| - \ \%(\%(\<no_\?\)\?posixargzero\>\)\|\%(\%(no_\?\)\?posix_arg_zero\>\)\|\%(\%(no_\?\)\?posix_argzero\>\)\| - \ \%(\%(\<no_\?\)\?posixbuiltins\>\)\|\%(\%(no_\?\)\?posix_builtins\>\)\| - \ \%(\%(\<no_\?\)\?posixcd\>\)\|\%(\%(no_\?\)\?posix_cd\>\)\| - \ \%(\%(\<no_\?\)\?posixidentifiers\>\)\|\%(\%(no_\?\)\?posix_identifiers\>\)\| - \ \%(\%(\<no_\?\)\?posixjobs\>\)\|\%(\%(no_\?\)\?posix_jobs\>\)\| - \ \%(\%(\<no_\?\)\?posixstrings\>\)\|\%(\%(no_\?\)\?posix_strings\>\)\| - \ \%(\%(\<no_\?\)\?posixtraps\>\)\|\%(\%(no_\?\)\?posix_traps\>\)\| - \ \%(\%(\<no_\?\)\?printeightbit\>\)\|\%(\%(no_\?\)\?print_eight_bit\>\)\| - \ \%(\%(\<no_\?\)\?printexitvalue\>\)\|\%(\%(no_\?\)\?print_exit_value\>\)\| - \ \%(\%(\<no_\?\)\?privileged\>\)\| - \ \%(\%(\<no_\?\)\?promptbang\>\)\|\%(\%(no_\?\)\?prompt_bang\>\)\| - \ \%(\%(\<no_\?\)\?promptcr\>\)\|\%(\%(no_\?\)\?prompt_cr\>\)\| - \ \%(\%(\<no_\?\)\?promptpercent\>\)\|\%(\%(no_\?\)\?prompt_percent\>\)\| - \ \%(\%(\<no_\?\)\?promptsp\>\)\|\%(\%(no_\?\)\?prompt_sp\>\)\| - \ \%(\%(\<no_\?\)\?promptsubst\>\)\|\%(\%(no_\?\)\?prompt_subst\>\)\| - \ \%(\%(\<no_\?\)\?promptvars\>\)\|\%(\%(no_\?\)\?prompt_vars\>\)\| - \ \%(\%(\<no_\?\)\?pushdignoredups\>\)\|\%(\%(no_\?\)\?pushd_ignore_dups\>\)\| - \ \%(\%(\<no_\?\)\?pushdminus\>\)\|\%(\%(no_\?\)\?pushd_minus\>\)\| - \ \%(\%(\<no_\?\)\?pushdsilent\>\)\|\%(\%(no_\?\)\?pushd_silent\>\)\| - \ \%(\%(\<no_\?\)\?pushdtohome\>\)\|\%(\%(no_\?\)\?pushd_to_home\>\)\| - \ \%(\%(\<no_\?\)\?rcexpandparam\>\)\|\%(\%(no_\?\)\?rc_expandparam\>\)\|\%(\%(no_\?\)\?rc_expand_param\>\)\| - \ \%(\%(\<no_\?\)\?rcquotes\>\)\|\%(\%(no_\?\)\?rc_quotes\>\)\| - \ \%(\%(\<no_\?\)\?rcs\>\)\| - \ \%(\%(\<no_\?\)\?recexact\>\)\|\%(\%(no_\?\)\?rec_exact\>\)\| - \ \%(\%(\<no_\?\)\?rematchpcre\>\)\|\%(\%(no_\?\)\?re_match_pcre\>\)\|\%(\%(no_\?\)\?rematch_pcre\>\)\| - \ \%(\%(\<no_\?\)\?restricted\>\)\| - \ \%(\%(\<no_\?\)\?rmstarsilent\>\)\|\%(\%(no_\?\)\?rm_star_silent\>\)\| - \ \%(\%(\<no_\?\)\?rmstarwait\>\)\|\%(\%(no_\?\)\?rm_star_wait\>\)\| - \ \%(\%(\<no_\?\)\?sharehistory\>\)\|\%(\%(no_\?\)\?share_history\>\)\| - \ \%(\%(\<no_\?\)\?shfileexpansion\>\)\|\%(\%(no_\?\)\?sh_file_expansion\>\)\| - \ \%(\%(\<no_\?\)\?shglob\>\)\|\%(\%(no_\?\)\?sh_glob\>\)\| - \ \%(\%(\<no_\?\)\?shinstdin\>\)\|\%(\%(no_\?\)\?shin_stdin\>\)\| - \ \%(\%(\<no_\?\)\?shnullcmd\>\)\|\%(\%(no_\?\)\?sh_nullcmd\>\)\| - \ \%(\%(\<no_\?\)\?shoptionletters\>\)\|\%(\%(no_\?\)\?sh_option_letters\>\)\| - \ \%(\%(\<no_\?\)\?shortloops\>\)\|\%(\%(no_\?\)\?short_loops\>\)\| - \ \%(\%(\<no_\?\)\?shortrepeat\>\)\|\%(\%(no_\?\)\?short_repeat\>\)\| - \ \%(\%(\<no_\?\)\?shwordsplit\>\)\|\%(\%(no_\?\)\?sh_word_split\>\)\| - \ \%(\%(\<no_\?\)\?singlecommand\>\)\|\%(\%(no_\?\)\?single_command\>\)\| - \ \%(\%(\<no_\?\)\?singlelinezle\>\)\|\%(\%(no_\?\)\?single_line_zle\>\)\| - \ \%(\%(\<no_\?\)\?sourcetrace\>\)\|\%(\%(no_\?\)\?source_trace\>\)\| - \ \%(\%(\<no_\?\)\?stdin\>\)\| - \ \%(\%(\<no_\?\)\?sunkeyboardhack\>\)\|\%(\%(no_\?\)\?sun_keyboard_hack\>\)\| - \ \%(\%(\<no_\?\)\?trackall\>\)\|\%(\%(no_\?\)\?track_all\>\)\| - \ \%(\%(\<no_\?\)\?transientrprompt\>\)\|\%(\%(no_\?\)\?transient_rprompt\>\)\| - \ \%(\%(\<no_\?\)\?trapsasync\>\)\|\%(\%(no_\?\)\?traps_async\>\)\| - \ \%(\%(\<no_\?\)\?typesetsilent\>\)\|\%(\%(no_\?\)\?type_set_silent\>\)\|\%(\%(no_\?\)\?typeset_silent\>\)\| - \ \%(\%(\<no_\?\)\?unset\>\)\| - \ \%(\%(\<no_\?\)\?verbose\>\)\| - \ \%(\%(\<no_\?\)\?vi\>\)\| - \ \%(\%(\<no_\?\)\?warnnestedvar\>\)\|\%(\%(no_\?\)\?warn_nested_var\>\)\| - \ \%(\%(\<no_\?\)\?warncreateglobal\>\)\|\%(\%(no_\?\)\?warn_create_global\>\)\| - \ \%(\%(\<no_\?\)\?xtrace\>\)\| - \ \%(\%(\<no_\?\)\?zle\>\)/ nextgroup=zshOption,zshComment skipwhite contained - +syn match zshOptStart + \ /\v^\s*%(%(un)?setopt|set\s+[-+]o)/ + \ nextgroup=zshOption skipwhite +syn match zshOption nextgroup=zshOption,zshComment skipwhite contained /\v + \ <%(no_?)?%( + \ auto_?cd|auto_?pushd|cdable_?vars|cd_?silent|chase_?dots|chase_?links|posix_?cd|pushd_?ignore_?dups|pushd_?minus|pushd_?silent|pushd_?to_?home|always_?last_?prompt|always_?to_?end|auto_?list|auto_?menu|auto_?name_?dirs|auto_?param_?keys|auto_?param_?slash|auto_?remove_?slash|bash_?auto_?list|complete_?aliases|complete_?in_?word|glob_?complete|hash_?list_?all|list_?ambiguous|list_?beep|list_?packed|list_?rows_?first|list_?types|menu_?complete|rec_?exact|bad_?pattern|bare_?glob_?qual|brace_?ccl|case_?glob|case_?match|case_?paths|csh_?null_?glob|equals|extended_?glob|force_?float|glob|glob_?assign|glob_?dots|glob_?star_?short|glob_?subst|hist_?subst_?pattern|ignore_?braces|ignore_?close_?braces|ksh_?glob|magic_?equal_?subst|mark_?dirs|multibyte|nomatch|null_?glob|numeric_?glob_?sort|rc_?expand_?param|rematch_?pcre|sh_?glob|unset|warn_?create_?global|warn_?nested_?var|warnnestedvar|append_?history|bang_?hist|extended_?history|hist_?allow_?clobber|hist_?beep|hist_?expire_?dups_?first|hist_?fcntl_?lock|hist_?find_?no_?dups|hist_?ignore_?all_?dups|hist_?ignore_?dups|hist_?ignore_?space|hist_?lex_?words|hist_?no_?functions|hist_?no_?store|hist_?reduce_?blanks|hist_?save_?by_?copy|hist_?save_?no_?dups|hist_?verify|inc_?append_?history|inc_?append_?history_?time|share_?history|all_?export|global_?export|global_?rcs|rcs|aliases|clobber|clobber_?empty|correct|correct_?all|dvorak|flow_?control|ignore_?eof|interactive_?comments|hash_?cmds|hash_?dirs|hash_?executables_?only|mail_?warning|path_?dirs|path_?script|print_?eight_?bit|print_?exit_?value|rc_?quotes|rm_?star_?silent|rm_?star_?wait|short_?loops|short_?repeat|sun_?keyboard_?hack|auto_?continue|auto_?resume|bg_?nice|check_?jobs|check_?running_?jobs|hup|long_?list_?jobs|monitor|notify|posix_?jobs|prompt_?bang|prompt_?cr|prompt_?sp|prompt_?percent|prompt_?subst|transient_?rprompt|alias_?func_?def|c_?bases|c_?precedences|debug_?before_?cmd|err_?exit|err_?return|eval_?lineno|exec|function_?argzero|local_?loops|local_?options|local_?patterns|local_?traps|multi_?func_?def|multios|octal_?zeroes|pipe_?fail|source_?trace|typeset_?silent|typeset_?to_?unset|verbose|xtrace|append_?create|bash_?rematch|bsd_?echo|continue_?on_?error|csh_?junkie_?history|csh_?junkie_?loops|csh_?junkie_?quotes|csh_?nullcmd|ksh_?arrays|ksh_?autoload|ksh_?option_?print|ksh_?typeset|ksh_?zero_?subscript|posix_?aliases|posix_?argzero|posix_?builtins|posix_?identifiers|posix_?strings|posix_?traps|sh_?file_?expansion|sh_?nullcmd|sh_?option_?letters|sh_?word_?split|traps_?async|interactive|login|privileged|restricted|shin_?stdin|single_?command|beep|combining_?chars|emacs|overstrike|single_?line_?zle|vi|zle|brace_?expand|dot_?glob|hash_?all|hist_?append|hist_?expand|log|mail_?warn|one_?cmd|physical|prompt_?vars|stdin|track_?all|no_?match + \)>/ syn case match syn keyword zshTypes float integer local typeset declare private readonly @@ -365,7 +164,7 @@ syn region zshGlob start='(#' end=')' syn region zshMathSubst matchgroup=zshSubstDelim transparent \ start='\%(\$\?\)[<=>]\@<!((' skip='\\)' end='))' \ contains=zshParentheses,@zshSubst,zshNumber, - \ @zshDerefs,zshString keepend fold + \ @zshDerefs,zshString fold " The ms=s+1 prevents matching zshBrackets several times on opening brackets " (see https://github.com/chrisbra/vim-zsh/issues/21#issuecomment-576330348) syn region zshBrackets contained transparent start='{'ms=s+1 skip='\\}' diff --git a/runtime/tools/check_colors.vim b/runtime/tools/check_colors.vim index 966072c706..85df882d1e 100644 --- a/runtime/tools/check_colors.vim +++ b/runtime/tools/check_colors.vim @@ -226,7 +226,13 @@ fu! Result(err) endif endfu -call Test_check_colors() - -let &cpo = s:save_cpo -unlet s:save_cpo +try + call Test_check_colors() +catch + echohl ErrorMsg + echomsg v:exception + echohl NONE +finally + let &cpo = s:save_cpo + unlet s:save_cpo +endtry 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/bump-deps.sh b/scripts/bump-deps.sh new file mode 100755 index 0000000000..85c7f72700 --- /dev/null +++ b/scripts/bump-deps.sh @@ -0,0 +1,104 @@ +#!/usr/bin/env bash +set -e +set -u +# Use privileged mode, which e.g. skips using CDPATH. +set -p + +# Ensure that the user has a bash that supports -A +if [[ "${BASH_VERSINFO[0]}" -lt 4 ]]; then + echo >&2 "error: script requires bash 4+ (you have ${BASH_VERSION})." + exit 1 +fi + +readonly NVIM_SOURCE_DIR="${NVIM_SOURCE_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)}" +readonly VIM_SOURCE_DIR_DEFAULT="${NVIM_SOURCE_DIR}/.vim-src" +readonly VIM_SOURCE_DIR="${VIM_SOURCE_DIR:-${VIM_SOURCE_DIR_DEFAULT}}" +BASENAME="$(basename "${0}")" +readonly BASENAME + +usage() { + echo "Bump Neovim dependencies" + echo + echo "Usage: ${BASENAME} [ -h | --pr | --branch=<dep> | --dep=<dependency> ]" + echo + echo "Options:" + echo " -h show this message and exit." + echo " --pr submit pr for bumping deps." + echo " --branch=<dep> create a branch bump-<dep> from current branch." + echo " --dep=<dependency> bump to a specific release or tag." + echo + echo "Dependency Options:" + echo " --version=<tag> bump to a specific release or tag." + echo " --commit=<hash> bump to a specific commit." + echo " --HEAD bump to a current head." + echo + echo " <dependency> is one of:" + echo " \"LuaJIT\", \"libuv\", \"Luv\", \"tree-sitter\"" +} + +# Checks if a program is in the user's PATH, and is executable. +check_executable() { + test -x "$(command -v "${1}")" +} + +require_executable() { + if ! check_executable "${1}"; then + echo >&2 "${BASENAME}: '${1}' not found in PATH or not executable." + exit 1 + fi +} + +require_executable "nvim" + +if [ $# -eq 0 ]; then + usage + exit 1 +fi + +PARSED_ARGS=$(getopt -a -n "$BASENAME" -o h --long pr,branch:,dep:,version:,commit:,HEAD -- "$@") + +DEPENDENCY="" +eval set -- "$PARSED_ARGS" +while :; do + case "$1" in + -h) + usage + exit 0 + ;; + --pr) + nvim -es +"lua require('scripts.bump_deps').submit_pr()" + exit 0 + ;; + --branch) + DEP=$2 + nvim -es +"lua require('scripts.bump_deps').create_branch('$DEP')" + exit 0 + ;; + --dep) + DEPENDENCY=$2 + shift 2 + ;; + --version) + VERSION=$2 + nvim -es +"lua require('scripts.bump_deps').version('$DEPENDENCY', '$VERSION')" + exit 0 + ;; + --commit) + COMMIT=$2 + nvim -es +"lua require('scripts.bump_deps').commit('$DEPENDENCY', '$COMMIT')" + exit 0 + ;; + --HEAD) + nvim -es +"lua require('scripts.bump_deps').head('$DEPENDENCY')" + exit 0 + ;; + *) + break + ;; + esac +done + +usage +exit 1 + +# vim: et sw=2 diff --git a/scripts/bump_deps.lua b/scripts/bump_deps.lua new file mode 100644 index 0000000000..2ecbb2e658 --- /dev/null +++ b/scripts/bump_deps.lua @@ -0,0 +1,343 @@ +-- Usage: +-- # bump to version +-- nvim -es +"lua require('scripts.bump_deps').version(dependency, version_tag)" +-- +-- # bump to commit +-- nvim -es +"lua require('scripts.bump_deps').commit(dependency, commit_hash)" +-- +-- # bump to HEAD +-- nvim -es +"lua require('scripts.bump_deps').head(dependency)" +-- +-- # submit PR +-- nvim -es +"lua require('scripts.bump_deps').submit_pr()" +-- +-- # create branch +-- nvim -es +"lua require('scripts.bump_deps').create_branch()" + +local M = {} + +local _trace = false +local required_branch_prefix = "bump-" +local commit_prefix = "build(deps): " + +-- Print message +local function p(s) + vim.cmd("set verbose=1") + vim.api.nvim_echo({ { s, "" } }, false, {}) + vim.cmd("set verbose=0") +end + +local function die() + p("") + vim.cmd("cquit 1") +end + +-- Executes and returns the output of `cmd`, or nil on failure. +-- if die_on_fail is true, process dies with die_msg on failure +-- +-- Prints `cmd` if `trace` is enabled. +local function _run(cmd, die_on_fail, die_msg) + if _trace then + p("run: " .. vim.inspect(cmd)) + end + local rv = vim.trim(vim.fn.system(cmd)) or "" + if vim.v.shell_error ~= 0 then + if die_on_fail then + if _trace then + p(rv) + end + p(die_msg) + die() + end + return nil + end + return rv +end + +-- Run a command, return nil on failure +local function run(cmd) + return _run(cmd, false, "") +end + +-- Run a command, die on failure with err_msg +local function run_die(cmd, err_msg) + return _run(cmd, true, err_msg) +end + +local function require_executable(cmd) + local cmd_path = run_die({ "command", "-v", cmd }, cmd .. " not found!") + run_die({ "test", "-x", cmd_path }, cmd .. " is not executable") +end + +local function rm_file_if_present(path_to_file) + run({ "rm", "-f", path_to_file }) +end + +local nvim_src_dir = vim.fn.getcwd() +local temp_dir = nvim_src_dir .. "/tmp" +run({ "mkdir", "-p", temp_dir }) + +local function get_dependency(dependency_name) + local dependency_table = { + ["LuaJIT"] = { + repo = "LuaJIT/LuaJIT", + symbol = "LUAJIT", + }, + ["libuv"] = { + repo = "libuv/libuv", + symbol = "LIBUV", + }, + ["Luv"] = { + repo = "luvit/luv", + symbol = "LUV", + }, + ["tree-sitter"] = { + repo = "tree-sitter/tree-sitter", + symbol = "TREESITTER", + }, + } + local dependency = dependency_table[dependency_name] + if dependency == nil then + p("Not a dependency: " .. dependency_name) + die() + end + dependency.name = dependency_name + return dependency +end + +local function get_gh_commit_sha(repo, ref) + require_executable("gh") + + local sha = run_die( + { "gh", "api", "repos/" .. repo .. "/commits/" .. ref, "--jq", ".sha" }, + "Failed to get commit hash from GitHub. Not a valid ref?" + ) + return sha +end + +local function get_archive_info(repo, ref) + require_executable("curl") + + local archive_name = ref .. ".tar.gz" + local archive_path = temp_dir .. "/" .. archive_name + local archive_url = "https://github.com/" .. repo .. "/archive/" .. archive_name + + rm_file_if_present(archive_path) + run_die({ "curl", "-sL", archive_url, "-o", archive_path }, "Failed to download archive from GitHub") + + local archive_sha = run({ "sha256sum", archive_path }):gmatch("%w+")() + return { url = archive_url, sha = archive_sha } +end + +local function write_cmakelists_line(symbol, kind, value) + require_executable("sed") + + local cmakelists_path = nvim_src_dir .. "/" .. "third-party/CMakeLists.txt" + run_die({ + "sed", + "-i", + "-e", + "s/set(" .. symbol .. "_" .. kind .. ".*$" .. "/set(" .. symbol .. "_" .. kind .. " " .. value .. ")" .. "/", + cmakelists_path, + }, "Failed to write " .. cmakelists_path) +end + +local function explicit_create_branch(dep) + require_executable("git") + + local checked_out_branch = run({ "git", "rev-parse", "--abbrev-ref", "HEAD" }) + if checked_out_branch ~= "master" then + p("Not on master!") + die() + end + run_die({ "git", "checkout", "-b", "bump-" .. dep }, "git failed to create branch") +end + +local function verify_branch(new_branch_suffix) + require_executable("git") + + local checked_out_branch = run({ "git", "rev-parse", "--abbrev-ref", "HEAD" }) + if not checked_out_branch:match("^" .. required_branch_prefix) then + p("Current branch '" .. checked_out_branch .. "' doesn't seem to start with " .. required_branch_prefix) + p("Checking out to bump-" .. new_branch_suffix) + explicit_create_branch(new_branch_suffix) + end +end + +local function update_cmakelists(dependency, archive, comment) + require_executable("git") + + verify_branch(dependency.name) + + local changed_file = nvim_src_dir .. "/" .. "third-party/CMakeLists.txt" + + p("Updating " .. dependency.name .. " to " .. archive.url .. "\n") + write_cmakelists_line(dependency.symbol, "URL", archive.url:gsub("/", "\\/")) + write_cmakelists_line(dependency.symbol, "SHA256", archive.sha) + run_die( + { "git", "commit", changed_file, "-m", commit_prefix .. "bump " .. dependency.name .. " to " .. comment }, + "git failed to commit" + ) +end + +local function verify_cmakelists_committed() + require_executable("git") + + local cmakelists_path = nvim_src_dir .. "/" .. "third-party/CMakeLists.txt" + run_die({ "git", "diff", "--quiet", "HEAD", "--", cmakelists_path }, cmakelists_path .. " has uncommitted changes") +end + +local function warn_luv_symbol() + p("warning: " .. get_dependency("Luv").symbol .. "_VERSION will not be updated") +end + +-- return first 9 chars of commit +local function short_commit(commit) + return string.sub(commit, 1, 9) +end + +-- TODO: remove hardcoded fork +local function gh_pr(pr_title, pr_body) + require_executable("gh") + + local pr_url = run_die({ + "gh", + "pr", + "create", + "--title", + pr_title, + "--body", + pr_body, + }, "Failed to create PR") + return pr_url +end + +local function find_git_remote(fork) + require_executable("git") + + local remotes = run({ "git", "remote", "-v" }) + local git_remote = "" + for remote in remotes:gmatch("[^\r\n]+") do + local words = {} + for word in remote:gmatch("%w+") do + table.insert(words, word) + end + local match = words[1]:match("/github.com[:/]neovim/neovim/") + if fork == "fork" then + match = not match + end + if match and words[3] == "(fetch)" then + git_remote = words[0] + break + end + end + if git_remote == "" then + git_remote = "origin" + end + return git_remote +end + +local function create_pr(pr_title, pr_body) + require_executable("git") + + local push_first = true + + local checked_out_branch = run({ "git", "rev-parse", "--abbrev-ref", "HEAD" }) + if push_first then + local push_remote = run({ "git", "config", "--get", "branch." .. checked_out_branch .. ".pushRemote" }) + if push_remote == nil then + push_remote = run({ "git", "config", "--get", "remote.pushDefault" }) + if push_remote == nil then + push_remote = run({ "git", "config", "--get", "branch." .. checked_out_branch .. ".remote" }) + if push_remote == nil or push_remote == find_git_remote(nil) then + push_remote = find_git_remote("fork") + end + end + end + + p("Pushing to " .. push_remote .. "/" .. checked_out_branch) + run_die({ "git", "push", push_remote, checked_out_branch }, "Git failed to push") + end + + local pr_url = gh_pr(pr_title, pr_body) + p("\nCreated PR: " .. pr_url .. "\n") +end + +function M.commit(dependency_name, commit) + local dependency = get_dependency(dependency_name) + verify_cmakelists_committed() + local commit_sha = get_gh_commit_sha(dependency.repo, commit) + if commit_sha ~= commit then + p("Not a commit: " .. commit .. ". Did you mean version?") + die() + end + local archive = get_archive_info(dependency.repo, commit) + if dependency_name == "Luv" then + warn_luv_symbol() + end + update_cmakelists(dependency, archive, short_commit(commit)) +end + +function M.version(dependency_name, version) + local dependency = get_dependency(dependency_name) + verify_cmakelists_committed() + local commit_sha = get_gh_commit_sha(dependency.repo, version) + if commit_sha == version then + p("Not a version: " .. version .. ". Did you mean commit?") + die() + end + local archive = get_archive_info(dependency.repo, version) + if dependency_name == "Luv" then + write_cmakelists_line(dependency.symbol, "VERSION", version) + end + update_cmakelists(dependency, archive, version) +end + +function M.head(dependency_name) + local dependency = get_dependency(dependency_name) + verify_cmakelists_committed() + local commit_sha = get_gh_commit_sha(dependency.repo, "HEAD") + local archive = get_archive_info(dependency.repo, commit_sha) + if dependency_name == "Luv" then + warn_luv_symbol() + end + update_cmakelists(dependency, archive, "HEAD - " .. short_commit(commit_sha)) +end + +function M.create_branch(dep) + explicit_create_branch(dep) +end + +function M.submit_pr() + require_executable("git") + + verify_branch("deps") + + local nvim_remote = find_git_remote(nil) + local relevant_commit = run_die({ + "git", + "log", + "--grep=" .. commit_prefix, + "--reverse", + "--format='%s'", + nvim_remote .. "/master..HEAD", + "-1", + }, "Failed to fetch commits") + + local pr_title + local pr_body + + if relevant_commit == "" then + pr_title = commit_prefix .. "bump some dependencies" + pr_body = "bump some dependencies" + else + relevant_commit = relevant_commit:gsub("'", "") + pr_title = relevant_commit + pr_body = relevant_commit:gsub(commit_prefix:gsub("%(", "%%("):gsub("%)", "%%)"), "") + end + pr_body = pr_body .. "\n\n(add explanations if needed)" + p(pr_title .. "\n" .. pr_body .. "\n") + create_pr(pr_title, pr_body) +end + +return M 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..7152f1005f 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)) @@ -84,8 +84,6 @@ CONFIG = { 'api': { 'mode': 'c', 'filename': 'api.txt', - # String used to find the start of the generated part of the doc. - 'section_start_token': '*api-global*', # Section ordering. 'section_order': [ 'vim.c', @@ -95,11 +93,11 @@ 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'), + # List of files/directories for doxygen to read, relative to `base_dir` + 'files': ['src/nvim/api'], # file patterns used by doxygen 'file_patterns': '*.h *.c', # Only function with this prefix are considered @@ -122,32 +120,46 @@ CONFIG = { 'lua': { 'mode': 'lua', 'filename': 'lua.txt', - 'section_start_token': '*lua-vim*', 'section_order': [ - 'vim.lua', + '_editor.lua', 'shared.lua', 'uri.lua', 'ui.lua', + 'filetype.lua', + 'keymap.lua', + ], + 'files': [ + 'runtime/lua/vim/_editor.lua', + 'runtime/lua/vim/shared.lua', + 'runtime/lua/vim/uri.lua', + 'runtime/lua/vim/ui.lua', + 'runtime/lua/vim/filetype.lua', + 'runtime/lua/vim/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'), - ]), 'file_patterns': '*.lua', 'fn_name_prefix': '', 'section_name': { 'lsp.lua': 'core', }, - 'section_fmt': lambda name: f'Lua module: {name.lower()}', - 'helptag_fmt': lambda name: f'*lua-{name.lower()}*', - 'fn_helptag_fmt': lambda fstem, name: f'*{fstem}.{name}()*', + 'section_fmt': lambda name: ( + 'Lua module: vim' + if name.lower() == '_editor' + else f'Lua module: {name.lower()}'), + 'helptag_fmt': lambda name: ( + '*lua-vim*' + if name.lower() == '_editor' + else f'*lua-{name.lower()}*'), + 'fn_helptag_fmt': lambda fstem, name: ( + f'*vim.{name}()*' + if fstem.lower() == '_editor' + else f'*{fstem}.{name}()*'), 'module_override': { # `shared` functions are exposed on the `vim` module. 'shared': 'vim', 'uri': 'vim', 'ui': 'vim.ui', + 'filetype': 'vim.filetype', + 'keymap': 'vim.keymap', }, 'append_only': [ 'shared.lua', @@ -156,7 +168,6 @@ CONFIG = { 'lsp': { 'mode': 'lua', 'filename': 'lsp.txt', - 'section_start_token': '*lsp-core*', 'section_order': [ 'lsp.lua', 'buf.lua', @@ -170,10 +181,10 @@ CONFIG = { 'sync.lua', 'protocol.lua', ], - 'files': ' '.join([ - os.path.join(base_dir, 'runtime/lua/vim/lsp'), - os.path.join(base_dir, 'runtime/lua/vim/lsp.lua'), - ]), + 'files': [ + 'runtime/lua/vim/lsp', + 'runtime/lua/vim/lsp.lua', + ], 'file_patterns': '*.lua', 'fn_name_prefix': '', 'section_name': {'lsp.lua': 'lsp'}, @@ -199,11 +210,10 @@ CONFIG = { 'diagnostic': { 'mode': 'lua', 'filename': 'diagnostic.txt', - 'section_start_token': '*diagnostic-api*', 'section_order': [ 'diagnostic.lua', ], - 'files': os.path.join(base_dir, 'runtime/lua/vim/diagnostic.lua'), + 'files': ['runtime/lua/vim/diagnostic.lua'], 'file_patterns': '*.lua', 'fn_name_prefix': '', 'section_name': {'diagnostic.lua': 'diagnostic'}, @@ -216,7 +226,6 @@ CONFIG = { 'treesitter': { 'mode': 'lua', 'filename': 'treesitter.txt', - 'section_start_token': '*lua-treesitter-core*', 'section_order': [ 'treesitter.lua', 'language.lua', @@ -224,10 +233,10 @@ CONFIG = { 'highlighter.lua', 'languagetree.lua', ], - 'files': ' '.join([ - os.path.join(base_dir, 'runtime/lua/vim/treesitter.lua'), - os.path.join(base_dir, 'runtime/lua/vim/treesitter/'), - ]), + 'files': [ + 'runtime/lua/vim/treesitter.lua', + 'runtime/lua/vim/treesitter/', + ], 'file_patterns': '*.lua', 'fn_name_prefix': '', 'section_name': {}, @@ -334,14 +343,6 @@ def self_or_child(n): return n.childNodes[0] -def clean_text(text): - """Cleans text. - - Only cleans superfluous whitespace at the moment. - """ - return ' '.join(text.split()).strip() - - def clean_lines(text): """Removes superfluous lines. @@ -362,12 +363,12 @@ def get_text(n, preformatted=False): if n.nodeName == 'computeroutput': for node in n.childNodes: text += get_text(node) - return '`{}` '.format(text) + return '`{}`'.format(text) for node in n.childNodes: if node.nodeType == node.TEXT_NODE: - text += node.data if preformatted else clean_text(node.data) + text += node.data elif node.nodeType == node.ELEMENT_NODE: - text += ' ' + get_text(node, preformatted) + text += get_text(node, preformatted) return text @@ -834,7 +835,9 @@ def extract_from_xml(filename, target, width): 'seealso': [], } if fmt_vimhelp: - fn['desc_node'] = desc # HACK :( + # HACK :( + fn['desc_node'] = desc + fn['brief_desc_node'] = brief_desc for m in paras: if 'text' in m: @@ -882,6 +885,8 @@ def fmt_doxygen_xml_as_vimhelp(filename, target): # Generate Vim :help for parameters. if fn['desc_node']: doc = fmt_node_as_vimhelp(fn['desc_node']) + if not doc and fn['brief_desc_node']: + doc = fmt_node_as_vimhelp(fn['brief_desc_node']) if not doc: doc = 'TODO: Documentation' @@ -991,7 +996,8 @@ def main(config, args): stderr=(subprocess.STDOUT if debug else subprocess.DEVNULL)) p.communicate( config.format( - input=CONFIG[target]['files'], + input=' '.join( + [f'"{file}"' for file in CONFIG[target]['files']]), output=output_dir, filter=filter_cmd, file_patterns=CONFIG[target]['file_patterns']) @@ -1081,6 +1087,7 @@ def main(config, args): raise RuntimeError( 'found new modules "{}"; update the "section_order" map'.format( set(sections).difference(CONFIG[target]['section_order']))) + first_section_tag = sections[CONFIG[target]['section_order'][0]][1] docs = '' @@ -1106,7 +1113,8 @@ def main(config, args): doc_file = os.path.join(base_dir, 'runtime', 'doc', CONFIG[target]['filename']) - delete_lines_below(doc_file, CONFIG[target]['section_start_token']) + if os.path.exists(doc_file): + delete_lines_below(doc_file, first_section_tag) with open(doc_file, 'ab') as fp: fp.write(docs.encode('utf8')) diff --git a/scripts/lintcommit.lua b/scripts/lintcommit.lua index c30a1b10da..6871858a0b 100644 --- a/scripts/lintcommit.lua +++ b/scripts/lintcommit.lua @@ -47,7 +47,7 @@ end local function validate_commit(commit_message) local commit_split = vim.split(commit_message, ":") - -- Return true if the type is vim-patch since most of the normal rules don't + -- Return nil if the type is vim-patch since most of the normal rules don't -- apply. if commit_split[1] == "vim-patch" then return nil @@ -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/squash_typos.py b/scripts/squash_typos.py deleted file mode 100644 index b403a9b7c8..0000000000 --- a/scripts/squash_typos.py +++ /dev/null @@ -1,263 +0,0 @@ -#!/usr/bin/env python -""" - -This script squashes a PR tagged with the "typo" label into a single, dedicated -"squash PR". - -""" - -import subprocess -import sys -import os - - -def get_authors_and_emails_from_pr(): - """ - - Return all contributing authors and their emails for the PR on current branch. - This includes co-authors, meaning that if two authors are credited for a - single commit, which is possible with GitHub, then both will get credited. - - """ - - # Get a list of all authors involved in the pull request (including co-authors). - authors = subprocess.check_output( - [ - "gh", - "pr", - "view", - os.environ["PR_NUMBER"], - "--json", - "commits", - "--jq", - ".[][].authors.[].name", - ], - text=True, - ).splitlines() - - # Get a list of emails of the aforementioned authors. - emails = subprocess.check_output( - [ - "gh", - "pr", - "view", - os.environ["PR_NUMBER"], - "--json", - "commits", - "--jq", - ".[][].authors.[].email", - ], - text=True, - ).splitlines() - - authors_and_emails = [(author, mail) for author, mail in zip(authors, emails)] - - return authors_and_emails - - -def rebase_onto_pr(): - """ - - Rebase current branch onto the PR. - - """ - - # Check out the pull request. - subprocess.call(["gh", "pr", "checkout", os.environ["PR_NUMBER"]]) - - rebase_onto_master() - - # Change back to the original branch. - subprocess.call(["git", "switch", "-"]) - - # Rebase onto the pull request, aka include the commits in the pull request - # in the current branch. Abort with error message if rebase fails. - - try: - subprocess.check_call(["git", "rebase", "-"]) - except subprocess.CalledProcessError: - subprocess.call(["git", "rebase", "--abort"]) - squash_url = subprocess.check_output( - ["gh", "pr", "view", "--json", "url", "--jq", ".url"], text=True - ).strip() - - subprocess.call( - [ - "gh", - "pr", - "comment", - os.environ["PR_NUMBER"], - "--body", - f"Your edit conflicts with an already scheduled fix \ - ({squash_url}). Please check that batch PR whether your fix is \ - already included; if not, then please wait until the batch PR \ - is merged and then rebase your PR on top of master.", - ] - ) - - sys.exit( - f"\n\nERROR: Your edit conflicts with an already scheduled fix \ -{squash_url} \n\n" - ) - - -def rebase_onto_master(): - """ - - Rebase current branch onto the master i.e. make sure current branch is up - to date. Abort on error. - - """ - - default_branch = f"{os.environ['GITHUB_BASE_REF']}" - subprocess.check_call(["git", "rebase", default_branch]) - - -def squash_all_commits(message_body_before): - """ - - Squash all commits on the PR into a single commit. Credit all authors by - name and email. - - """ - - default_branch = f"{os.environ['GITHUB_BASE_REF']}" - subprocess.call(["git", "reset", "--soft", default_branch]) - - authors_and_emails = get_authors_and_emails_from_pr() - commit_message_coauthors = ( - "\n" - + "\n".join([f"Co-authored-by: {i[0]} <{i[1]}>" for i in authors_and_emails]) - + "\n" - + message_body_before - ) - subprocess.call( - ["git", "commit", "-m", "chore: typo fixes", "-m", commit_message_coauthors] - ) - - -def force_push(branch): - """ - - Like the name implies, force push <branch>. - - """ - - gh_actor = os.environ["GITHUB_ACTOR"] - gh_token = os.environ["GITHUB_TOKEN"] - gh_repo = os.environ["GITHUB_REPOSITORY"] - subprocess.call( - [ - "git", - "push", - "--force", - f"https://{gh_actor}:{gh_token}@github.com/{gh_repo}", - branch, - ] - ) - - -def checkout_branch(branch): - """ - - Create and checkout <branch>. Check if branch exists on remote, if so then - sync local branch to remote. - - Return True if remote branch exists, else False. - - """ - - # FIXME I'm not sure why the local branch isn't tracking the remote branch - # automatically. This works but I'm pretty sure it can be done in a more - # "elegant" fashion - - show_ref_output = subprocess.check_output(["git", "show-ref"], text=True).strip() - - if branch in show_ref_output: - subprocess.call(["git", "checkout", "-b", branch, f"origin/{branch}"]) - return True - - subprocess.call(["git", "checkout", "-b", branch]) - return False - - -def get_all_pr_urls(pr_branch_exists): - """ - - Return a list of URLs for the pull requests with the typo fixes. If a - squash branch exists then extract the URLs from the body text. - - """ - - all_pr_urls = "" - if pr_branch_exists: - all_pr_urls += subprocess.check_output( - ["gh", "pr", "view", "--json", "body", "--jq", ".body"], text=True - ) - - all_pr_urls += subprocess.check_output( - ["gh", "pr", "view", os.environ["PR_NUMBER"], "--json", "url", "--jq", ".url"], - text=True, - ).strip() - - return all_pr_urls - - -def main(): - pr_branch = "marvim/squash-typos" - - pr_branch_exists = checkout_branch(pr_branch) - - rebase_onto_master() - force_push(pr_branch) - - message_body_before = "\n".join( - subprocess.check_output( - ["git", "log", "--format=%B", "-n1", pr_branch], text=True - ).splitlines()[2:] - ) - - rebase_onto_pr() - force_push(pr_branch) - - subprocess.call( - [ - "gh", - "pr", - "create", - "--fill", - "--head", - pr_branch, - "--title", - "chore: typo fixes (automated)", - ], - text=True, - ) - - squash_all_commits(message_body_before) - force_push(pr_branch) - - all_pr_urls = get_all_pr_urls(pr_branch_exists) - subprocess.call(["gh", "pr", "edit", "--add-label", "typo", "--body", all_pr_urls]) - - subprocess.call(["gh", "pr", "close", os.environ["PR_NUMBER"]]) - - squash_url = subprocess.check_output( - ["gh", "pr", "view", "--json", "url", "--jq", ".url"], text=True - ).strip() - subprocess.call( - [ - "gh", - "pr", - "comment", - os.environ["PR_NUMBER"], - "--body", - f"Thank you for your contribution! We collect all typo fixes \ - into a single pull request and merge it once it gets big enough: \ - {squash_url}", - ] - ) - - -if __name__ == "__main__": - main() diff --git a/scripts/stripdecls.py b/scripts/stripdecls.py deleted file mode 100755 index b4ac34dcfe..0000000000 --- a/scripts/stripdecls.py +++ /dev/null @@ -1,140 +0,0 @@ -#!/usr/bin/python -# vim: set fileencoding=utf-8: - -from __future__ import print_function, unicode_literals, division - -from clang.cindex import Index, CursorKind -from collections import namedtuple, OrderedDict, defaultdict -import sys -import os - - -DECL_KINDS = { - CursorKind.FUNCTION_DECL, -} - - -Strip = namedtuple('Strip', 'start_line start_column end_line end_column') - - -def main(progname, cfname, only_static, move_all): - cfname = os.path.abspath(os.path.normpath(cfname)) - - hfname1 = os.path.splitext(cfname)[0] + os.extsep + 'h' - hfname2 = os.path.splitext(cfname)[0] + '_defs' + os.extsep + 'h' - - files_to_modify = (cfname, hfname1, hfname2) - - index = Index.create() - src_dirname = os.path.join(os.path.dirname(__file__), '..', 'src') - src_dirname = os.path.abspath(os.path.normpath(src_dirname)) - relname = os.path.join(src_dirname, 'nvim') - unit = index.parse(cfname, args=('-I' + src_dirname, - '-DUNIX', - '-DEXITFREE', - '-DFEAT_USR_CMDS', - '-DFEAT_CMDL_COMPL', - '-DFEAT_COMPL_FUNC', - '-DPROTO', - '-DUSE_MCH_ERRMSG')) - cursor = unit.cursor - - tostrip = defaultdict(OrderedDict) - definitions = set() - - for child in cursor.get_children(): - if not (child.location and child.location.file): - continue - fname = os.path.abspath(os.path.normpath(child.location.file.name)) - if fname not in files_to_modify: - continue - if child.kind not in DECL_KINDS: - continue - if only_static and next(child.get_tokens()).spelling == 'static': - continue - - if child.is_definition() and fname == cfname: - definitions.add(child.spelling) - else: - stripdict = tostrip[fname] - assert(child.spelling not in stripdict) - stripdict[child.spelling] = Strip( - child.extent.start.line, - child.extent.start.column, - child.extent.end.line, - child.extent.end.column, - ) - - for (fname, stripdict) in tostrip.items(): - if not move_all: - for name in set(stripdict) - definitions: - stripdict.pop(name) - - if not stripdict: - continue - - if fname.endswith('.h'): - is_h_file = True - include_line = next(reversed(stripdict.values())).start_line + 1 - else: - is_h_file = False - include_line = next(iter(stripdict.values())).start_line - - lines = None - generated_existed = os.path.exists(fname + '.generated.h') - with open(fname, 'rb') as F: - lines = list(F) - - stripped = [] - - for name, position in reversed(stripdict.items()): - sl = slice(position.start_line - 1, position.end_line) - if is_h_file: - include_line -= sl.stop - sl.start - stripped += lines[sl] - lines[sl] = () - - if not generated_existed: - lines[include_line:include_line] = [ - '#ifdef INCLUDE_GENERATED_DECLARATIONS\n', - '# include "{0}.generated.h"\n'.format( - os.path.relpath(fname, relname)), - '#endif\n', - ] - - with open(fname, 'wb') as F: - F.writelines(lines) - - -if __name__ == '__main__': - progname = sys.argv[0] - args = sys.argv[1:] - if not args or '--help' in args: - print('Usage:') - print('') - print(' {0} [--static [--all]] file.c...'.format(progname)) - print('') - print('Stripts all declarations from file.c, file.h and file_defs.h.') - print('If --static argument is given then only static declarations are') - print('stripped. Declarations are stripped only if corresponding') - print('definition is found unless --all argument was given.') - print('') - print('Note: it is assumed that static declarations starts with "static"') - print(' keyword.') - sys.exit(0 if args else 1) - - if args[0] == '--static': - only_static = True - args = args[1:] - else: - only_static = False - - if args[0] == '--all': - move_all = True - args = args[1:] - else: - move_all = False - - for cfname in args: - print('Processing {0}'.format(cfname)) - main(progname, cfname, only_static, move_all) 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..57f51d9d46 100755 --- a/scripts/vim-patch.sh +++ b/scripts/vim-patch.sh @@ -36,7 +36,7 @@ usage() { echo " can be a Vim version (8.0.xxx) or a Git hash." echo " -P {vim-revision} Download, generate and apply a Vim patch." echo " -g {vim-revision} Download a Vim patch." - echo " -s Create a vim-patch pull request." + echo " -s [pr args] Create a vim-patch pull request." echo " -r {pr-number} Review a vim-patch pull request." echo " -V Clone the Vim source code to \$VIM_SOURCE_DIR." echo @@ -190,8 +190,8 @@ preprocess_patch() { 2>/dev/null $nvim --cmd 'set dir=/tmp' +'g@^diff --git a/\<\%('"${na_files}"'\)\>@norm! d/\v(^diff)|%$
' +w +q "$file" # Remove *.proto, Make*, INSTALL*, gui_*, beval.*, some if_*, gvim, libvterm, tee, VisVim, xpm, xxd - local na_src='auto\|configure.*\|GvimExt\|libvterm\|proto\|tee\|VisVim\|xpm\|xxd\|Make*\|INSTALL*\|beval.*\|gui_*\|if_lua\|if_mzsch\|if_olepp\|if_ole\|if_perl\|if_py\|if_ruby\|if_tcl\|if_xcmdsrv' - 2>/dev/null $nvim --cmd 'set dir=/tmp' +'g@^diff --git a/src/\S*\<\%(testdir/\)\@<!\%('"${na_src}"'\)@norm! d/\v(^diff)|%$
' +w +q "$file" + local na_src='auto\|configure.*\|GvimExt\|libvterm\|proto\|tee\|VisVim\|xpm\|xxd\|Make.*\|INSTALL.*\|beval.*\|gui.*\|if_lua\|if_mzsch\|if_olepp\|if_ole\|if_perl\|if_py\|if_ruby\|if_tcl\|if_xcmdsrv' + 2>/dev/null $nvim --cmd 'set dir=/tmp' +'g@^diff --git a/src/\S*\<\%(testdir/\)\@<!\%('"${na_src}"'\)\>@norm! d/\v(^diff)|%$
' +w +q "$file" # Remove unwanted Vim doc files. local na_doc='channel\.txt\|netbeans\.txt\|os_\w\+\.txt\|term\.txt\|todo\.txt\|version\d\.txt\|vim9\.txt\|sponsor\.txt\|intro\.txt\|tags' @@ -201,19 +201,18 @@ preprocess_patch() { 2>/dev/null $nvim --cmd 'set dir=/tmp' +'%s/^@@.*\n.*For Vim version.*Last change.*\n.*For Vim version.*Last change.*//' +w +q "$file" # Remove gui, option, setup, screen dumps, testdir/Make_*.mak files - local na_src_testdir='gen_opt_test.vim\|gui_.*\|Make_amiga.mak\|Make_dos.mak\|Make_ming.mak\|Make_vms.mms\|dumps/.*.dump\|setup_gui.vim' + local na_src_testdir='gen_opt_test\.vim\|gui_.*\|Make_amiga\.mak\|Make_dos\.mak\|Make_ming\.mak\|Make_vms\.mms\|dumps/.*\.dump\|setup_gui\.vim' 2>/dev/null $nvim --cmd 'set dir=/tmp' +'g@^diff --git a/src/testdir/\<\%('"${na_src_testdir}"'\)\>@norm! d/\v(^diff)|%$
' +w +q "$file" # Remove testdir/test_*.vim files - local na_src_testdir='balloon.*\|channel.*\|crypt.vim\|gui.*\|job_fails.vim\|json.vim\|mzscheme.vim\|netbeans.*\|paste.vim\|popupwin.*\|restricted.vim\|shortpathname.vim\|tcl.vim\|terminal.*\|xxd.vim' + local na_src_testdir='balloon.*\|channel.*\|crypt\.vim\|gui.*\|job_fails\.vim\|json\.vim\|mzscheme\.vim\|netbeans.*\|paste\.vim\|popupwin.*\|restricted\.vim\|shortpathname\.vim\|tcl\.vim\|terminal.*\|xxd\.vim' 2>/dev/null $nvim --cmd 'set dir=/tmp' +'g@^diff --git a/src/testdir/\<test_\%('"${na_src_testdir}"'\)\>@norm! d/\v(^diff)|%$
' +w +q "$file" # Remove version.c #7555 - local na_po='version.c' - 2>/dev/null $nvim --cmd 'set dir=/tmp' +'g@^diff --git a/src/\<\%('${na_po}'\)\>@norm! d/\v(^diff)|%$
' +w +q "$file" + 2>/dev/null $nvim --cmd 'set dir=/tmp' +'g@^diff --git a/src/\<\%(version\.c\)\>@norm! d/\v(^diff)|%$
' +w +q "$file" # Remove some *.po files. #5622 - local na_po='sjiscorr.c\|ja.sjis.po\|ko.po\|pl.cp1250.po\|pl.po\|ru.cp1251.po\|uk.cp1251.po\|zh_CN.cp936.po\|zh_CN.po\|zh_TW.po' + local na_po='sjiscorr\.c\|ja\.sjis\.po\|ko\.po\|pl\.cp1250\.po\|pl\.po\|ru\.cp1251\.po\|uk\.cp1251\.po\|zh_CN\.cp936\.po\|zh_CN\.po\|zh_TW\.po' 2>/dev/null $nvim --cmd 'set dir=/tmp' +'g@^diff --git a/src/po/\<\%('${na_po}'\)\>@norm! d/\v(^diff)|%$
' +w +q "$file" # Remove vimrc_example.vim @@ -236,12 +235,16 @@ preprocess_patch() { LC_ALL=C sed -e 's/\( [ab]\/src\/nvim\)\/session\(\.[ch]\)/\1\/ex_session\2/g' \ "$file" > "$file".tmp && mv "$file".tmp "$file" + # Rename highlight.c to highlight_group.c + LC_ALL=C sed -e 's/\( [ab]\/src\/nvim\)\/highlight\(\.[ch]\)/\1\/highlight_group\2/g' \ + "$file" > "$file".tmp && mv "$file".tmp "$file" + # Rename test_urls.vim to check_urls.vim - LC_ALL=C sed -e 's@\( [ab]\)/runtime/doc/test\(_urls.vim\)@\1/scripts/check\2@g' \ + LC_ALL=C sed -e 's@\( [ab]\)/runtime/doc/test\(_urls\.vim\)@\1/scripts/check\2@g' \ "$file" > "$file".tmp && mv "$file".tmp "$file" # Rename path to check_colors.vim - LC_ALL=C sed -e 's@\( [ab]/runtime\)/colors/\(tools/check_colors.vim\)@\1/\2@g' \ + LC_ALL=C sed -e 's@\( [ab]/runtime\)/colors/\(tools/check_colors\.vim\)@\1/\2@g' \ "$file" > "$file".tmp && mv "$file".tmp "$file" } @@ -266,8 +269,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 +283,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 @@ -322,7 +332,8 @@ stage_patch() { * Do this only for _related_ patches (otherwise it increases the size of the pull request, making it harder to review) - When you are done, try "%s -s" to create the pull request. + When you are done, try "%s -s" to create the pull request, + or "%s -s --draft" to create a draft pull request. See the wiki for more information: * https://github.com/neovim/neovim/wiki/Merging-patches-from-upstream-vim @@ -331,17 +342,21 @@ stage_patch() { } gh_pr() { - gh pr create --title "$1" --body "$2" + local pr_title + local pr_body + pr_title="$1" + pr_body="$2" + shift 2 + gh pr create --title "${pr_title}" --body "${pr_body}" "$@" } git_hub_pr() { local pr_message pr_message="$(printf '%s\n\n%s\n' "$1" "$2")" - git hub pull new -m "${pr_message}" + shift 2 + 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 +407,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 +713,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}" @@ -788,7 +809,8 @@ while getopts "hlLmMVp:P:g:r:s" opt; do exit 0 ;; s) - submit_pr + shift # remove opt + submit_pr "$@" exit 0 ;; V) diff --git a/src/cjson/lua_cjson.c b/src/cjson/lua_cjson.c index cf9e82c38e..c243f93c05 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) { @@ -1110,7 +1110,7 @@ static int json_is_invalid_number(json_parse_t *json) /* Reject numbers starting with 0x, or leading zeros */ if (*p == '0') { - int ch2 = *(p + 1); + char ch2 = *(p + 1); if ((ch2 | 0x20) == 'x' || /* Hex */ ('0' <= ch2 && ch2 <= '9')) /* Leading zero */ @@ -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/clint.py b/src/clint.py index 4b7bf002e6..75aaba176d 100755 --- a/src/clint.py +++ b/src/clint.py @@ -191,7 +191,6 @@ _ERROR_CATEGORIES = [ 'readability/fn_size', 'readability/multiline_comment', 'readability/multiline_string', - 'readability/nolint', 'readability/nul', 'readability/todo', 'readability/utf8', @@ -298,9 +297,6 @@ def ParseNolintSuppressions(filename, raw_line, linenum, error): if category in _ERROR_CATEGORIES: _error_suppressions.setdefault( category, set()).add(linenum) - else: - error(filename, linenum, 'readability/nolint', 5, - 'Unknown NOLINT error category: %s' % category) def ParseKnownErrorSuppressions(filename, raw_lines, linenum): @@ -373,7 +369,7 @@ def Search(pattern, s): return _regexp_compile_cache[pattern].search(s) -class _IncludeState(dict): +class _IncludeState(dict): # lgtm [py/missing-equals] """Tracks line numbers for includes, and the order in which includes appear. @@ -3191,8 +3187,8 @@ def CheckLanguage(filename, clean_lines, linenum, file_extension, r'|li_(?:next|prev|tv))\b', line) if match: error(filename, linenum, 'runtime/deprecated', 4, - 'Accessing list_T internals directly is prohibited ' - '(hint: see commit d46e37cb4c71)') + 'Accessing list_T internals directly is prohibited; ' + 'see https://github.com/neovim/neovim/wiki/List-management-in-Neovim') # Check for suspicious usage of "if" like # } if (a == b) { diff --git a/src/mpack/lmpack.c b/src/mpack/lmpack.c index 87acf46592..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)); @@ -595,6 +595,7 @@ static void lmpack_unparse_enter(mpack_parser_t *parser, mpack_node_t *node) /* push the pair */ result = lua_next(L, -2); assert(result); /* should not be here if the map was fully processed */ + (void)result; /* ignore unused warning */ if (parent->key_visited) { /* release the current key */ lmpack_unref(L, packer->reg, (int)parent->data[1].i); @@ -1010,6 +1011,7 @@ static int lmpack_session_reply(lua_State *L) "invalid request id"); result = mpack_rpc_reply(session->session, &b, &bl, (mpack_uint32_t)id); assert(result == MPACK_OK); + (void)result; /* ignore unused warning */ lua_pushlstring(L, buf, sizeof(buf) - bl); return 1; } @@ -1027,6 +1029,7 @@ static int lmpack_session_notify(lua_State *L) session = lmpack_check_session(L, 1); result = mpack_rpc_notify(session->session, &b, &bl); assert(result == MPACK_OK); + (void)result; /* ignore unused warning */ lua_pushlstring(L, buf, sizeof(buf) - bl); return 1; } diff --git a/src/mpack/object.h b/src/mpack/object.h index 5327e56e18..e69821f9de 100644 --- a/src/mpack/object.h +++ b/src/mpack/object.h @@ -22,7 +22,7 @@ enum { }; /* Storing integer in pointers in undefined behavior according to the C - * standard. Define a union type to accomodate arbitrary user data associated + * standard. Define a union type to accommodate arbitrary user data associated * with nodes(and with requests in rpc.h). */ typedef union { void *p; diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt index 185d55daed..b7f9292de1 100644..100755 --- a/src/nvim/CMakeLists.txt +++ b/src/nvim/CMakeLists.txt @@ -39,6 +39,7 @@ set(GENERATED_UI_EVENTS ${GENERATED_DIR}/ui_events.generated.h) set(GENERATED_UI_EVENTS_CALL ${GENERATED_DIR}/ui_events_call.generated.h) set(GENERATED_UI_EVENTS_REMOTE ${GENERATED_DIR}/ui_events_remote.generated.h) set(GENERATED_UI_EVENTS_BRIDGE ${GENERATED_DIR}/ui_events_bridge.generated.h) +set(GENERATED_UI_EVENTS_CLIENT ${GENERATED_DIR}/ui_events_client.generated.h) set(GENERATED_UI_EVENTS_METADATA ${GENERATED_DIR}/api/private/ui_events_metadata.generated.h) set(GENERATED_EX_CMDS_ENUM ${GENERATED_INCLUDES_DIR}/ex_cmds_enum.generated.h) set(GENERATED_EX_CMDS_DEFS ${GENERATED_DIR}/ex_cmds_defs.generated.h) @@ -57,11 +58,14 @@ set(UNICODE_TABLES_GENERATOR ${GENERATOR_DIR}/gen_unicode_tables.lua) set(UNICODE_DIR ${PROJECT_SOURCE_DIR}/unicode) set(GENERATED_UNICODE_TABLES ${GENERATED_DIR}/unicode_tables.generated.h) set(VIM_MODULE_FILE ${GENERATED_DIR}/lua/vim_module.generated.h) -set(LUA_VIM_MODULE_SOURCE ${PROJECT_SOURCE_DIR}/src/nvim/lua/vim.lua) +set(LUA_EDITOR_MODULE_SOURCE ${PROJECT_SOURCE_DIR}/runtime/lua/vim/_editor.lua) 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_INIT_PACKAGES_MODULE_SOURCE ${PROJECT_SOURCE_DIR}/runtime/lua/vim/_init_packages.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") @@ -132,6 +136,9 @@ foreach(sfile ${NVIM_SOURCES}) if(${f} MATCHES "^(regexp_nfa.c)$") list(APPEND to_remove ${sfile}) endif() + if(${f} MATCHES "^(regexp_bt.c)$") + list(APPEND to_remove ${sfile}) + endif() if(WIN32 AND ${f} MATCHES "^(pty_process_unix.c)$") list(APPEND to_remove ${sfile}) endif() @@ -175,12 +182,15 @@ foreach(sfile ${CONV_SOURCES}) message(FATAL_ERROR "${sfile} doesn't exist (it was added to CONV_SOURCES)") endif() endforeach() -# xdiff, mpack, lua-cjson: inlined external project, we don't maintain it. #9306 -list(APPEND CONV_SOURCES ${EXTERNAL_SOURCES}) if(NOT MSVC) set_source_files_properties( ${CONV_SOURCES} PROPERTIES COMPILE_FLAGS "${COMPILE_FLAGS} -Wno-conversion") + + # xdiff, mpack, lua-cjson: inlined external project, we don't maintain it. #9306 + set_source_files_properties( + ${EXTERNAL_SOURCES} PROPERTIES COMPILE_FLAGS "${COMPILE_FLAGS} -Wno-conversion -Wno-missing-noreturn -Wno-missing-format-attribute -Wno-double-promotion") + # gperf generates ANSI-C with incorrect linkage, ignore it. check_c_compiler_flag(-Wstatic-in-inline HAS_WSTATIC_IN_INLINE) if(HAS_WSTATIC_IN_INLINE) @@ -258,12 +268,14 @@ endif() # NVIM_GENERATED_SOURCES: generated source files # These lists must be mutually exclusive. foreach(sfile ${NVIM_SOURCES} + "${CMAKE_CURRENT_LIST_DIR}/regexp_bt.c" "${CMAKE_CURRENT_LIST_DIR}/regexp_nfa.c" ${GENERATED_API_DISPATCH} "${GENERATED_UI_EVENTS_CALL}" "${GENERATED_UI_EVENTS_REMOTE}" "${GENERATED_UI_EVENTS_BRIDGE}" "${GENERATED_KEYSETS}" + "${GENERATED_UI_EVENTS_CLIENT}" ) get_filename_component(full_d ${sfile} PATH) file(RELATIVE_PATH d "${CMAKE_CURRENT_LIST_DIR}" "${full_d}") @@ -326,19 +338,29 @@ add_custom_command( add_custom_command( OUTPUT ${VIM_MODULE_FILE} - COMMAND ${LUA_PRG} ${CHAR_BLOB_GENERATOR} ${VIM_MODULE_FILE} - ${LUA_VIM_MODULE_SOURCE} vim_module - ${LUA_SHARED_MODULE_SOURCE} shared_module - ${LUA_INSPECT_MODULE_SOURCE} inspect_module - ${LUA_F_MODULE_SOURCE} lua_F_module - ${LUA_META_MODULE_SOURCE} lua_meta_module + COMMAND ${CMAKE_COMMAND} -E env + "LUAC_PRG=${LUAC_PRG}" + ${LUA_PRG} ${CHAR_BLOB_GENERATOR} -c ${VIM_MODULE_FILE} + ${LUA_INIT_PACKAGES_MODULE_SOURCE} "vim._init_packages" + ${LUA_INSPECT_MODULE_SOURCE} "vim.inspect" + ${LUA_EDITOR_MODULE_SOURCE} "vim._editor" + ${LUA_SHARED_MODULE_SOURCE} "vim.shared" + ${LUA_F_MODULE_SOURCE} "vim.F" + ${LUA_META_MODULE_SOURCE} "vim._meta" + ${LUA_FILETYPE_MODULE_SOURCE} "vim.filetype" + ${LUA_KEYMAP_MODULE_SOURCE} "vim.keymap" DEPENDS ${CHAR_BLOB_GENERATOR} - ${LUA_VIM_MODULE_SOURCE} + ${LUA_INIT_PACKAGES_MODULE_SOURCE} + ${LUA_EDITOR_MODULE_SOURCE} ${LUA_SHARED_MODULE_SOURCE} ${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 ) list(APPEND NVIM_GENERATED_SOURCES @@ -351,6 +373,7 @@ add_custom_command( ${GENERATED_UI_EVENTS_REMOTE} ${GENERATED_UI_EVENTS_BRIDGE} ${GENERATED_UI_EVENTS_METADATA} + ${GENERATED_UI_EVENTS_CLIENT} COMMAND ${LUA_PRG} ${API_UI_EVENTS_GENERATOR} ${CMAKE_CURRENT_LIST_DIR} ${CMAKE_CURRENT_LIST_DIR}/api/ui_events.in.h ${GENERATED_UI_EVENTS} @@ -358,6 +381,7 @@ add_custom_command( ${GENERATED_UI_EVENTS_REMOTE} ${GENERATED_UI_EVENTS_BRIDGE} ${GENERATED_UI_EVENTS_METADATA} + ${GENERATED_UI_EVENTS_CLIENT} DEPENDS ${API_UI_EVENTS_GENERATOR} ${GENERATOR_C_GRAMMAR} @@ -468,9 +492,11 @@ list(APPEND NVIM_LINK_LIBRARIES if(UNIX) list(APPEND NVIM_LINK_LIBRARIES - m - util - ) + m) + if (NOT CMAKE_SYSTEM_NAME STREQUAL "SunOS") + list(APPEND NVIM_LINK_LIBRARIES + util) + endif() endif() set(NVIM_EXEC_LINK_LIBRARIES ${NVIM_LINK_LIBRARIES} ${LUA_PREFERRED_LIBRARIES}) @@ -485,6 +511,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..ccf4ae3d02 --- /dev/null +++ b/src/nvim/api/autocmd.c @@ -0,0 +1,979 @@ +// 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 all autocommands that match the corresponding {opts}. +/// +/// These examples will get autocommands matching ALL the given criteria: +/// <pre> +/// -- Matches all criteria +/// autocommands = vim.api.nvim_get_autocmds({ +/// group = "MyGroup", +/// event = {"BufEnter", "BufWinEnter"}, +/// pattern = {"*.c", "*.h"} +/// }) +/// +/// -- All commands from one group +/// autocommands = vim.api.nvim_get_autocmds({ +/// group = "MyGroup", +/// }) +/// </pre> +/// +/// NOTE: When multiple patterns or events are provided, it will find all the autocommands that +/// match any combination of them. +/// +/// @param opts Dictionary with at least one of the following: +/// - group (string|integer): the autocommand group name or id to match against. +/// - event (string|array): event or events to match against |autocmd-events|. +/// - pattern (string|array): pattern or patterns to match against |autocmd-pattern|. +/// @return Array of autocommands matching the criteria, with each item +/// containing the following fields: +/// - id (number): the autocommand id (only when defined with the API). +/// - group (integer): the autocommand group id. +/// - desc (string): the autocommand description. +/// - event (string): the autocommand event. +/// - command (string): the autocommand command. +/// - once (boolean): whether the autocommand is only run once. +/// - pattern (string): the autocommand pattern. +/// If the autocommand is buffer local |autocmd-buffer-local|: +/// - buflocal (boolean): true if the autocommand is buffer local. +/// - buffer (number): the buffer number. +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]; + + Array buffers = ARRAY_DICT_INIT; + + bool event_set[NUM_EVENTS] = { false }; + bool check_event = false; + + int group = 0; + + switch (opts->group.type) { + case kObjectTypeNil: + break; + case kObjectTypeString: + group = augroup_find(opts->group.data.string.data); + if (group < 0) { + api_set_error(err, kErrorTypeValidation, "invalid augroup passed."); + goto cleanup; + } + break; + case kObjectTypeInteger: + group = (int)opts->group.data.integer; + char *name = augroup_name(group); + if (!augroup_exists(name)) { + api_set_error(err, kErrorTypeValidation, "invalid augroup passed."); + goto cleanup; + } + break; + default: + api_set_error(err, kErrorTypeValidation, "group must be a string or an integer."); + 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; + } + } + + if (opts->pattern.type != kObjectTypeNil && opts->buffer.type != kObjectTypeNil) { + api_set_error(err, kErrorTypeValidation, + "Cannot use both 'pattern' and 'buffer'"); + 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) { + if (v.data.array.size > AUCMD_MAX_PATTERNS) { + api_set_error(err, kErrorTypeValidation, + "Too many patterns. Please limit yourself to %d or fewer", + AUCMD_MAX_PATTERNS); + goto cleanup; + } + + FOREACH_ITEM(v.data.array, item, { + if (item.type != kObjectTypeString) { + api_set_error(err, kErrorTypeValidation, "Invalid value for 'pattern': must be a string"); + goto cleanup; + } + + 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 (opts->buffer.type == kObjectTypeInteger || opts->buffer.type == kObjectTypeBuffer) { + 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(buffers, CSTR_TO_OBJ((char *)pattern_buflocal)); + } else if (opts->buffer.type == kObjectTypeArray) { + if (opts->buffer.data.array.size > AUCMD_MAX_PATTERNS) { + api_set_error(err, + kErrorTypeValidation, + "Too many buffers. Please limit yourself to %d or fewer", AUCMD_MAX_PATTERNS); + goto cleanup; + } + + FOREACH_ITEM(opts->buffer.data.array, bufnr, { + if (bufnr.type != kObjectTypeInteger && bufnr.type != kObjectTypeBuffer) { + api_set_error(err, kErrorTypeValidation, "Invalid value for 'buffer': must be an integer"); + goto cleanup; + } + + buf_T *buf = find_buffer_by_handle((Buffer)bufnr.data.integer, err); + if (ERROR_SET(err)) { + goto cleanup; + } + + snprintf((char *)pattern_buflocal, BUFLOCAL_PAT_LEN, "<buffer=%d>", (int)buf->handle); + ADD(buffers, CSTR_TO_OBJ((char *)pattern_buflocal)); + }); + } else if (opts->buffer.type != kObjectTypeNil) { + api_set_error(err, kErrorTypeValidation, + "Invalid value for 'buffer': must be an integer or array of integers"); + goto cleanup; + } + + FOREACH_ITEM(buffers, bufnr, { + pattern_filters[pattern_filter_count] = (char_u *)bufnr.data.string.data; + pattern_filter_count += 1; + }); + + 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->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: + api_free_array(buffers); + return autocmd_list; +} + +/// Create an |autocommand| +/// +/// The API allows for two (mutually exclusive) types of actions to be executed when the autocommand +/// triggers: a callback function (Lua or Vimscript), or a command (like regular autocommands). +/// +/// Example using callback: +/// <pre> +/// -- Lua function +/// local myluafun = function() print("This buffer enters") end +/// +/// -- Vimscript function name (as a string) +/// local myvimfun = "g:MyVimFunction" +/// +/// vim.api.nvim_create_autocmd({"BufEnter", "BufWinEnter"}, { +/// pattern = {"*.c", "*.h"}, +/// callback = myluafun, -- Or myvimfun +/// }) +/// </pre> +/// +/// Example using command: +/// <pre> +/// vim.api.nvim_create_autocmd({"BufEnter", "BufWinEnter"}, { +/// pattern = {"*.c", "*.h"}, +/// command = "echo 'Entering a C or C++ file'", +/// }) +/// </pre> +/// +/// Example values for pattern: +/// <pre> +/// pattern = "*.py" +/// pattern = { "*.py", "*.pyi" } +/// </pre> +/// +/// Examples values for event: +/// <pre> +/// "BufPreWrite" +/// {"CursorHold", "BufPreWrite", "BufPostWrite"} +/// </pre> +/// +/// @param event (string|array) The event or events to register this autocommand +/// @param opts Dictionary of autocommand options: +/// - group (string|integer) optional: the autocommand group name or +/// id to match against. +/// - pattern (string|array) optional: pattern or patterns to match +/// against |autocmd-pattern|. +/// - buffer (integer) optional: buffer number for buffer local autocommands +/// |autocmd-buflocal|. Cannot be used with {pattern}. +/// - desc (string) optional: description of the autocommand. +/// - callback (function|string) optional: if a string, the name of a Vimscript function +/// to call when this autocommand is triggered. Otherwise, a Lua function which is +/// called when this autocommand is triggered. Cannot be used with {command}. Lua +/// callbacks can return true to delete the autocommand; in addition, they accept a +/// single table argument with the following keys: +/// - id: (number) the autocommand id +/// - event: (string) the name of the event that triggered the autocommand +/// |autocmd-events| +/// - group: (number|nil) the autocommand group id, if it exists +/// - match: (string) the expanded value of |<amatch>| +/// - buf: (number) the expanded value of |<abuf>| +/// - file: (string) the expanded value of |<afile>| +/// - command (string) optional: Vim command to execute on event. Cannot be used with +/// {callback} +/// - once (boolean) optional: defaults to false. Run the autocommand +/// only once |autocmd-once|. +/// - nested (boolean) optional: defaults to false. Run nested +/// autocommands |autocmd-nested|. +/// +/// @return Integer id of the created autocommand. +/// @see |autocommand| +/// @see |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; + 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", true, 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); + + int au_group = get_augroup_from_object(opts->group, err); + if (au_group == AUGROUP_ERROR) { + goto cleanup; + } + + if (!get_patterns_from_pattern_or_buf(&patterns, opts->pattern, opts->buffer, err)) { + 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 autocommand by id. +/// +/// NOTE: Only autocommands created via the API have an id. +/// @param id Integer The id returned by nvim_create_autocmd +/// @see |nvim_create_autocmd()| +void nvim_del_autocmd(Integer id, Error *err) + FUNC_API_SINCE(9) +{ + if (id <= 0) { + api_set_error(err, kErrorTypeException, "Invalid autocmd id"); + return; + } + if (!autocmd_delete_id(id)) { + api_set_error(err, kErrorTypeException, "Failed to delete autocmd"); + } +} + +/// Clear all autocommands that match the corresponding {opts}. To delete +/// a particular autocmd, see |nvim_del_autocmd|. +/// @param opts Parameters +/// - event: (string|table) +/// Examples: +/// - event: "pat1" +/// - event: { "pat1" } +/// - event: { "pat1", "pat2", "pat3" } +/// - pattern: (string|table) +/// - pattern or patterns to match exactly. +/// - For example, if you have `*.py` as that pattern for the autocmd, +/// you must pass `*.py` exactly to clear it. `test.py` will not +/// match the pattern. +/// - defaults to clearing all patterns. +/// - NOTE: Cannot be used with {buffer} +/// - buffer: (bufnr) +/// - clear only |autocmd-buflocal| autocommands. +/// - NOTE: Cannot be used with {pattern} +/// - group: (string|int) The augroup name or id. +/// - NOTE: If not passed, will only delete autocmds *not* in any group. +/// +void nvim_clear_autocmds(Dict(clear_autocmds) *opts, Error *err) + FUNC_API_SINCE(9) +{ + // TODO(tjdevries): Future improvements: + // - once: (boolean) - Only clear autocmds with once. See |autocmd-once| + // - nested: (boolean) - Only clear autocmds with nested. See |autocmd-nested| + // - group: Allow passing "*" or true or something like that to force doing all + // autocmds, regardless of their group. + + Array patterns = ARRAY_DICT_INIT; + Array event_array = ARRAY_DICT_INIT; + + if (!unpack_string_or_array(&event_array, &opts->event, "event", false, err)) { + goto cleanup; + } + + if (opts->pattern.type != kObjectTypeNil && opts->buffer.type != kObjectTypeNil) { + api_set_error(err, kErrorTypeValidation, + "Cannot use both 'pattern' and 'buffer'"); + goto cleanup; + } + + int au_group = get_augroup_from_object(opts->group, err); + if (au_group == AUGROUP_ERROR) { + goto cleanup; + } + + if (!get_patterns_from_pattern_or_buf(&patterns, opts->pattern, opts->buffer, err)) { + goto cleanup; + } + + // When we create the autocmds, we want to say that they are all matched, so that's * + // but when we clear them, we want to say that we didn't pass a pattern, so that's NUL + if (patterns.size == 0) { + ADD(patterns, STRING_OBJ(STATIC_CSTR_TO_STRING(""))); + } + + // If we didn't pass any events, that means clear all events. + if (event_array.size == 0) { + FOR_ALL_AUEVENTS(event) { + FOREACH_ITEM(patterns, pat_object, { + char_u *pat = (char_u *)pat_object.data.string.data; + if (!clear_autocmd(event, pat, au_group, err)) { + goto cleanup; + } + }); + } + } else { + FOREACH_ITEM(event_array, event_str, { + GET_ONE_EVENT(event_nr, event_str, cleanup); + + FOREACH_ITEM(patterns, pat_object, { + char_u *pat = (char_u *)pat_object.data.string.data; + if (!clear_autocmd(event_nr, pat, au_group, err)) { + goto cleanup; + } + }); + }); + } + +cleanup: + api_free_array(event_array); + api_free_array(patterns); + + return; +} + +/// Create or get an autocommand group |autocmd-groups|. +/// +/// To get an existing group id, do: +/// <pre> +/// local id = vim.api.nvim_create_augroup("MyGroup", { +/// clear = false +/// }) +/// </pre> +/// +/// @param name String: The name of the group +/// @param opts Dictionary Parameters +/// - clear (bool) optional: defaults to true. Clear existing +/// commands if the group already exists |autocmd-groups|. +/// @return Integer id of the created group. +/// @see |autocmd-groups| +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 autocommand group by id. +/// +/// To get a group id one can use |nvim_get_autocmds()|. +/// +/// NOTE: behavior differs from |augroup-delete|. When deleting a group, autocommands contained in +/// this group will also be deleted and cleared. This group will no longer exist. +/// @param id Integer The id of the group. +/// @see |nvim_del_augroup_by_name()| +/// @see |nvim_create_augroup()| +void nvim_del_augroup_by_id(Integer id, Error *err) + FUNC_API_SINCE(9) +{ + TRY_WRAP({ + try_start(); + char *name = augroup_name((int)id); + augroup_del(name, false); + try_end(err); + }); +} + +/// Delete an autocommand group by name. +/// +/// NOTE: behavior differs from |augroup-delete|. When deleting a group, autocommands contained in +/// this group will also be deleted and cleared. This group will no longer exist. +/// @param name String The name of the group. +/// @see |autocommand-groups| +void nvim_del_augroup_by_name(String name, Error *err) + FUNC_API_SINCE(9) +{ + TRY_WRAP({ + try_start(); + augroup_del(name.data, false); + try_end(err); + }); +} + +/// Execute all autocommands for {event} that match the corresponding +/// {opts} |autocmd-execute|. +/// @param event (String|Array) The event or events to execute +/// @param opts Dictionary of autocommand options: +/// - group (string|integer) optional: the autocommand group name or +/// id to match against. |autocmd-groups|. +/// - pattern (string|array) optional: defaults to "*" |autocmd-pattern|. Cannot be used +/// with {buffer}. +/// - buffer (integer) optional: buffer number |autocmd-buflocal|. Cannot be used with +/// {pattern}. +/// - modeline (bool) optional: defaults to true. Process the +/// modeline after the autocommands |<nomodeline>|. +/// @see |:doautocmd| +void nvim_exec_autocmds(Object event, Dict(exec_autocmds) *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", true, err)) { + goto cleanup; + } + + switch (opts->group.type) { + case kObjectTypeNil: + break; + case kObjectTypeString: + au_group = augroup_find(opts->group.data.string.data); + if (au_group == AUGROUP_ERROR) { + api_set_error(err, + kErrorTypeValidation, + "invalid augroup: %s", opts->group.data.string.data); + goto cleanup; + } + break; + case kObjectTypeInteger: + au_group = (int)opts->group.data.integer; + char *name = augroup_name(au_group); + if (!augroup_exists(name)) { + api_set_error(err, kErrorTypeValidation, "invalid augroup: %d", au_group); + goto cleanup; + } + break; + default: + api_set_error(err, kErrorTypeValidation, "'group' must be a string or an integer."); + 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, bool required, 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 { + if (required) { + api_set_error(err, + kErrorTypeValidation, + "'%s' must be an array or a string.", + k); + return false; + } + } + + return true; +} + +// Returns AUGROUP_ERROR if there was a problem with {group} +static int get_augroup_from_object(Object group, Error *err) +{ + int au_group = AUGROUP_ERROR; + + switch (group.type) { + case kObjectTypeNil: + return AUGROUP_DEFAULT; + case kObjectTypeString: + au_group = augroup_find(group.data.string.data); + if (au_group == AUGROUP_ERROR) { + api_set_error(err, + kErrorTypeValidation, + "invalid augroup: %s", group.data.string.data); + + return AUGROUP_ERROR; + } + + return au_group; + case kObjectTypeInteger: + au_group = (int)group.data.integer; + char *name = augroup_name(au_group); + if (!augroup_exists(name)) { + api_set_error(err, kErrorTypeValidation, "invalid augroup: %d", au_group); + return AUGROUP_ERROR; + } + + return au_group; + default: + api_set_error(err, kErrorTypeValidation, "'group' must be a string or an integer."); + return AUGROUP_ERROR; + } +} + +static bool get_patterns_from_pattern_or_buf(Array *patterns, Object pattern, Object buffer, + Error *err) +{ + const char_u pattern_buflocal[BUFLOCAL_PAT_LEN]; + + if (pattern.type != kObjectTypeNil && buffer.type != kObjectTypeNil) { + api_set_error(err, kErrorTypeValidation, + "cannot pass both: 'pattern' and 'buffer' for the same autocmd"); + return false; + } else if (pattern.type != kObjectTypeNil) { + Object *v = &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)) { + return false; + } + + 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"); + return false; + } + } else if (buffer.type != kObjectTypeNil) { + if (buffer.type != kObjectTypeInteger) { + api_set_error(err, + kErrorTypeValidation, + "'buffer' must be an integer"); + return false; + } + + buf_T *buf = find_buffer_by_handle((Buffer)buffer.data.integer, err); + if (ERROR_SET(err)) { + return false; + } + + snprintf((char *)pattern_buflocal, BUFLOCAL_PAT_LEN, "<buffer=%d>", (int)buf->handle); + ADD(*patterns, STRING_OBJ(cstr_to_string((char *)pattern_buflocal))); + } + + return true; +} + +static bool clear_autocmd(event_T event, char_u *pat, int au_group, Error *err) +{ + if (do_autocmd_event(event, pat, false, false, (char_u *)"", true, au_group) == FAIL) { + api_set_error(err, kErrorTypeException, "Failed to clear autocmd"); + 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 4076a0d220..6d5803a5fe 100644 --- a/src/nvim/api/buffer.c +++ b/src/nvim/api/buffer.c @@ -12,24 +12,19 @@ #include "nvim/api/buffer.h" #include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" +#include "nvim/autocmd.h" #include "nvim/buffer.h" #include "nvim/buffer_updates.h" #include "nvim/change.h" -#include "nvim/charset.h" #include "nvim/cursor.h" #include "nvim/decoration.h" #include "nvim/ex_cmds.h" #include "nvim/ex_docmd.h" #include "nvim/extmark.h" -#include "nvim/fileio.h" -#include "nvim/getchar.h" #include "nvim/lua/executor.h" -#include "nvim/map.h" -#include "nvim/map_defs.h" #include "nvim/mark.h" #include "nvim/memline.h" #include "nvim/memory.h" -#include "nvim/misc1.h" #include "nvim/move.h" #include "nvim/ops.h" #include "nvim/undo.h" @@ -57,7 +52,7 @@ /// whether a buffer is loaded. -/// Gets the buffer line count +/// Returns the number of lines in the given buffer. /// /// @param buffer Buffer handle, or 0 for current buffer /// @param[out] err Error details, if any @@ -138,7 +133,7 @@ Integer nvim_buf_line_count(Buffer buffer, Error *err) /// - buffer handle /// - on_reload: Lua callback invoked on reload. The entire buffer /// content should be considered changed. Args: -/// - the string "detach" +/// - the string "reload" /// - buffer handle /// - utf_sizes: include UTF-32 and UTF-16 size of the replaced /// region, as args to `on_lines`. @@ -292,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"); @@ -379,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, @@ -535,7 +529,7 @@ end: /// @param channel_id /// @param buffer Buffer handle, or 0 for current buffer /// @param start_row First line index -/// @param start_column Last column +/// @param start_column First column /// @param end_row Last line index /// @param end_column Last column /// @param replacement Array of lines to use as replacement @@ -559,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; @@ -603,12 +597,11 @@ void nvim_buf_set_text(uint64_t channel_id, Buffer buffer, Integer start_row, In if (start_row == end_row) { old_byte = (bcount_t)end_col - start_col; } else { - const char *bufline; old_byte += (bcount_t)strlen(str_at_start) - start_col; for (int64_t i = 1; i < end_row - start_row; i++) { int64_t lnum = start_row + i; - bufline = (char *)ml_get_buf(buf, lnum, false); + const char *bufline = (char *)ml_get_buf(buf, lnum, false); old_byte += (bcount_t)(strlen(bufline))+1; } old_byte += (bcount_t)end_col+1; @@ -762,6 +755,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. @@ -840,7 +935,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); @@ -849,7 +944,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. @@ -857,11 +952,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. @@ -869,11 +964,11 @@ 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|. @@ -1246,7 +1341,7 @@ ArrayOf(Integer, 2) nvim_buf_get_mark(Buffer buffer, String name, Error *err) /// If the current window already shows "buffer", the window is not switched /// If a window inside the current tabpage (including a float) already shows the /// buffer One of these windows will be set as current window temporarily. -/// Otherwise a temporary scratch window (calleed the "autocmd window" for +/// Otherwise a temporary scratch window (called the "autocmd window" for /// historical reasons) will be used. /// /// This is useful e.g. to call vimL functions that only work with the current @@ -1278,6 +1373,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_create_user_command +void nvim_buf_create_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; + create_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_create_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; @@ -1334,11 +1486,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..6a41df0aa9 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. @@ -51,11 +51,10 @@ Integer nvim_buf_get_number(Buffer buffer, Error *err) FUNC_API_SINCE(1) FUNC_API_DEPRECATED_SINCE(2) { - Integer rv = 0; buf_T *buf = find_buffer_by_handle(buffer, err); if (!buf) { - return rv; + return 0; } return buf->b_fnum; @@ -130,7 +129,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 +147,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 6f1fb15dac..8dca37a321 100644 --- a/src/nvim/api/extmark.c +++ b/src/nvim/api/extmark.c @@ -8,11 +8,12 @@ #include "nvim/api/extmark.h" #include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" +#include "nvim/decoration_provider.h" #include "nvim/extmark.h" +#include "nvim/highlight_group.h" #include "nvim/lua/executor.h" #include "nvim/memline.h" #include "nvim/screen.h" -#include "nvim/syntax.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "api/extmark.c.generated.h" @@ -85,12 +86,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 +107,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)); + } + + 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 (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)); + 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)); + } + 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)); } - if (kv_size(decor->virt_text)) { + 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 +163,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 +205,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 +230,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; } @@ -220,9 +259,9 @@ ArrayOf(Integer) nvim_buf_get_extmark_by_id(Buffer buffer, Integer ns_id, /// local pos = a.nvim_win_get_cursor(0) /// local ns = a.nvim_create_namespace('my-plugin') /// -- Create new extmark at line 1, column 1. -/// local m1 = a.nvim_buf_set_extmark(0, ns, 0, 0, 0, {}) +/// local m1 = a.nvim_buf_set_extmark(0, ns, 0, 0, {}) /// -- Create new extmark at line 3, column 1. -/// local m2 = a.nvim_buf_set_extmark(0, ns, 0, 2, 0, {}) +/// local m2 = a.nvim_buf_set_extmark(0, ns, 0, 2, {}) /// -- Get extmarks only from line 3. /// local ms = a.nvim_buf_get_extmarks(0, ns, {2,0}, {2,0}, {}) /// -- Get all marks in this buffer + namespace. @@ -252,7 +291,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 +349,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++) { @@ -339,7 +378,7 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, Object e /// @param col Column where to place the mark, 0-based. |api-indexing| /// @param opts Optional parameters. /// - id : id of the extmark to edit. -/// - end_line : ending line of the mark, 0-based inclusive. +/// - end_row : ending line of the mark, 0-based inclusive. /// - end_col : ending col of the mark, 0-based exclusive. /// - hl_group : name of the highlight group used to highlight /// this mark. @@ -404,6 +443,36 @@ 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. +/// - sign_text: string of length 1-2 used to display in the +/// sign column. +/// Note: ranges are unsupported and decorations are only +/// applied to start_row +/// - sign_hl_group: name of the highlight group used to +/// highlight the sign column text. +/// Note: ranges are unsupported and decorations are only +/// applied to start_row +/// - number_hl_group: name of the highlight group used to +/// highlight the number column. +/// Note: ranges are unsupported and decorations are only +/// applied to start_row +/// - line_hl_group: name of the highlight group used to +/// highlight the whole line. +/// Note: ranges are unsupported and decorations are only +/// applied to start_row +/// - cursorline_hl_group: name of the highlight group used to +/// highlight the line when the cursor is on the same line +/// as the mark and 'cursorline' is enabled. +/// Note: ranges are unsupported and decorations are only +/// applied to start_row +/// - conceal: string which should be either empty or a single +/// character. Enable concealing similar to |:syn-conceal|. +/// When a character is supplied it is used as |:syn-cchar|. +/// "hl_group" is used as highlight for the cchar if provided, +/// otherwise it defaults to |hl-Conceal|. +/// /// @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,30 +486,49 @@ 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; } int line2 = -1; - if (opts->end_line.type == kObjectTypeInteger) { - Integer val = opts->end_line.data.integer; - if (val < 0 || val > buf->b_ml.ml_line_count) { - api_set_error(err, kErrorTypeValidation, "end_line value outside range"); + + // For backward compatibility we support "end_line" as an alias for "end_row" + if (HAS_KEY(opts->end_line)) { + if (HAS_KEY(opts->end_row)) { + api_set_error(err, kErrorTypeValidation, "cannot use both end_row and end_line"); + goto error; + } + 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 && strict)) { + api_set_error(err, kErrorTypeValidation, "end_row value outside range"); goto error; } else { line2 = (int)val; } - } else if (HAS_KEY(opts->end_line)) { - api_set_error(err, kErrorTypeValidation, "end_line is not an integer"); + } else if (HAS_KEY(opts->end_row)) { + api_set_error(err, kErrorTypeValidation, "end_row is not an integer"); goto error; } @@ -458,13 +546,39 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer goto error; } - if (HAS_KEY(opts->hl_group)) { - decor.hl_id = object_to_hl_id(opts->hl_group, "hl_group", err); - if (ERROR_SET(err)) { - goto error; + struct { + const char *name; + Object *opt; + int *dest; + } hls[] = { + { "hl_group" , &opts->hl_group , &decor.hl_id }, + { "sign_hl_group" , &opts->sign_hl_group , &decor.sign_hl_id }, + { "number_hl_group" , &opts->number_hl_group , &decor.number_hl_id }, + { "line_hl_group" , &opts->line_hl_group , &decor.line_hl_id }, + { "cursorline_hl_group", &opts->cursorline_hl_group, &decor.cursorline_hl_id }, + { NULL, NULL, NULL }, + }; + + for (int j = 0; hls[j].name && hls[j].dest; j++) { + if (HAS_KEY(*hls[j].opt)) { + *hls[j].dest = object_to_hl_id(*hls[j].opt, hls[j].name, err); + if (ERROR_SET(err)) { + goto error; + } } } + if (opts->conceal.type == kObjectTypeString) { + String c = opts->conceal.data.string; + decor.conceal = true; + if (c.size) { + decor.conceal_char = utf_ptr2char((const char_u *)c.data); + } + } else if (HAS_KEY(opts->conceal)) { + api_set_error(err, kErrorTypeValidation, "conceal is not a String"); + goto error; + } + if (opts->virt_text.type == kObjectTypeArray) { decor.virt_text = parse_virt_text(opts->virt_text.data.array, err, &decor.virt_text_width); @@ -502,12 +616,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); @@ -567,14 +675,25 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer goto error; } + if (opts->sign_text.type == kObjectTypeString) { + if (!init_sign_text(&decor.sign_text, + (char_u *)opts->sign_text.data.string.data)) { + api_set_error(err, kErrorTypeValidation, "sign_text is not a valid value"); + goto error; + } + } else if (HAS_KEY(opts->sign_text)) { + api_set_error(err, kErrorTypeValidation, "sign_text is not a String"); + goto error; + } + bool right_gravity = true; OPTION_TO_BOOL(right_gravity, right_gravity, true); // Only error out if they try to set end_right_gravity without - // setting end_col or end_line + // setting end_col or end_row if (line2 == -1 && col2 == -1 && HAS_KEY(opts->end_right_gravity)) { api_set_error(err, kErrorTypeValidation, - "cannot set end_right_gravity without setting end_line or end_col"); + "cannot set end_right_gravity without setting end_row or end_col"); goto error; } @@ -586,16 +705,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; } @@ -611,27 +744,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) { @@ -642,18 +765,15 @@ 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; error: clear_virttext(&decor.virt_text); + xfree(decor.sign_text); return 0; } @@ -672,23 +792,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); } } @@ -743,7 +863,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 @@ -752,7 +872,7 @@ Integer nvim_buf_add_highlight(Buffer buffer, Integer ns_id, String hl_group, In int hl_id = 0; if (hl_group.size > 0) { - hl_id = syn_check_group(hl_group.data, (int)hl_group.size); + hl_id = syn_check_group(hl_group.data, hl_group.size); } else { return ns_id; } @@ -763,10 +883,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; } @@ -798,7 +921,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); } @@ -839,7 +962,7 @@ void nvim_buf_clear_namespace(Buffer buffer, Integer ns_id, Integer line_start, /// - on_win: called when starting to redraw a specific window. /// ["win", winid, bufnr, topline, botline_guess] /// - on_line: called for each buffer line being redrawn. (The -/// interation with fold lines is subject to change) +/// interaction with fold lines is subject to change) /// ["win", winid, bufnr, row] /// - on_end: called at the end of a redraw cycle /// ["end", tick] diff --git a/src/nvim/api/keysets.lua b/src/nvim/api/keysets.lua index 144c252687..8ad4dae928 100644 --- a/src/nvim/api/keysets.lua +++ b/src/nvim/api/keysets.lua @@ -5,6 +5,7 @@ return { set_extmark = { "id"; "end_line"; + "end_row"; "end_col"; "hl_group"; "virt_text"; @@ -20,6 +21,13 @@ return { "virt_lines"; "virt_lines_above"; "virt_lines_leftcol"; + "strict"; + "sign_text"; + "sign_hl_group"; + "number_hl_group"; + "line_hl_group"; + "cursorline_hl_group"; + "conceal"; }; keymap = { "noremap"; @@ -28,10 +36,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"; @@ -58,5 +81,78 @@ return { "highlights"; "use_tabline"; }; + option = { + "scope"; + }; + highlight = { + "bold"; + "standout"; + "strikethrough"; + "underline"; + "underlineline"; + "undercurl"; + "underdot"; + "underdash"; + "italic"; + "reverse"; + "nocombine"; + "default"; + "global"; + "cterm"; + "foreground"; "fg"; + "background"; "bg"; + "ctermfg"; + "ctermbg"; + "special"; "sp"; + "link"; + "fallback"; + "blend"; + "temp"; + }; + highlight_cterm = { + "bold"; + "standout"; + "strikethrough"; + "underline"; + "underlineline"; + "undercurl"; + "underdot"; + "underdash"; + "italic"; + "reverse"; + "nocombine"; + }; + -- Autocmds + clear_autocmds = { + "buffer"; + "event"; + "group"; + "pattern"; + }; + create_autocmd = { + "buffer"; + "callback"; + "command"; + "desc"; + "group"; + "nested"; + "once"; + "pattern"; + }; + exec_autocmds = { + "buffer"; + "group"; + "modeline"; + "pattern"; + }; + get_autocmds = { + "event"; + "group"; + "pattern"; + "buffer"; + }; + create_augroup = { + "clear"; + }; } diff --git a/src/nvim/api/private/converter.c b/src/nvim/api/private/converter.c index 36da6c13a9..a26383ec7d 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,14 +57,20 @@ 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) #define TYPVAL_ENCODE_CONV_FUNC_START(tv, fun) \ do { \ - TYPVAL_ENCODE_CONV_NIL(tv); \ + ufunc_T *fp = find_func(fun); \ + if (fp != NULL && fp->uf_cb == nlua_CFunction_func_call) { \ + LuaRef ref = api_new_luaref(((LuaCFunctionState *)fp->uf_cb_state)->lua_callable.func_ref); \ + kvi_push(edata->stack, LUAREF_OBJ(ref)); \ + } else { \ + TYPVAL_ENCODE_CONV_NIL(tv); \ + } \ goto typval_encode_stop_converting_one_item; \ } while (0) @@ -340,6 +349,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..ba2e560d63 100644 --- a/src/nvim/api/private/dispatch.c +++ b/src/nvim/api/private/dispatch.c @@ -6,22 +6,31 @@ #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" +#include "nvim/ui_client.h" static Map(String, MsgpackRpcRequestHandler) methods = MAP_INIT; @@ -30,6 +39,13 @@ static void msgpack_rpc_add_method_handler(String method, MsgpackRpcRequestHandl map_put(String, MsgpackRpcRequestHandler)(&methods, method, handler); } +void msgpack_rpc_add_redraw(void) +{ + msgpack_rpc_add_method_handler(STATIC_CSTR_AS_STRING("redraw"), + (MsgpackRpcRequestHandler) { .fn = ui_client_handle_redraw, + .fast = true }); +} + /// @param name API method name /// @param name_len name size (includes terminating NUL) MsgpackRpcRequestHandler msgpack_rpc_get_handler_for(const char *name, size_t name_len, diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index d470def277..5ba4700f62 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -22,6 +22,7 @@ #include "nvim/extmark.h" #include "nvim/fileio.h" #include "nvim/getchar.h" +#include "nvim/highlight_group.h" #include "nvim/lib/kvec.h" #include "nvim/lua/executor.h" #include "nvim/map.h" @@ -32,7 +33,6 @@ #include "nvim/msgpack_rpc/helpers.h" #include "nvim/option.h" #include "nvim/option_defs.h" -#include "nvim/syntax.h" #include "nvim/ui.h" #include "nvim/version.h" #include "nvim/vim.h" @@ -111,7 +111,7 @@ bool try_leave(const TryState *const tstate, Error *const err) /// try_enter()/try_leave() pair should be used instead. void try_start(void) { - ++trylevel; + trylevel++; } /// End try block, set the error message if any and return true if an error @@ -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); + }); } @@ -513,7 +508,7 @@ String cbuf_to_string(const char *buf, size_t size) String cstrn_to_string(const char *str, size_t maxsize) FUNC_ATTR_NONNULL_ALL { - return cbuf_to_string(str, strnlen(str, maxsize)); + return cbuf_to_string(str, STRNLEN(str, maxsize)); } /// Creates a String using the given C string. Unlike @@ -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,15 @@ 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) - { + 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 +1051,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 +1109,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 +1131,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 +1181,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; @@ -1220,7 +1293,7 @@ int object_to_hl_id(Object obj, const char *what, Error *err) { if (obj.type == kObjectTypeString) { String str = obj.data.string; - return str.size ? syn_check_group(str.data, (int)str.size) : 0; + return str.size ? syn_check_group(str.data, str.size) : 0; } else if (obj.type == kObjectTypeInteger) { return MAX((int)obj.data.integer, 0); } else { @@ -1254,7 +1327,7 @@ HlMessage parse_hl_msg(Array chunks, Error *err) String hl = chunk.items[1].data.string; if (hl.size > 0) { // TODO(bfredl): use object_to_hl_id and allow integer - int hl_id = syn_check_group(hl.data, (int)hl.size); + int hl_id = syn_check_group(hl.data, hl.size); attr = hl_id > 0 ? syn_id2attr(hl_id) : 0; } } @@ -1342,3 +1415,267 @@ const char *get_default_stl_hl(win_T *wp) return "StatusLineNC"; } } + +void create_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; +} + +// adapted from sign.c:sign_define_init_text. +// TODO(lewis6991): Consider merging +int init_sign_text(char_u **sign_text, char_u *text) +{ + char_u *s; + + char_u *endp = text + (int)STRLEN(text); + + // Count cells and check for non-printable chars + int cells = 0; + for (s = text; s < endp; s += utfc_ptr2len(s)) { + if (!vim_isprintc(utf_ptr2char(s))) { + break; + } + cells += utf_ptr2cells(s); + } + // Currently must be empty, one or two display cells + if (s != endp || cells > 2) { + return FAIL; + } + if (cells < 1) { + return OK; + } + + // Allocate one byte more if we need to pad up + // with a space. + size_t len = (size_t)(endp - text + ((cells == 1) ? 1 : 0)); + *sign_text = vim_strnsave(text, len); + + if (cells == 1) { + STRCPY(*sign_text + len - 1, " "); + } + + return OK; +} diff --git a/src/nvim/api/private/helpers.h b/src/nvim/api/private/helpers.h index 6d0aec9c90..650349cde7 100644 --- a/src/nvim/api/private/helpers.h +++ b/src/nvim/api/private/helpers.h @@ -138,10 +138,30 @@ 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_item ## _index) = 0; (__foreach_item ## _index) < (a).size; \ + (__foreach_item ## _index)++) { \ + Object __foreach_item = (a).items[__foreach_item ## _index]; \ + 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/tabpage.c b/src/nvim/api/tabpage.c index 14b6be8eeb..b994d18c43 100644 --- a/src/nvim/api/tabpage.c +++ b/src/nvim/api/tabpage.c @@ -102,11 +102,10 @@ void nvim_tabpage_del_var(Tabpage tabpage, String name, Error *err) Window nvim_tabpage_get_win(Tabpage tabpage, Error *err) FUNC_API_SINCE(1) { - Window rv = 0; tabpage_T *tab = find_tab_by_handle(tabpage, err); if (!tab || !valid_tabpage(tab)) { - return rv; + return 0; } if (tab == curtab) { @@ -130,11 +129,10 @@ Window nvim_tabpage_get_win(Tabpage tabpage, Error *err) Integer nvim_tabpage_get_number(Tabpage tabpage, Error *err) FUNC_API_SINCE(1) { - Integer rv = 0; tabpage_T *tab = find_tab_by_handle(tabpage, err); if (!tab) { - return rv; + return 0; } return tabpage_index(tab); diff --git a/src/nvim/api/ui_events.in.h b/src/nvim/api/ui_events.in.h index 03fe5c5058..db348359eb 100644 --- a/src/nvim/api/ui_events.in.h +++ b/src/nvim/api/ui_events.in.h @@ -78,13 +78,13 @@ void hl_attr_define(Integer id, HlAttrs rgb_attrs, HlAttrs cterm_attrs, void hl_group_set(String name, Integer id) FUNC_API_SINCE(6) FUNC_API_BRIDGE_IMPL; void grid_resize(Integer grid, Integer width, Integer height) - FUNC_API_SINCE(5) FUNC_API_REMOTE_IMPL FUNC_API_COMPOSITOR_IMPL; + FUNC_API_SINCE(5) FUNC_API_REMOTE_IMPL FUNC_API_COMPOSITOR_IMPL FUNC_API_CLIENT_IMPL; void grid_clear(Integer grid) FUNC_API_SINCE(5) FUNC_API_REMOTE_IMPL; void grid_cursor_goto(Integer grid, Integer row, Integer col) FUNC_API_SINCE(5) FUNC_API_REMOTE_IMPL FUNC_API_COMPOSITOR_IMPL; void grid_line(Integer grid, Integer row, Integer col_start, Array data) - FUNC_API_SINCE(5) FUNC_API_REMOTE_ONLY; + FUNC_API_SINCE(5) FUNC_API_REMOTE_ONLY FUNC_API_CLIENT_IMPL; void grid_scroll(Integer grid, Integer top, Integer bot, Integer left, Integer right, Integer rows, Integer cols) FUNC_API_SINCE(5) FUNC_API_REMOTE_IMPL FUNC_API_COMPOSITOR_IMPL; diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 3103905819..626f7dc3eb 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -19,8 +19,10 @@ #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/decoration_provider.h" #include "nvim/edit.h" #include "nvim/eval.h" #include "nvim/eval/typval.h" @@ -30,7 +32,10 @@ #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/highlight_group.h" #include "nvim/lua/executor.h" #include "nvim/mark.h" #include "nvim/memline.h" @@ -46,7 +51,6 @@ #include "nvim/popupmnu.h" #include "nvim/screen.h" #include "nvim/state.h" -#include "nvim/syntax.h" #include "nvim/types.h" #include "nvim/ui.h" #include "nvim/vim.h" @@ -108,7 +112,7 @@ Dictionary nvim_get_hl_by_id(Integer hl_id, Boolean rgb, Error *err) Integer nvim_get_hl_id_by_name(String name) FUNC_API_SINCE(7) { - return syn_check_group(name.data, (int)name.size); + return syn_check_group(name.data, name.size); } Dictionary nvim__get_hl_defs(Integer ns_id, Error *err) @@ -119,33 +123,36 @@ Dictionary nvim__get_hl_defs(Integer ns_id, Error *err) abort(); } -/// Set a highlight group. -/// -/// @param ns_id number of namespace for this highlight -/// @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: -/// `default`: don't override existing definition, -/// like `hi default` -/// `ctermfg`: sets foreground of cterm color -/// `ctermbg`: sets background of cterm color -/// `cterm` : cterm attribute map. sets attributed for -/// cterm colors. similer to `hi cterm` -/// Note: by default cterm attributes are -/// same as attributes of gui color +/// Sets a highlight group. +/// +/// Note: Unlike the `:highlight` command which can update a highlight group, +/// this function completely replaces the definition. For example: +/// ``nvim_set_hl(0, 'Visual', {})`` will clear the highlight group 'Visual'. +/// +/// @param ns_id Namespace id for this highlight |nvim_create_namespace()|. +/// Use 0 to set a highlight group globally |:highlight|. +/// @param name Highlight group name, e.g. "ErrorMsg" +/// @param val Highlight definition map, like |synIDattr()|. In +/// addition, the following keys are recognized: +/// - default: Don't override existing definition |:hi-default| +/// - ctermfg: Sets foreground of cterm color |highlight-ctermfg| +/// - ctermbg: Sets background of cterm color |highlight-ctermbg| +/// - cterm: cterm attribute map, like +/// |highlight-args|. +/// Note: Attributes default to those set for `gui` +/// if not set. /// @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); + int hl_id = syn_check_group(name.data, name.size); int link_id = -1; 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 +176,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 +194,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; @@ -210,7 +219,7 @@ void nvim_feedkeys(String keys, String mode, Boolean escape_csi) bool execute = false; bool dangerous = false; - for (size_t i = 0; i < mode.size; ++i) { + for (size_t i = 0; i < mode.size; i++) { switch (mode.data[i]) { case 'n': remap = false; break; @@ -232,10 +241,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 +254,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 +392,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 +500,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 +553,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 +608,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. @@ -642,7 +666,7 @@ void nvim_set_vvar(String name, Object value, Error *err) dict_set_var(&vimvardict, name, value, false, false, err); } -/// Gets an option value string. +/// Gets the global value of an option. /// /// @param name Option name /// @param[out] err Error details, if any @@ -653,6 +677,125 @@ Object nvim_get_option(String name, Error *err) return get_option_from(NULL, SREQ_GLOBAL, name, err); } +/// Gets the value of an option. The behavior of this function matches that of +/// |:set|: the local value of an option is returned if it exists; otherwise, +/// the global value is returned. Local values always correspond to the current +/// buffer or window. To get a buffer-local or window-local option for a +/// specific buffer or window, use |nvim_buf_get_option()| or +/// |nvim_win_get_option()|. +/// +/// @param name Option name +/// @param opts Optional parameters +/// - scope: One of 'global' or 'local'. Analogous to +/// |:setglobal| and |:setlocal|, respectively. +/// @param[out] err Error details, if any +/// @return Option value +Object nvim_get_option_value(String name, Dict(option) *opts, Error *err) + FUNC_API_SINCE(9) +{ + Object rv = OBJECT_INIT; + + int scope = 0; + if (opts->scope.type == kObjectTypeString) { + if (!strcmp(opts->scope.data.string.data, "local")) { + scope = OPT_LOCAL; + } else if (!strcmp(opts->scope.data.string.data, "global")) { + scope = OPT_GLOBAL; + } else { + api_set_error(err, kErrorTypeValidation, "invalid scope: must be 'local' or 'global'"); + goto end; + } + } else if (HAS_KEY(opts->scope)) { + api_set_error(err, kErrorTypeValidation, "invalid value for key: scope"); + goto end; + } + + long numval = 0; + char *stringval = NULL; + switch (get_option_value(name.data, &numval, (char_u **)&stringval, scope)) { + case 0: + rv = STRING_OBJ(cstr_as_string(stringval)); + break; + case 1: + rv = INTEGER_OBJ(numval); + break; + case 2: + switch (numval) { + case 0: + case 1: + rv = BOOLEAN_OBJ(numval); + break; + default: + // Boolean options that return something other than 0 or 1 should return nil. Currently this + // only applies to 'autoread' which uses -1 as a local value to indicate "unset" + rv = NIL; + break; + } + break; + default: + api_set_error(err, kErrorTypeValidation, "unknown option '%s'", name.data); + goto end; + } + +end: + return rv; +} + +/// Sets the value of an option. The behavior of this function matches that of +/// |:set|: for global-local options, both the global and local value are set +/// unless otherwise specified with {scope}. +/// +/// @param name Option name +/// @param value New option value +/// @param opts Optional parameters +/// - 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) + FUNC_API_SINCE(9) +{ + int scope = 0; + if (opts->scope.type == kObjectTypeString) { + if (!strcmp(opts->scope.data.string.data, "local")) { + scope = OPT_LOCAL; + } else if (!strcmp(opts->scope.data.string.data, "global")) { + scope = OPT_GLOBAL; + } else { + api_set_error(err, kErrorTypeValidation, "invalid scope: must be 'local' or 'global'"); + return; + } + } else if (HAS_KEY(opts->scope)) { + api_set_error(err, kErrorTypeValidation, "invalid value for key: scope"); + return; + } + + long numval = 0; + char *stringval = NULL; + + switch (value.type) { + case kObjectTypeInteger: + numval = value.data.integer; + break; + case kObjectTypeBoolean: + numval = value.data.boolean ? 1 : 0; + break; + case kObjectTypeString: + stringval = value.data.string.data; + break; + case kObjectTypeNil: + scope |= OPT_CLEAR; + break; + default: + api_set_error(err, kErrorTypeValidation, "invalid value for option"); + return; + } + + char *e = set_option_value(name.data, numval, stringval, scope); + if (e) { + api_set_error(err, kErrorTypeException, "%s", e); + } +} + /// Gets the option information for all options. /// /// The dictionary has the full option names as keys and option metadata @@ -694,7 +837,7 @@ Dictionary nvim_get_option_info(String name, Error *err) return get_vimoption(name, err); } -/// Sets an option value. +/// Sets the global value of an option. /// /// @param channel_id /// @param name Option name @@ -733,7 +876,7 @@ void nvim_echo(Array chunks, Boolean history, Dictionary opts, Error *err) for (uint32_t i = 0; i < kv_size(hl_msg); i++) { HlMessageChunk chunk = kv_A(hl_msg, i); msg_multiline_attr((const char *)chunk.text.data, chunk.attr, - false, &need_clear); + true, &need_clear); } if (history) { msg_ext_set_kind("echomsg"); @@ -995,6 +1138,7 @@ Integer nvim_open_term(Buffer buffer, DictionaryOf(LuaRef) opts, Error *err) TerminalOptions topts; Channel *chan = channel_alloc(kChannelStreamInternal); chan->stream.internal.cb = cb; + chan->stream.internal.closed = false; topts.data = chan; // NB: overridden in terminal_check_size if a window is already // displaying the buffer @@ -1044,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 @@ -1184,7 +1328,7 @@ Boolean nvim_paste(String data, Boolean crlf, Integer phase, Error *err) if (!cancel && !(State & CMDLINE)) { // Dot-repeat. for (size_t i = 0; i < lines.size; i++) { String s = lines.items[i].data.string; - assert(data.size <= INT_MAX); + assert(s.size <= INT_MAX); AppendToRedobuffLit((char_u *)s.data, (int)s.size); // readfile()-style: "\n" is indicated by presence of N+1 item. if (i + 1 < lines.size) { @@ -1405,10 +1549,11 @@ Dictionary nvim_get_mode(void) FUNC_API_SINCE(2) FUNC_API_FAST { Dictionary rv = ARRAY_DICT_INIT; - char *modestr = get_mode(); + char modestr[MODE_MAX_LENGTH]; + get_mode(modestr); bool blocked = input_blocking(); - PUT(rv, "mode", STRING_OBJ(cstr_as_string(modestr))); + PUT(rv, "mode", STRING_OBJ(cstr_to_string(modestr))); PUT(rv, "blocking", BOOLEAN_OBJ(blocked)); return rv; @@ -1419,10 +1564,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. @@ -1442,18 +1587,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. @@ -1461,10 +1611,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. @@ -1622,7 +1772,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 @@ -1731,15 +1881,18 @@ static void write_msg(String message, bool to_err) } \ line_buf[pos++] = message.data[i]; - ++no_wait_return; + no_wait_return++; for (uint32_t i = 0; i < message.size; i++) { + if (got_int) { + break; + } if (to_err) { PUSH_CHAR(i, err_pos, err_line_buf, emsg); } else { PUSH_CHAR(i, out_pos, out_line_buf, msg); } } - --no_wait_return; + no_wait_return--; msg_end(); } @@ -1805,7 +1958,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; } @@ -1839,14 +1992,13 @@ Array nvim_get_proc_children(Integer pid, Error *err) size_t proc_count; int rv = os_proc_children((int)pid, &proc_list, &proc_count); - if (rv != 0) { + if (rv == 2) { // syscall failed (possibly because of kernel options), try shelling out. DLOG("fallback to vim._os_proc_children()"); Array a = ARRAY_DICT_INIT; ADD(a, INTEGER_OBJ(pid)); - String s = cstr_to_string("return vim._os_proc_children(select(1, ...))"); + String s = STATIC_CSTR_AS_STRING("return vim._os_proc_children(...)"); Object o = nlua_exec(s, a, err); - api_free_string(s); api_free_array(a); if (o.type == kObjectTypeArray) { rvobj = o.data.array; @@ -1979,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 @@ -2089,7 +2241,7 @@ Array nvim_get_mark(String name, Dictionary opts, Error *err) /// - winid: (number) |window-ID| of the window to use as context for statusline. /// - maxwidth: (number) Maximum width of statusline. /// - fillchar: (string) Character to fill blank spaces in the statusline (see -/// 'fillchars'). +/// 'fillchars'). Treated as single-width even if it isn't. /// - highlights: (boolean) Return highlight information. /// - use_tabline: (boolean) Evaluate tabline instead of statusline. When |TRUE|, {winid} /// is ignored. @@ -2109,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; @@ -2124,12 +2276,13 @@ 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 + || ((size_t)utf_ptr2len((char_u *)opts->fillchar.data.string.data) + != opts->fillchar.data.string.size)) { + api_set_error(err, kErrorTypeValidation, "fillchar must be a single character"); return result; } - - fillchar = opts->fillchar.data.string.data[0]; + fillchar = utf_ptr2char((char_u *)opts->fillchar.data.string.data); } if (HAS_KEY(opts->highlights)) { @@ -2156,11 +2309,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); } } @@ -2172,7 +2330,7 @@ Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Error * maxwidth = (int)opts->maxwidth.data.integer; } else { - maxwidth = use_tabline ? Columns : wp->w_width; + maxwidth = (use_tabline || global_stl_height() > 0) ? Columns : wp->w_width; } char buf[MAXPATHL]; @@ -2188,7 +2346,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); @@ -2241,3 +2399,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_create_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_create_user_command(String name, Object command, Dict(user_command) *opts, Error *err) + FUNC_API_SINCE(9) +{ + create_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/win_config.c b/src/nvim/api/win_config.c index ceb7f71423..5e37596884 100644 --- a/src/nvim/api/win_config.c +++ b/src/nvim/api/win_config.c @@ -10,6 +10,7 @@ #include "nvim/api/private/helpers.h" #include "nvim/api/win_config.h" #include "nvim/ascii.h" +#include "nvim/highlight_group.h" #include "nvim/option.h" #include "nvim/screen.h" #include "nvim/strings.h" @@ -125,13 +126,13 @@ /// [ "╔", "═" ,"╗", "║", "╝", "═", "╚", "║" ]. /// If the number of chars are less than eight, they will be repeated. Thus /// an ASCII border could be specified as -/// [ "/", "-", "\\", "|" ], +/// [ "/", "-", \"\\\\\", "|" ], /// or all chars the same as /// [ "x" ]. /// An empty string can be used to turn off a specific border, for instance, /// [ "", "", "", ">", "", "", "", "<" ] /// will only make vertical borders but not horizontal ones. -/// By default, `FloatBorder` highlight is used, which links to `VertSplit` +/// By default, `FloatBorder` highlight is used, which links to `WinSeparator` /// when not defined. It could also be specified by character: /// [ {"+", "MyCorner"}, {"x", "MyBorder"} ]. /// - noautocmd: If true then no buffer-related autocommand events such as @@ -149,7 +150,7 @@ Window nvim_open_win(Buffer buffer, Boolean enter, Dict(float_config) *config, E if (!parse_float_config(config, &fconfig, false, true, err)) { return 0; } - win_T *wp = win_new_float(NULL, fconfig, err); + win_T *wp = win_new_float(NULL, false, fconfig, err); if (!wp) { return 0; } @@ -199,7 +200,7 @@ void nvim_win_set_config(Window window, Dict(float_config) *config, Error *err) return; } if (new_float) { - if (!win_new_float(win, fconfig, err)) { + if (!win_new_float(win, false, fconfig, err)) { return; } redraw_later(win, NOT_VALID); diff --git a/src/nvim/api/window.c b/src/nvim/api/window.c index 6e68c057dc..fd33a82be3 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| +/// This scrolls the window even if it is not the current one. /// /// @param window Window handle, or 0 for current window /// @param pos (row, col) tuple representing the new position @@ -118,6 +119,7 @@ void nvim_win_set_cursor(Window window, ArrayOf(Integer, 2) pos, Error *err) update_topline_win(win); redraw_later(win, VALID); + win->w_redr_status = true; } /// Gets the window height @@ -395,7 +397,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 +457,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/assert.h b/src/nvim/assert.h index ad92d9a2af..65519a8004 100644 --- a/src/nvim/assert.h +++ b/src/nvim/assert.h @@ -1,3 +1,5 @@ +// uncrustify:off + #ifndef NVIM_ASSERT_H #define NVIM_ASSERT_H diff --git a/src/nvim/aucmd.c b/src/nvim/aucmd.c deleted file mode 100644 index af519dcba9..0000000000 --- a/src/nvim/aucmd.c +++ /dev/null @@ -1,122 +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; - - dict_T *dict = get_vim_var_dict(VV_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); - tv_dict_clear(dict); - - 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 6be51c504c..9e3eb06752 100644 --- a/src/nvim/auevents.lua +++ b/src/nvim/auevents.lua @@ -37,8 +37,10 @@ return { 'CursorHoldI', -- idem, in Insert mode 'CursorMoved', -- cursor was moved 'CursorMovedI', -- cursor was moved in Insert mode + '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 @@ -69,11 +71,15 @@ return { 'InsertLeave', -- just after leaving Insert mode 'InsertLeavePre', -- just before leaving Insert mode 'MenuPopup', -- just before popup menu is displayed + 'ModeChanged', -- after changing the mode 'OptionSet', -- after setting any option 'QuickFixCmdPost', -- after :make, :grep etc. 'QuickFixCmdPre', -- before :make, :grep etc. 'QuitPre', -- before :quit + 'RecordingEnter', -- when starting to record a macro + 'RecordingLeave', -- just before a macro stops recording 'RemoteReply', -- upon string reception from a remote vim + 'SearchWrapped', -- after the search wrapped around 'SessionLoadPost', -- after loading a session file 'ShellCmdPost', -- after ":!cmd" 'ShellFilterPost', -- after ":1,2!cmd", ":w !cmd", ":r !cmd". @@ -126,16 +132,14 @@ return { -- syntax file nvim_specific = { BufModifiedSet=true, - DirChanged=true, + DiagnosticChanged=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 2d0c0f3fd5..1b146f82c6 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,10 +15,13 @@ #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/misc1.h" +#include "nvim/lua/executor.h" +#include "nvim/map.h" #include "nvim/option.h" +#include "nvim/os/input.h" #include "nvim/regexp.h" #include "nvim/search.h" #include "nvim/state.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,31 @@ 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 and ids. +// name -> ID +// ID -> name +static Map(String, int) map_augroup_name_to_id = MAP_INIT; +static Map(int, String) map_augroup_id_to_name = MAP_INIT; + +static void augroup_map_del(int id, char *name) +{ + if (name != NULL) { + String key = map_key(String, int)(&map_augroup_name_to_id, cstr_as_string(name)); + map_del(String, int)(&map_augroup_name_to_id, key); + api_free_string(key); + } + if (id > 0) { + String mapped = map_get(int, String)(&map_augroup_id_to_name, id); + api_free_string(mapped); + map_del(int, String)(&map_augroup_id_to_name, id); + } +} + + static inline const char *get_deleted_augroup(void) FUNC_ATTR_ALWAYS_INLINE { if (deleted_augroup == NULL) { @@ -98,48 +135,53 @@ 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, event_T event, int previous_group) { - AutoCmd *ac; - // Check for "got_int" (here and at various places below), which is set // when "q" has been hit for the "--more--" prompt if (got_int) { return; } + // pattern has been removed if (ap->pat == NULL) { return; } + char *name = augroup_name(ap->group); + msg_putchar('\n'); if (got_int) { return; } - if (event != last_event || ap->group != last_group) { + // 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 (AUGROUP_NAME(ap->group) == NULL) { + if (name == 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_attr(name, HL_ATTR(HLF_T)); } msg_puts(" "); } + // show the event name 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 + for (AutoCmd *ac = ap->cmds; ac != NULL; ac = ac->next) { + // skip removed commands + if (aucmd_exec_is_deleted(ac->exec)) { continue; } + if (msg_col >= 14) { msg_putchar('\n'); } @@ -147,7 +189,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 +205,111 @@ static void show_autocmd(AutoPat *ap, event_T event) } } +static void au_show_for_all_events(int group, char_u *pat) +{ + FOR_ALL_AUEVENTS(event) { + au_show_for_event(group, event, pat); + } +} + +static void au_show_for_event(int group, event_T event, char_u *pat) +{ + // Return early if there are no autocmds for this event + if (au_event_is_empty(event)) { + return; + } + + // always need to show group information before the first pattern for the event + int previous_group = AUGROUP_ERROR; + + if (*pat == NUL) { + FOR_ALL_AUPATS_IN_EVENT(event, ap) { + if (group == AUGROUP_ALL || ap->group == group) { + aupat_show(ap, event, previous_group); + previous_group = ap->group; + } + } + return; + } + + char_u buflocal_pat[BUFLOCAL_PAT_LEN]; // for "<buffer=X>" + // Loop through all the specified patterns. + int patlen = (int)aucmd_pattern_length(pat); + while (patlen) { + // detect special <buffer[=X]> buffer-local patterns + if (aupat_is_buflocal(pat, patlen)) { + // normalize pat into standard "<buffer>#N" form + aupat_normalize_buflocal_pat(buflocal_pat, pat, patlen, aupat_get_buflocal_nr(pat, patlen)); + pat = buflocal_pat; + patlen = (int)STRLEN(buflocal_pat); + } + + assert(*pat != NUL); + + // Find AutoPat entries with this pattern. + // always goes at or after the last one, so start at the end. + FOR_ALL_AUPATS_IN_EVENT(event, ap) { + 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 ((group == AUGROUP_ALL || ap->group == group) + && ap->patlen == patlen + && STRNCMP(pat, ap->pat, patlen) == 0) { + // Show autocmd's for this autopat, or buflocals <buffer=X> + aupat_show(ap, event, previous_group); + previous_group = ap->group; + } + } + } + + pat = aucmd_next_pattern(pat, (size_t)patlen); + patlen = (int)aucmd_pattern_length(pat); + } +} + // 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_cleanup(); +} + // 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,19 +317,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; - 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) { + AutoPat **prev_ap = &(first_autopat[(int)event]); + for (AutoPat *ap = *prev_ap; ap != NULL; ap = *prev_ap) { bool has_cmd = false; // Loop over all commands for this pattern. @@ -211,9 +333,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 +350,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,28 +376,30 @@ 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 - for (apc = active_apc_list; apc; apc = apc->next) { + for (AutoPatCmd *apc = active_apc_list; apc; apc = apc->next) { if (buf->b_fnum == apc->arg_bufnr) { apc->arg_bufnr = 0; } } // 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 +412,74 @@ 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; + } + + if (existing_id == AUGROUP_DELETED) { + augroup_map_del(existing_id, name); } - return i; + int next_id = next_augroup_id++; + String name_key = cstr_to_string(name); + String name_val = cstr_to_string(name); + map_put(String, int)(&map_augroup_name_to_id, name_key, next_id); + map_put(int, String)(&map_augroup_id_to_name, next_id, name_val); + + return next_id; } -static void au_del_group(char_u *name) +/// Delete the augroup that matches name. +/// @param stupid_legacy_mode bool: This parameter 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)(&map_augroup_name_to_id, cstr_as_string(name), AUGROUP_DELETED); + augroup_map_del(ap->group, NULL); + 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(i, name); + au_cleanup(); } } @@ -346,25 +488,67 @@ 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)(&map_augroup_name_to_id, 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 = map_get(int, String)(&map_augroup_id_to_name, group); + if (key.data != NULL) { + 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 +558,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(&map_augroup_name_to_id, name, value, { + if (value > 0) { + msg_puts(name.data); + } else { + msg_puts(augroup_name(value)); } - } + + msg_puts(" "); + }); + msg_clr_eos(); msg_end(); } @@ -399,35 +587,45 @@ 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(); + + // Delete the augroup_map, including free the data + String name; + int id; + map_foreach(&map_augroup_name_to_id, name, id, { + (void)id; + api_free_string(name); + }) + map_destroy(String, int)(&map_augroup_name_to_id); + + map_foreach(&map_augroup_id_to_name, id, name, { + (void)id; + api_free_string(name); + }) + map_destroy(int, String)(&map_augroup_id_to_name); } #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; - int len; // the event name ends with end of line, '|', a blank or a comma for (p = start; *p && !ascii_iswhite(*p) && *p != ',' && *p != '|'; p++) { } for (i = 0; event_names[i].name != NULL; i++) { - len = (int)event_names[i].len; + int len = (int)event_names[i].len; if (len == p - start && STRNICMP(event_names[i].name, start, len) == 0) { break; } @@ -447,12 +645,10 @@ 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; - - for (i = 0; event_names[i].name != NULL; i++) { + for (int i = 0; event_names[i].name != NULL; i++) { if (event_names[i].event == event) { return event_names[i].name; } @@ -460,33 +656,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'. /// @@ -532,11 +701,8 @@ int check_ei(void) // Returns the old value of 'eventignore' in allocated memory. char_u *au_event_disable(char *what) { - char_u *new_ei; - char_u *save_ei; - - save_ei = vim_strsave(p_ei); - new_ei = vim_strnsave(p_ei, STRLEN(p_ei) + STRLEN(what)); + char_u *save_ei = vim_strsave(p_ei); + char_u *new_ei = vim_strnsave(p_ei, STRLEN(p_ei) + STRLEN(what)); if (*what == ',' && *p_ei == NUL) { STRCPY(new_ei, what + 1); } else { @@ -591,11 +757,10 @@ void au_event_restore(char_u *old_ei) void do_autocmd(char_u *arg_in, int forceit) { char_u *arg = arg_in; - char_u *pat; char_u *envpat = NULL; char_u *cmd; int need_free = false; - int nested = false; + bool nested = false; bool once = false; int group; @@ -604,12 +769,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); + char_u *pat = arg_event_skip(arg, group != AUGROUP_ALL); if (pat == NULL) { return; } @@ -646,37 +811,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 +838,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, pat); } else { - for (event_T event = (event_T)0; event < NUM_EVENTS; - event = (event_T)(event + 1)) { + event_T event = event_name2nr(arg, &arg); + assert(event < NUM_EVENTS); + au_show_for_event(group, event, pat); + } + } 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 { + 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 +877,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,219 +894,310 @@ 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; - 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); - } - } - } + int patlen = (int)aucmd_pattern_length(pat); + while (patlen) { + // detect special <buffer[=X]> buffer-local patterns + int is_buflocal = aupat_is_buflocal(pat, patlen); 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. 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(); // may really delete removed patterns/commands now + 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; - } - } - 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; +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); + + AutoPat *ap; + AutoPat **prev_ap; + AutoCmd *ac; + int findgroup; + char_u buflocal_pat[BUFLOCAL_PAT_LEN]; // for "<buffer=X>" + + if (patlen > (int)STRLEN(pat)) { + return FAIL; + } + + if (group == AUGROUP_ALL) { + findgroup = current_augroup; + } else { + findgroup = group; + } + + + // detect special <buffer[=X]> buffer-local patterns + int is_buflocal = aupat_is_buflocal(pat, patlen); + int 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); + } + + // always goes at or after the last one, so start at the end. + 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)) { + get_mode(last_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; + } + + // Initialize the fields checked by the WinScrolled trigger to + // stop it from firing right after the first autocmd is defined. + if (event == EVENT_WINSCROLLED && !has_event(EVENT_WINSCROLLED)) { + curwin->w_last_topline = curwin->w_topline; + curwin->w_last_leftcol = curwin->w_leftcol; + curwin->w_last_width = curwin->w_width; + curwin->w_last_height = curwin->w_height; + } + + 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. + AutoCmd **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; /// /// @param do_msg give message for no matching autocmds? int do_doautocmd(char_u *arg, bool do_msg, bool *did_something) { - char_u *fname; int nothing_done = true; - int group; if (did_something != NULL) { *did_something = false; } // Check for a legal group name. If not, use AUGROUP_ALL. - group = au_get_grouparg(&arg); + int group = arg_augroup_get(&arg); if (*arg == '*') { emsg(_("E217: Can't execute autocommands for ALL events")); @@ -980,7 +1206,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); + char_u *fname = arg_event_skip(arg, group != AUGROUP_ALL); if (fname == NULL) { return FAIL; } @@ -1056,8 +1282,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 @@ -1147,7 +1371,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; @@ -1155,6 +1382,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. @@ -1206,10 +1437,13 @@ win_found: // Hmm, original window disappeared. Just use the first one. curwin = firstwin; } + curbuf = curwin->w_buffer; + // May need to restore insert mode for a prompt buffer. + entering_window(curwin); + prevwin = win_find_by_handle(aco->save_prevwin_handle); vars_clear(&aucmd_win->w_vars->dv_hashtab); // free all w: variables hash_init(&aucmd_win->w_vars->dv_hashtab); // re-use the hashtab - curbuf = curwin->w_buffer; xfree(globaldir); globaldir = aco->globaldir; @@ -1248,6 +1482,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". @@ -1330,11 +1570,9 @@ bool has_cursorhold(void) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT /// Return true if the CursorHold/CursorHoldI event can be triggered. bool trigger_cursorhold(void) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { - int state; - if (!did_cursorhold && has_cursorhold() && reg_recording == 0 && typebuf.tb_len == 0 && !ins_compl_active()) { - state = get_real_state(); + int state = get_real_state(); if (state == NORMAL_BUSY || (state & INSERT) != 0) { return true; } @@ -1354,23 +1592,12 @@ 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; - bool save_changed; - buf_T *old_curbuf; bool retval = false; - char_u *save_sourcing_name; - linenr_T save_sourcing_lnum; - char_u *save_autocmd_fname; - int save_autocmd_bufnr; - char_u *save_autocmd_match; - int save_autocmd_busy; - int save_autocmd_nested; static int nesting = 0; - AutoPatCmd patcmd; AutoPat *ap; char_u *save_cmdarg; long save_cmdbang; @@ -1378,7 +1605,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. @@ -1427,20 +1654,20 @@ static bool apply_autocmds_group(event_T event, char_u *fname, char_u *fname_io, } // Save the autocmd_* variables and info about the current buffer. - save_autocmd_fname = autocmd_fname; - save_autocmd_bufnr = autocmd_bufnr; - save_autocmd_match = autocmd_match; - save_autocmd_busy = autocmd_busy; - save_autocmd_nested = autocmd_nested; - save_changed = curbuf->b_changed; - old_curbuf = curbuf; + char_u *save_autocmd_fname = autocmd_fname; + int save_autocmd_bufnr = autocmd_bufnr; + char_u *save_autocmd_match = autocmd_match; + int save_autocmd_busy = autocmd_busy; + int save_autocmd_nested = autocmd_nested; + bool save_changed = curbuf->b_changed; + buf_T *old_curbuf = curbuf; // Set the file name to be used for <afile>. // Make a copy to avoid that changing a buffer name or directory makes it // invalid. if (fname_io == NULL) { if (event == EVENT_COLORSCHEME || event == EVENT_COLORSCHEMEPRE - || event == EVENT_OPTIONSET) { + || event == EVENT_OPTIONSET || event == EVENT_MODECHANGED) { autocmd_fname = NULL; } else if (fname != NULL && !ends_excmd(*fname)) { autocmd_fname = fname; @@ -1493,12 +1720,14 @@ 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_OPTIONSET + || 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_WINCLOSED) { + || event == EVENT_TABCLOSED || event == EVENT_USER + || event == EVENT_WINCLOSED || event == EVENT_WINSCROLLED) { fname = vim_strsave(fname); } else { fname = (char_u *)FullName_save((char *)fname, false); @@ -1524,9 +1753,9 @@ static bool apply_autocmds_group(event_T event, char_u *fname, char_u *fname_io, // Don't redraw while doing autocommands. RedrawingDisabled++; - save_sourcing_name = sourcing_name; + char_u *save_sourcing_name = sourcing_name; sourcing_name = NULL; // don't free this one - save_sourcing_lnum = sourcing_lnum; + linenr_T save_sourcing_lnum = sourcing_lnum; sourcing_lnum = 0; // no line number here const sctx_T save_current_sctx = current_sctx; @@ -1559,9 +1788,10 @@ static bool apply_autocmds_group(event_T event, char_u *fname, char_u *fname_io, did_filetype = true; } - tail = path_tail(fname); + char_u *tail = path_tail(fname); // Find first autocommand that matches + AutoPatCmd patcmd; patcmd.curpat = first_autopat[(int)event]; patcmd.nextcmd = NULL; patcmd.group = group; @@ -1783,14 +2013,62 @@ void auto_next_pat(AutoPatCmd *apc, int stop_at_last) } } +static bool call_autocmd_callback(const AutoCmd *ac, const AutoPatCmd *apc) +{ + bool ret = false; + Callback callback = ac->exec.callable.cb; + if (callback.type == kCallbackLua) { + Dictionary data = ARRAY_DICT_INIT; + PUT(data, "id", INTEGER_OBJ(ac->id)); + PUT(data, "event", CSTR_TO_OBJ(event_nr2name(apc->event))); + PUT(data, "match", CSTR_TO_OBJ((char *)autocmd_match)); + PUT(data, "file", CSTR_TO_OBJ((char *)autocmd_fname)); + PUT(data, "buf", INTEGER_OBJ(autocmd_bufnr)); + + int group = apc->curpat->group; + switch (group) { + case AUGROUP_ERROR: + abort(); // unreachable + case AUGROUP_DEFAULT: + case AUGROUP_ALL: + case AUGROUP_DELETED: + // omit group in these cases + break; + default: + PUT(data, "group", INTEGER_OBJ(group)); + break; + } + + FIXED_TEMP_ARRAY(args, 1); + args.items[0] = DICTIONARY_OBJ(data); + + Object result = nlua_call_ref(callback.data.luaref, NULL, args, true, NULL); + if (result.type == kObjectTypeBoolean) { + ret = result.data.boolean; + } + api_free_dictionary(data); + api_free_object(result); + } else { + typval_T argsin = TV_INITIAL_VALUE; + typval_T rettv = TV_INITIAL_VALUE; + callback_call(&callback, 0, &argsin, &rettv); + } + + return ret; +} + /// Get next autocommand command. /// Called by do_cmdline() to get the next line for ":if". /// @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; // Can be called again after returning the last line. if (acp->curpat == NULL) { @@ -1800,7 +2078,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 { @@ -1826,21 +2105,46 @@ char_u *getnextac(int c, void *cookie, int indent, bool do_concat) } } - ac = acp->nextcmd; + AutoCmd *ac = acp->nextcmd; + bool oneshot = ac->once; 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); - // Remove one-shot ("once") autocmd in anticipation of its execution. - if (ac->once) { - au_del_cmd(ac); - } + + // Make sure to set autocmd_nested before executing + // lua code, so that it works properly autocmd_nested = ac->nested; current_sctx = ac->script_ctx; + + if (ac->exec.type == CALLABLE_CB) { + if (call_autocmd_callback(ac, acp)) { + // If an autocommand callback returns true, delete the autocommand + oneshot = true; + } + + // 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 (oneshot) { + aucmd_del(ac); + } if (ac->last) { acp->nextcmd = NULL; } else { @@ -1859,12 +2163,10 @@ char_u *getnextac(int c, void *cookie, int indent, bool do_concat) /// @param buf buffer the file is open in bool has_autocmd(event_T event, char_u *sfname, buf_T *buf) FUNC_ATTR_WARN_UNUSED_RESULT { - AutoPat *ap; - char_u *fname; char_u *tail = path_tail(sfname); bool retval = false; - fname = (char_u *)FullName_save((char *)sfname, false); + char_u *fname = (char_u *)FullName_save((char *)sfname, false); if (fname == NULL) { return false; } @@ -1877,7 +2179,7 @@ bool has_autocmd(event_T event, char_u *sfname, buf_T *buf) FUNC_ATTR_WARN_UNUSE forward_slash(fname); #endif - for (ap = first_autopat[(int)event]; ap != NULL; ap = ap->next) { + for (AutoPat *ap = first_autopat[(int)event]; ap != NULL; ap = ap->next) { if (ap->pat != NULL && ap->cmds != NULL && (ap->buflocal_nr == 0 ? match_file_pat(NULL, @@ -1902,31 +2204,21 @@ 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 char_u *set_context_in_autocmd(expand_T *xp, char_u *arg, int doautocmd) { - char_u *p; - int group; - // check for a group name, skip it if present autocmd_include_groups = false; - p = arg; - group = au_get_grouparg(&arg); + char_u *p = arg; + int 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])) { @@ -1967,16 +2259,24 @@ 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 @@ -2005,10 +2305,7 @@ bool autocmd_supported(const char *const event) /// @param arg autocommand string bool au_exists(const char *const arg) FUNC_ATTR_WARN_UNUSED_RESULT { - event_T event; - AutoPat *ap; buf_T *buflocal_buf = NULL; - int group; bool retval = false; // Make a copy so that we can change the '#' chars to a NUL. @@ -2019,7 +2316,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); + int 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. @@ -2043,7 +2340,7 @@ bool au_exists(const char *const arg) FUNC_ATTR_WARN_UNUSED_RESULT char *pattern = p; // "pattern" is NULL when there is no pattern. // Find the index (enum) for the event name. - event = event_name2nr((char_u *)event_name, (char_u **)&p); + event_T event = event_name2nr((char_u *)event_name, (char_u **)&p); // return false if the event name is not recognized if (event == NUM_EVENTS) { @@ -2053,7 +2350,7 @@ bool au_exists(const char *const arg) FUNC_ATTR_WARN_UNUSED_RESULT // Find the first autocommand for this event. // If there isn't any, return false; // If there is one and no pattern given, return true; - ap = first_autopat[(int)event]; + AutoPat *ap = first_autopat[(int)event]; if (ap == NULL) { goto theend; } @@ -2083,3 +2380,366 @@ 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 +bool autocmd_delete_id(int64_t id) +{ + assert(id > 0); + bool success = false; + + // Note that since multiple AutoCmd objects can have the same ID, we need to do a full scan. + 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); + success = true; + } + } + } + } + return success; +} + +// =========================================================================== +// 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; + } +} + +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 acc.callable.cb.type == kCallbackNone; + 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 *p; + char_u *arg = *argp; + int group = AUGROUP_ALL; + + for (p = arg; *p && !ascii_iswhite(*p) && *p != '|'; p++) { + } + if (p > arg) { + char_u *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 1512d6bcd5..4948e2bb69 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -34,6 +34,7 @@ #include "nvim/channel.h" #include "nvim/charset.h" #include "nvim/cursor.h" +#include "nvim/decoration.h" #include "nvim/diff.h" #include "nvim/digraph.h" #include "nvim/eval.h" @@ -50,6 +51,7 @@ #include "nvim/getchar.h" #include "nvim/hashtab.h" #include "nvim/highlight.h" +#include "nvim/highlight_group.h" #include "nvim/indent.h" #include "nvim/indent_c.h" #include "nvim/main.h" @@ -57,7 +59,6 @@ #include "nvim/mbyte.h" #include "nvim/memory.h" #include "nvim/message.h" -#include "nvim/misc1.h" #include "nvim/move.h" #include "nvim/option.h" #include "nvim/os/input.h" @@ -173,7 +174,7 @@ int open_buffer(int read_stdin, exarg_T *eap, int flags) if (ml_open(curbuf) == FAIL) { // There MUST be a memfile, otherwise we can't do anything // If we can't create one for the current buffer, take another buffer - close_buffer(NULL, curbuf, 0, false); + close_buffer(NULL, curbuf, 0, false, false); curbuf = NULL; FOR_ALL_BUFFERS(buf) { @@ -223,7 +224,7 @@ int open_buffer(int read_stdin, exarg_T *eap, int flags) || (S_ISCHR(perm) && is_dev_fd_file(curbuf->b_ffname)) # endif - )) { + )) { // NOLINT(whitespace/parens) read_fifo = true; } if (read_fifo) { @@ -401,8 +402,10 @@ bool buf_valid(buf_T *buf) /// there to be only one window with this buffer. e.g. when /// ":quit" is supposed to close the window but autocommands /// close all other windows. -/// @returns true when we got to the end and b_nwindows was decremented. -bool close_buffer(win_T *win, buf_T *buf, int action, bool abort_if_last) +/// @param ignore_abort +/// If true, don't abort even when aborting() returns true. +/// @return true when we got to the end and b_nwindows was decremented. +bool close_buffer(win_T *win, buf_T *buf, int action, bool abort_if_last, bool ignore_abort) { bool unload_buf = (action != 0); bool del_buf = (action == DOBUF_DEL || action == DOBUF_WIPE); @@ -493,7 +496,8 @@ bool close_buffer(win_T *win, buf_T *buf, int action, bool abort_if_last) return false; } } - if (aborting()) { // autocmds may abort script processing + // autocmds may abort script processing + if (!ignore_abort && aborting()) { return false; } } @@ -551,14 +555,16 @@ bool close_buffer(win_T *win, buf_T *buf, int action, bool abort_if_last) buf->b_nwindows = nwindows; - buf_freeall(buf, (del_buf ? BFA_DEL : 0) + (wipe_buf ? BFA_WIPE : 0)); + buf_freeall(buf, ((del_buf ? BFA_DEL : 0) + + (wipe_buf ? BFA_WIPE : 0) + + (ignore_abort ? BFA_IGNORE_ABORT : 0))); if (!bufref_valid(&bufref)) { // Autocommands may have deleted the buffer. return false; } - if (aborting()) { - // Autocmds may abort script processing. + // autocmds may abort script processing. + if (!ignore_abort && aborting()) { return false; } @@ -587,10 +593,12 @@ bool close_buffer(win_T *win, buf_T *buf, int action, bool abort_if_last) // No need to check `unload_buf`: in that case the function returned above. buf_updates_unload(buf, false); - /* - * Remove the buffer from the list. - */ + // Remove the buffer from the list. if (wipe_buf) { + // Do not wipe out the buffer if it is used in a window. + if (buf->b_nwindows > 0) { + return false; + } if (buf->b_sfname != buf->b_ffname) { XFREE_CLEAR(buf->b_sfname); } else { @@ -657,9 +665,10 @@ void buf_clear(void) /// buf_freeall() - free all things allocated for a buffer that are related to /// the file. Careful: get here with "curwin" NULL when exiting. /// -/// @param flags BFA_DEL buffer is going to be deleted -/// BFA_WIPE buffer is going to be wiped out -/// BFA_KEEP_UNDO do not free undo information +/// @param flags BFA_DEL buffer is going to be deleted +/// BFA_WIPE buffer is going to be wiped out +/// BFA_KEEP_UNDO do not free undo information +/// BFA_IGNORE_ABORT don't abort even when aborting() returns true void buf_freeall(buf_T *buf, int flags) { bool is_curbuf = (buf == curbuf); @@ -703,7 +712,8 @@ void buf_freeall(buf_T *buf, int flags) goto_tabpage_win(the_curtab, the_curwin); unblock_autocmds(); } - if (aborting()) { // autocmds may abort script processing + // autocmds may abort script processing + if ((flags & BFA_IGNORE_ABORT) == 0 && aborting()) { return; } @@ -737,10 +747,8 @@ void buf_freeall(buf_T *buf, int flags) buf->b_flags &= ~BF_READERR; // a read error is no longer relevant } -/* - * Free a buffer structure and the things it contains related to the buffer - * itself (not the file, that must have been done already). - */ +/// Free a buffer structure and the things it contains related to the buffer +/// itself (not the file, that must have been done already). static void free_buffer(buf_T *buf) { pmap_del(handle_T)(&buffer_handles, buf->b_fnum); @@ -813,9 +821,7 @@ static void free_buffer_stuff(buf_T *buf, int free_flags) buf_updates_unload(buf, false); } -/* - * Free the b_wininfo list for buffer "buf". - */ +/// Free the b_wininfo list for buffer "buf". static void clear_wininfo(buf_T *buf) { wininfo_T *wip; @@ -827,9 +833,7 @@ static void clear_wininfo(buf_T *buf) } } -/* - * Go to another buffer. Handles the result of the ATTENTION dialog. - */ +/// Go to another buffer. Handles the result of the ATTENTION dialog. void goto_buffer(exarg_T *eap, int start, int dir, int count) { bufref_T old_curbuf; @@ -847,7 +851,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; @@ -880,7 +884,7 @@ void handle_swap_exists(bufref_T *old_curbuf) // open a new, empty buffer. swap_exists_action = SEA_NONE; // don't want it again swap_exists_did_quit = true; - close_buffer(curwin, curbuf, DOBUF_UNLOAD, false); + close_buffer(curwin, curbuf, DOBUF_UNLOAD, false, false); if (old_curbuf == NULL || !bufref_valid(old_curbuf) || old_curbuf->br_buf == curbuf) { @@ -1033,10 +1037,8 @@ char *do_bufdel(int command, char_u *arg, int addr_count, int start_bnr, int end } -/* - * Make the current buffer empty. - * Used when it is wiped out and it's the last buffer. - */ +/// Make the current buffer empty. +/// Used when it is wiped out and it's the last buffer. static int empty_curbuf(int close_others, int forceit, int action) { int retval; @@ -1051,8 +1053,24 @@ static int empty_curbuf(int close_others, int forceit, int action) set_bufref(&bufref, buf); if (close_others) { - // Close any other windows on this buffer, then make it empty. - close_windows(buf, true); + bool can_close_all_others = true; + if (curwin->w_floating) { + // Closing all other windows with this buffer may leave only floating windows. + can_close_all_others = false; + for (win_T *wp = firstwin; !wp->w_floating; wp = wp->w_next) { + if (wp->w_buffer != curbuf) { + // Found another non-floating window with a different (probably unlisted) buffer. + // Closing all other windows with this buffer is fine in this case. + can_close_all_others = true; + break; + } + } + } + // If it is fine to close all other windows with this buffer, keep the current window and + // close any other windows with this buffer, then make it empty. + // Otherwise close_windows() will refuse to close the last non-floating window, so allow it + // to close the current window instead. + close_windows(buf, can_close_all_others); } setpcmark(); @@ -1063,7 +1081,7 @@ static int empty_curbuf(int close_others, int forceit, int action) // the old one. But do_ecmd() may have done that already, check // if the buffer still exists. if (buf != curbuf && bufref_valid(&bufref) && buf->b_nwindows == 0) { - close_buffer(NULL, buf, action, false); + close_buffer(NULL, buf, action, false, false); } if (!close_others) { @@ -1233,12 +1251,13 @@ int do_buffer(int action, int start, int dir, int count, int forceit) } // If the deleted buffer is the current one, close the current window - // (unless it's the only window). Repeat this so long as we end up in - // a window with this buffer. + // (unless it's the only non-floating window). + // When the autocommand window is involved win_close() may need to print an error message. + // Repeat this so long as we end up in a window with this buffer. 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) { + && (lastwin == aucmd_win || !last_window(curwin))) { + if (win_close(curwin, false, false) == FAIL) { break; } } @@ -1247,7 +1266,7 @@ int do_buffer(int action, int start, int dir, int count, int forceit) if (buf != curbuf) { close_windows(buf, false); if (buf != curbuf && bufref_valid(&bufref) && buf->b_nwindows <= 0) { - close_buffer(NULL, buf, action, false); + close_buffer(NULL, buf, action, false, false); } return OK; } @@ -1276,8 +1295,10 @@ int do_buffer(int action, int start, int dir, int count, int forceit) while (jumpidx != curwin->w_jumplistidx) { buf = buflist_findnr(curwin->w_jumplist[jumpidx].fmark.fnum); if (buf != NULL) { - if (buf == curbuf || !buf->b_p_bl) { - buf = NULL; // skip current and unlisted bufs + // Skip current and unlisted bufs. Also skip a quickfix + // buffer, it might be deleted soon. + if (buf == curbuf || !buf->b_p_bl || bt_quickfix(buf)) { + buf = NULL; } else if (buf->b_ml.ml_mfp == NULL) { // skip unloaded buf, but may keep it for later if (bp == NULL) { @@ -1315,7 +1336,7 @@ int do_buffer(int action, int start, int dir, int count, int forceit) continue; } // in non-help buffer, try to skip help buffers, and vv - if (buf->b_help == curbuf->b_help && buf->b_p_bl) { + if (buf->b_help == curbuf->b_help && buf->b_p_bl && !bt_quickfix(buf)) { if (buf->b_ml.ml_mfp != NULL) { // found loaded buffer break; } @@ -1335,7 +1356,7 @@ int do_buffer(int action, int start, int dir, int count, int forceit) } if (buf == NULL) { // No loaded buffer, find listed one FOR_ALL_BUFFERS(buf2) { - if (buf2->b_p_bl && buf2 != curbuf) { + if (buf2->b_p_bl && buf2 != curbuf && !bt_quickfix(buf2)) { buf = buf2; break; } @@ -1347,6 +1368,9 @@ int do_buffer(int action, int start, int dir, int count, int forceit) } else { buf = curbuf->b_prev; } + if (bt_quickfix(buf)) { + buf = NULL; + } } } @@ -1410,15 +1434,15 @@ int do_buffer(int action, int start, int dir, int count, int forceit) } -/* - * Set current buffer to "buf". Executes autocommands and closes current - * buffer. "action" tells how to close the current buffer: - * DOBUF_GOTO free or hide it - * DOBUF_SPLIT nothing - * DOBUF_UNLOAD unload it - * DOBUF_DEL delete it - * DOBUF_WIPE wipe it out - */ +/// Set current buffer to "buf". Executes autocommands and closes current +/// buffer. +/// +/// @param action tells how to close the current buffer: +/// DOBUF_GOTO free or hide it +/// DOBUF_SPLIT nothing +/// DOBUF_UNLOAD unload it +/// DOBUF_DEL delete it +/// DOBUF_WIPE wipe it out void set_curbuf(buf_T *buf, int action) { buf_T *prevbuf; @@ -1442,7 +1466,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) @@ -1455,7 +1479,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, @@ -1464,7 +1492,7 @@ void set_curbuf(buf_T *buf, int action) ? action : (action == DOBUF_GOTO && !buf_hide(prevbuf) && !bufIsChanged(prevbuf)) ? DOBUF_UNLOAD : 0, - false); + false, false); if (curwin != previouswin && win_valid(previouswin)) { // autocommands changed curwin, Grr! curwin = previouswin; @@ -1474,10 +1502,15 @@ void set_curbuf(buf_T *buf, int action) // An autocommand may have deleted "buf", already entered it (e.g., when // it did ":bunload") or aborted the script processing! // If curwin->w_buffer is null, enter_buffer() will make it valid again - if ((buf_valid(buf) && buf != curbuf - && !aborting() - ) || curwin->w_buffer == NULL) { - enter_buffer(buf); + bool valid = buf_valid(buf); + if ((valid && buf != curbuf && !aborting()) || curwin->w_buffer == NULL) { + // If the buffer is not valid but curwin->w_buffer is NULL we must + // enter some buffer. Using the last one is hopefully OK. + if (!valid) { + enter_buffer(lastbuf); + } else { + enter_buffer(buf); + } if (old_tw != curbuf->b_p_tw) { check_colorcolumn(curwin); } @@ -1488,13 +1521,16 @@ void set_curbuf(buf_T *buf, int action) } } -/* - * Enter a new current buffer. - * Old curbuf must have been abandoned already! This also means "curbuf" may - * be pointing to freed memory. - */ +/// Enter a new current buffer. +/// Old curbuf must have been abandoned already! This also means "curbuf" may +/// be pointing to freed memory. 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) { @@ -1505,11 +1541,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); } @@ -1579,15 +1610,15 @@ void enter_buffer(buf_T *buf) redraw_later(curwin, NOT_VALID); } -// Change to the directory of the current buffer. -// Don't do this while still starting up. +/// Change to the directory of the current buffer. +/// Don't do this while still starting up. void do_autochdir(void) { if (p_acd) { if (starting == 0 && curbuf->b_ffname != NULL && vim_chdirfile(curbuf->b_ffname, kCdCauseAuto) == OK) { - post_chdir(kCdScopeGlobal, false); + last_chdir_reason = "autochdir"; shorten_fnames(true); } } @@ -1657,7 +1688,7 @@ static inline void buf_init_changedtick(buf_T *const buf) /// @param flags BLN_ defines /// @param bufnr /// -/// @return pointer to the buffer +/// @return pointer to the buffer buf_T *buflist_new(char_u *ffname_arg, char_u *sfname_arg, linenr_T lnum, int flags) { char_u *ffname = ffname_arg; @@ -1699,14 +1730,12 @@ buf_T *buflist_new(char_u *ffname_arg, char_u *sfname_arg, linenr_T lnum, int fl return buf; } - /* - * If the current buffer has no name and no contents, use the current - * buffer. Otherwise: Need to allocate a new buffer structure. - * - * This is the ONLY place where a new buffer structure is allocated! - * (A spell file buffer is allocated in spell.c, but that's not a normal - * buffer.) - */ + // If the current buffer has no name and no contents, use the current + // buffer. Otherwise: Need to allocate a new buffer structure. + // + // This is the ONLY place where a new buffer structure is allocated! + // (A spell file buffer is allocated in spell.c, but that's not a normal + // buffer.) buf = NULL; if ((flags & BLN_CURBUF) && curbuf_reusable()) { assert(curbuf != NULL); @@ -1733,7 +1762,7 @@ buf_T *buflist_new(char_u *ffname_arg, char_u *sfname_arg, linenr_T lnum, int fl buf = xcalloc(1, sizeof(buf_T)); // init b: variables buf->b_vars = tv_dict_alloc(); - buf->b_signcols_valid = false; + buf->b_signcols.valid = false; init_var_dict(buf->b_vars, &buf->b_bufvar, VAR_SCOPE); buf_init_changedtick(buf); } @@ -1777,9 +1806,7 @@ buf_T *buflist_new(char_u *ffname_arg, char_u *sfname_arg, linenr_T lnum, int fl // need to reload lmaps and set b:keymap_name curbuf->b_kmap_state |= KEYMAP_INIT; } else { - /* - * put new buffer at the end of the buffer list - */ + // put new buffer at the end of the buffer list buf->b_next = NULL; if (firstbuf == NULL) { // buffer list is empty buf->b_prev = NULL; @@ -1871,11 +1898,9 @@ bool curbuf_reusable(void) && !curbufIsChanged()); } -/* - * Free the memory for the options of a buffer. - * If "free_p_ff" is true also free 'fileformat', 'buftype' and - * 'fileencoding'. - */ +/// Free the memory for the options of a buffer. +/// If "free_p_ff" is true also free 'fileformat', 'buftype' and +/// 'fileencoding'. void free_buf_options(buf_T *buf, int free_p_ff) { if (free_p_ff) { @@ -1897,10 +1922,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); @@ -1922,6 +1945,7 @@ void free_buf_options(buf_T *buf, int free_p_ff) clear_string_option(&buf->b_p_cink); clear_string_option(&buf->b_p_cino); clear_string_option(&buf->b_p_cinw); + clear_string_option(&buf->b_p_cinsd); clear_string_option(&buf->b_p_cpt); clear_string_option(&buf->b_p_cfu); clear_string_option(&buf->b_p_ofu); @@ -2037,7 +2061,7 @@ int buflist_getfile(int n, linenr_T lnum, int options, int forceit) return FAIL; } -// Go to the last known line number for the current buffer. +/// Go to the last known line number for the current buffer. void buflist_getfpos(void) { pos_T *fpos; @@ -2057,10 +2081,9 @@ void buflist_getfpos(void) } } -/* - * Find file in buffer list by name (it has to be for the current window). - * Returns NULL if not found. - */ +/// Find file in buffer list by name (it has to be for the current window). +/// +/// @return buffer or NULL if not found buf_T *buflist_findname_exp(char_u *fname) { char_u *ffname; @@ -2074,7 +2097,7 @@ buf_T *buflist_findname_exp(char_u *fname) #else false #endif - ); + ); // NOLINT(whitespace/parens) if (ffname != NULL) { buf = buflist_findname(ffname); xfree(ffname); @@ -2082,12 +2105,11 @@ buf_T *buflist_findname_exp(char_u *fname) return buf; } -/* - * Find file in buffer list by name (it has to be for the current window). - * "ffname" must have a full path. - * Skips dummy buffers. - * Returns NULL if not found. - */ +/// Find file in buffer list by name (it has to be for the current window). +/// "ffname" must have a full path. +/// Skips dummy buffers. +/// +/// @return buffer or NULL if not found buf_T *buflist_findname(char_u *ffname) { FileID file_id; @@ -2095,11 +2117,10 @@ buf_T *buflist_findname(char_u *ffname) return buflist_findname_file_id(ffname, &file_id, file_id_valid); } -/* - * Same as buflist_findname(), but pass the FileID structure to avoid - * getting it twice for the same file. - * Returns NULL if not found. - */ +/// Same as buflist_findname(), but pass the FileID structure to avoid +/// getting it twice for the same file. +/// +/// @return buffer or NULL if not found static buf_T *buflist_findname_file_id(char_u *ffname, FileID *file_id, bool file_id_valid) { // Start at the last buffer, expect to find a match sooner. @@ -2113,13 +2134,13 @@ static buf_T *buflist_findname_file_id(char_u *ffname, FileID *file_id, bool fil } /// Find file in buffer list by a regexp pattern. -/// Return fnum of the found buffer. -/// Return < 0 for error. /// /// @param pattern_end pointer to first char after pattern /// @param unlisted find unlisted buffers /// @param diffmode find diff-mode buffers only /// @param curtab_only find buffers in current tab only +/// +/// @return fnum of the found buffer or < 0 for error. int buflist_findpat(const char_u *pattern, const char_u *pattern_end, bool unlisted, bool diffmode, bool curtab_only) FUNC_ATTR_NONNULL_ARG(1) @@ -2143,7 +2164,6 @@ int buflist_findpat(const char_u *pattern, const char_u *pattern_end, bool unlis match = -1; } } else { - // // Try four ways of matching a listed buffer: // attempt == 0: without '^' or '$' (at any position) // attempt == 1: with '^' at start (only at position 0) @@ -2151,7 +2171,6 @@ int buflist_findpat(const char_u *pattern, const char_u *pattern_end, bool unlis // attempt == 3: with '^' at start and '$' at end (only full match) // Repeat this for finding an unlisted buffer if there was no matching // listed buffer. - // pat = file_pat_to_reg_pat(pattern, pattern_end, NULL, false); if (pat == NULL) { @@ -2249,11 +2268,10 @@ static int buf_time_compare(const void *s1, const void *s2) return buf1->b_last_used > buf2->b_last_used ? -1 : 1; } -/* - * Find all buffer names that match. - * For command line expansion of ":buf" and ":sbuf". - * Return OK if matches found, FAIL otherwise. - */ +/// Find all buffer names that match. +/// For command line expansion of ":buf" and ":sbuf". +/// +/// @return OK if matches found, FAIL otherwise. int ExpandBufnames(char_u *pat, int *num_file, char_u ***file, int options) { int count = 0; @@ -2377,7 +2395,7 @@ int ExpandBufnames(char_u *pat, int *num_file, char_u ***file, int options) /// Check for a match on the file name for buffer "buf" with regprog "prog". /// -/// @param ignore_case When true, ignore case. Use 'fic' otherwise. +/// @param ignore_case When true, ignore case. Use 'fic' otherwise. static char_u *buflist_match(regmatch_T *rmp, buf_T *buf, bool ignore_case) { // First try the short file name, then the long file name. @@ -2390,8 +2408,9 @@ static char_u *buflist_match(regmatch_T *rmp, buf_T *buf, bool ignore_case) /// Try matching the regexp in "prog" with file name "name". /// -/// @param ignore_case When true, ignore case. Use 'fileignorecase' otherwise. -/// @return "name" when there is a match, NULL when not. +/// @param ignore_case When true, ignore case. Use 'fileignorecase' otherwise. +/// +/// @return "name" when there is a match, NULL when not. static char_u *fname_match(regmatch_T *rmp, char_u *name, bool ignore_case) { char_u *match = NULL; @@ -2526,12 +2545,13 @@ static bool wininfo_other_tab_diff(wininfo_T *wip) return false; } -// Find info for the current window in buffer "buf". -// If not found, return the info for the most recently used window. -// When "need_options" is true skip entries where wi_optset is false. -// When "skip_diff_buffer" is true avoid windows with 'diff' set that is in -// another tab page. -// Returns NULL when there isn't any info. +/// Find info for the current window in buffer "buf". +/// If not found, return the info for the most recently used window. +/// +/// @param need_options when true, skip entries where wi_optset is false. +/// @param skip_diff_buffer when true, avoid windows with 'diff' set that is in another tab page. +/// +/// @return NULL when there isn't any info. static wininfo_T *find_wininfo(buf_T *buf, bool need_options, bool skip_diff_buffer) FUNC_ATTR_NONNULL_ALL { @@ -2568,12 +2588,10 @@ static wininfo_T *find_wininfo(buf_T *buf, bool need_options, bool skip_diff_buf return wip; } -/* - * Reset the local window options to the values last used in this window. - * If the buffer wasn't used in this window before, use the values from - * the most recently used window. If the values were never set, use the - * global values for the window. - */ +/// Reset the local window options to the values last used in this window. +/// If the buffer wasn't used in this window before, use the values from +/// the most recently used window. If the values were never set, use the +/// global values for the window. void get_winopts(buf_T *buf) { clear_winopt(&curwin->w_onebuf_opt); @@ -2608,11 +2626,10 @@ void get_winopts(buf_T *buf) didset_window_options(curwin); } -/* - * Find the position (lnum and col) for the buffer 'buf' for the current - * window. - * Returns a pointer to no_position if no position is found. - */ +/// Find the position (lnum and col) for the buffer 'buf' for the current +/// window. +/// +/// @return a pointer to no_position if no position is found. pos_T *buflist_findfpos(buf_T *buf) { static pos_T no_position = { 1, 0, 0 }; @@ -2621,15 +2638,13 @@ pos_T *buflist_findfpos(buf_T *buf) return (wip == NULL) ? &no_position : &(wip->wi_fpos); } -/* - * Find the lnum for the buffer 'buf' for the current window. - */ +/// Find the lnum for the buffer 'buf' for the current window. linenr_T buflist_findlnum(buf_T *buf) { return buflist_findfpos(buf)->lnum; } -// List all known file names (for :files and :buffers command). +/// List all known file names (for :files and :buffers command). void buflist_list(exarg_T *eap) { buf_T *buf = firstbuf; @@ -2657,8 +2672,7 @@ void buflist_list(exarg_T *eap) for (; buf != NULL && !got_int; buf = buflist_data != NULL - ? (++p < buflist_data + buflist.ga_len ? *p : NULL) - : buf->b_next) { + ? (++p < buflist_data + buflist.ga_len ? *p : NULL) : buf->b_next) { const bool is_terminal = buf->terminal; const bool job_running = buf->terminal && terminal_running(buf->terminal); @@ -2719,12 +2733,10 @@ void buflist_list(exarg_T *eap) IObuff[len++] = ' '; } while (--i > 0 && len < IOSIZE - 18); if (vim_strchr(eap->arg, 't') && buf->b_last_used) { - add_time(IObuff + len, (size_t)(IOSIZE - len), buf->b_last_used); + undo_fmt_time(IObuff + len, (size_t)(IOSIZE - len), buf->b_last_used); } else { - vim_snprintf((char *)IObuff + len, (size_t)(IOSIZE - len), - _("line %" PRId64), - buf == curbuf ? (int64_t)curwin->w_cursor.lnum - : (int64_t)buflist_findlnum(buf)); + vim_snprintf((char *)IObuff + len, (size_t)(IOSIZE - len), _("line %" PRId64), + buf == curbuf ? (int64_t)curwin->w_cursor.lnum : (int64_t)buflist_findlnum(buf)); } msg_outtrans(IObuff); @@ -2736,12 +2748,11 @@ void buflist_list(exarg_T *eap) } } -/* - * Get file name and line number for file 'fnum'. - * Used by DoOneCmd() for translating '%' and '#'. - * Used by insert_reg() and cmdline_paste() for '#' register. - * Return FAIL if not found, OK for success. - */ +/// Get file name and line number for file 'fnum'. +/// Used by DoOneCmd() for translating '%' and '#'. +/// Used by insert_reg() and cmdline_paste() for '#' register. +/// +/// @return FAIL if not found, OK for success. int buflist_name_nr(int fnum, char_u **fname, linenr_T *lnum) { buf_T *buf; @@ -2802,7 +2813,7 @@ int setfname(buf_T *buf, char_u *ffname_arg, char_u *sfname_arg, bool message) return FAIL; } // delete from the list - close_buffer(NULL, obuf, DOBUF_WIPE, false); + close_buffer(NULL, obuf, DOBUF_WIPE, false, false); } sfname = vim_strsave(sfname); #ifdef USE_FNAME_CASE @@ -2827,10 +2838,8 @@ int setfname(buf_T *buf, char_u *ffname_arg, char_u *sfname_arg, bool message) return OK; } -/* - * Crude way of changing the name of a buffer. Use with care! - * The name should be relative to the current directory. - */ +/// Crude way of changing the name of a buffer. Use with care! +/// The name should be relative to the current directory. void buf_set_name(int fnum, char_u *name) { buf_T *buf; @@ -2850,15 +2859,10 @@ void buf_set_name(int fnum, char_u *name) } } -/* - * Take care of what needs to be done when the name of buffer "buf" has - * changed. - */ +/// Take care of what needs to be done when the name of buffer "buf" has changed. void buf_name_changed(buf_T *buf) { - /* - * If the file name changed, also change the name of the swapfile - */ + // If the file name changed, also change the name of the swapfile if (buf->b_ml.ml_mfp != NULL) { ml_setname(buf); } @@ -2872,12 +2876,11 @@ void buf_name_changed(buf_T *buf) ml_timestamp(buf); // reset timestamp } -/* - * set alternate file name for current window - * - * Used by do_one_cmd(), do_write() and do_ecmd(). - * Return the buffer. - */ +/// Set alternate file name for current window +/// +/// Used by do_one_cmd(), do_write() and do_ecmd(). +/// +/// @return the buffer. buf_T *setaltfname(char_u *ffname, char_u *sfname, linenr_T lnum) { buf_T *buf; @@ -2908,12 +2911,10 @@ char_u *getaltfname(bool errmsg) return fname; } -/* - * Add a file name to the buflist and return its number. - * Uses same flags as buflist_new(), except BLN_DUMMY. - * - * used by qf_init(), main() and doarglist() - */ +/// Add a file name to the buflist and return its number. +/// Uses same flags as buflist_new(), except BLN_DUMMY. +/// +/// Used by qf_init(), main() and doarglist() int buflist_add(char_u *fname, int flags) { buf_T *buf; @@ -2926,9 +2927,7 @@ int buflist_add(char_u *fname, int flags) } #if defined(BACKSLASH_IN_FILENAME) -/* - * Adjust slashes in file names. Called after 'shellslash' was set. - */ +/// Adjust slashes in file names. Called after 'shellslash' was set. void buflist_slash_adjust(void) { FOR_ALL_BUFFERS(bp) { @@ -2943,10 +2942,8 @@ void buflist_slash_adjust(void) #endif -/* - * Set alternate cursor position for the current buffer and window "win". - * Also save the local window option values. - */ +/// Set alternate cursor position for the current buffer and window "win". +/// Also save the local window option values. void buflist_altfpos(win_T *win) { buflist_setfpos(curbuf, win, win->w_cursor.lnum, win->w_cursor.col, true); @@ -3009,8 +3006,8 @@ static bool otherfile_buf(buf_T *buf, char_u *ffname, FileID *file_id_p, bool fi return true; } -// Set file_id for a buffer. -// Must always be called when b_fname is changed! +/// Set file_id for a buffer. +/// Must always be called when b_fname is changed! void buf_set_file_id(buf_T *buf) { FileID file_id; @@ -3150,7 +3147,7 @@ static char_u *lasttitle = NULL; static char_u *lasticon = NULL; -// Put the title name in the title bar and icon of the window. +/// Put the title name in the title bar and icon of the window. void maketitle(void) { char_u *title_str = NULL; @@ -3327,7 +3324,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); @@ -3348,8 +3345,8 @@ void maketitle(void) /// /// @param str desired title string /// @param[in,out] last current title string -// -/// @return true if resettitle() is to be called. +/// +/// @return true if resettitle() is to be called. static bool value_change(char_u *str, char_u **last) FUNC_ATTR_WARN_UNUSED_RESULT { @@ -3406,20 +3403,20 @@ typedef enum { /// If maxwidth is not zero, the string will be filled at any middle marker /// or truncated if too long, fillchar is used for all whitespace. /// -/// @param wp The window to build a statusline for -/// @param out The output buffer to write the statusline to -/// Note: This should not be NameBuff -/// @param outlen The length of the output buffer -/// @param fmt The statusline format string -/// @param use_sandbox Use a sandboxed environment when evaluating fmt -/// @param fillchar Character to use when filling empty space in the statusline -/// @param maxwidth The maximum width to make the statusline -/// @param hltab HL attributes (can be NULL) -/// @param tabtab Tab clicks definition (can be NULL). +/// @param wp The window to build a statusline for +/// @param out The output buffer to write the statusline to +/// Note: This should not be NameBuff +/// @param outlen The length of the output buffer +/// @param fmt The statusline format string +/// @param use_sandbox Use a sandboxed environment when evaluating fmt +/// @param fillchar Character to use when filling empty space in the statusline +/// @param maxwidth The maximum width to make the statusline +/// @param hltab HL attributes (can be NULL) +/// @param tabtab Tab clicks definition (can be NULL). /// -/// @return The final width of the statusline +/// @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; @@ -3438,8 +3435,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); } @@ -3462,9 +3463,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 @@ -3517,8 +3515,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); @@ -3662,7 +3660,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); } // } @@ -3678,21 +3676,20 @@ int build_stl_str_hl(win_T *wp, char_u *out, size_t outlen, char_u *fmt, int use } } // If the group is shorter than the minimum width, add padding characters. - } else if ( - abs(stl_items[stl_groupitems[groupdepth]].minwid) > group_len) { + } else if (abs(stl_items[stl_groupitems[groupdepth]].minwid) > group_len) { long min_group_width = stl_items[stl_groupitems[groupdepth]].minwid; // If the group is left-aligned, add characters to the right. if (min_group_width < 0) { min_group_width = 0 - min_group_width; 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); } @@ -3706,7 +3703,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); } } } @@ -4002,14 +3999,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 @@ -4238,7 +4228,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; @@ -4249,20 +4239,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. @@ -4352,7 +4343,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)) { @@ -4455,7 +4446,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; } // } @@ -4506,13 +4497,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; @@ -4577,7 +4568,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; @@ -4585,12 +4576,10 @@ int build_stl_str_hl(win_T *wp, char_u *out, size_t outlen, char_u *fmt, int use } return width; -} +} // NOLINT(readability/fn_size) -/* - * Get relative cursor position in window into "buf[buflen]", in the form 99%, - * using "Top", "Bot" or "All" when appropriate. - */ +/// Get relative cursor position in window into "buf[buflen]", in the form 99%, +/// using "Top", "Bot" or "All" when appropriate. void get_rel_pos(win_T *wp, char_u *buf, int buflen) { // Need at least 3 chars for writing. @@ -4627,7 +4616,7 @@ void get_rel_pos(win_T *wp, char_u *buf, int buflen) /// @param buflen length of the string buffer /// @param add_file if true, add "file" before the arg number /// -/// @return true if it was appended. +/// @return true if it was appended. static bool append_arg_number(win_T *wp, char_u *buf, int buflen, bool add_file) FUNC_ATTR_NONNULL_ALL { @@ -4656,12 +4645,12 @@ static bool append_arg_number(win_T *wp, char_u *buf, int buflen, bool add_file) return true; } -// Make "*ffname" a full file name, set "*sfname" to "*ffname" if not NULL. -// "*ffname" becomes a pointer to allocated memory (or NULL). -// When resolving a link both "*sfname" and "*ffname" will point to the same -// allocated memory. -// The "*ffname" and "*sfname" pointer values on call will not be freed. -// Note that the resulting "*ffname" pointer should be considered not allocated. +/// Make "*ffname" a full file name, set "*sfname" to "*ffname" if not NULL. +/// "*ffname" becomes a pointer to allocated memory (or NULL). +/// When resolving a link both "*sfname" and "*ffname" will point to the same +/// allocated memory. +/// The "*ffname" and "*sfname" pointer values on call will not be freed. +/// Note that the resulting "*ffname" pointer should be considered not allocated. void fname_expand(buf_T *buf, char_u **ffname, char_u **sfname) { if (*ffname == NULL) { // no file name given, nothing to do @@ -4685,9 +4674,7 @@ void fname_expand(buf_T *buf, char_u **ffname, char_u **sfname) #endif } -/* - * Get the file name for an argument list entry. - */ +/// Get the file name for an argument list entry. char_u *alist_name(aentry_T *aep) { buf_T *bp; @@ -4729,8 +4716,7 @@ void do_arg_all(int count, int forceit, int keep_tabs) assert(firstwin != NULL); // satisfy coverity if (ARGCOUNT <= 0) { - /* Don't give an error message. We don't want it when the ":all" - * command is in the .vimrc. */ + // Don't give an error message. We don't want it when the ":all" command is in the .vimrc. return; } setpcmark(); @@ -4738,9 +4724,9 @@ void do_arg_all(int count, int forceit, int keep_tabs) opened_len = ARGCOUNT; opened = xcalloc((size_t)opened_len, 1); - /* Autocommands may do anything to the argument list. Make sure it's not - * freed while we are working here by "locking" it. We still have to - * watch out for its size to be changed. */ + // Autocommands may do anything to the argument list. Make sure it's not + // freed while we are working here by "locking" it. We still have to + // watch out for its size to be changed. alist = curwin->w_alist; alist->al_refcount++; @@ -4748,13 +4734,11 @@ void do_arg_all(int count, int forceit, int keep_tabs) old_curtab = curtab; - /* - * Try closing all windows that are not in the argument list. - * Also close windows that are not full width; - * When 'hidden' or "forceit" set the buffer becomes hidden. - * Windows that have a changed buffer and can't be hidden won't be closed. - * When the ":tab" modifier was used do this for all tab pages. - */ + // Try closing all windows that are not in the argument list. + // Also close windows that are not full width; + // When 'hidden' or "forceit" set the buffer becomes hidden. + // Windows that have a changed buffer and can't be hidden won't be closed. + // When the ":tab" modifier was used do this for all tab pages. if (had_tab > 0) { goto_tabpage_tp(first_tabpage, true, true); } @@ -4799,8 +4783,7 @@ void do_arg_all(int count, int forceit, int keep_tabs) } if (wp->w_alist != alist) { - /* Use the current argument list for all windows - * containing a file from it. */ + // Use the current argument list for all windows containing a file from it. alist_unlink(wp->w_alist); wp->w_alist = alist; wp->w_alist->al_refcount++; @@ -4814,8 +4797,7 @@ void do_arg_all(int count, int forceit, int keep_tabs) if (i == opened_len && !keep_tabs) { // close this window if (buf_hide(buf) || forceit || buf->b_nwindows > 1 || !bufIsChanged(buf)) { - /* If the buffer was changed, and we would like to hide it, - * try autowriting. */ + // If the buffer was changed, and we would like to hide it, try autowriting. if (!buf_hide(buf) && buf->b_nwindows <= 1 && bufIsChanged(buf)) { bufref_T bufref; set_bufref(&bufref, buf); @@ -4831,7 +4813,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... @@ -4854,10 +4836,8 @@ void do_arg_all(int count, int forceit, int keep_tabs) goto_tabpage_tp(tpnext, true, true); } - /* - * Open a window for files in the argument list that don't have one. - * ARGCOUNT may change while doing this, because of autocommands. - */ + // Open a window for files in the argument list that don't have one. + // ARGCOUNT may change while doing this, because of autocommands. if (count > opened_len || count <= 0) { count = opened_len; } @@ -4912,9 +4892,7 @@ void do_arg_all(int count, int forceit, int keep_tabs) autocmd_no_leave--; } - /* - * edit file "i" - */ + // edit file "i" curwin->w_arg_idx = i; if (i == 0) { new_curwin = curwin; @@ -4966,15 +4944,13 @@ void do_arg_all(int count, int forceit, int keep_tabs) xfree(opened); } -/// @return true if "buf" is a prompt buffer. +/// @return true if "buf" is a prompt buffer. bool bt_prompt(buf_T *buf) { return buf != NULL && buf->b_p_bt[0] == 'p'; } -/* - * Open a window for a number of buffers. - */ +/// Open a window for a number of buffers. void ex_buffer_all(exarg_T *eap) { buf_T *buf; @@ -5002,10 +4978,8 @@ void ex_buffer_all(exarg_T *eap) setpcmark(); - /* - * Close superfluous windows (two windows for the same buffer). - * Also close windows that are not full-width. - */ + // Close superfluous windows (two windows for the same buffer). + // Also close windows that are not full-width. if (had_tab > 0) { goto_tabpage_tp(first_tabpage, true, true); } @@ -5015,14 +4989,14 @@ void ex_buffer_all(exarg_T *eap) wpnext = wp->w_next; if ((wp->w_buffer->b_nwindows > 1 || ((cmdmod.split & WSP_VERT) - ? wp->w_height + wp->w_status_height < Rows - p_ch - - tabline_height() + ? wp->w_height + wp->w_hsep_height + wp->w_status_height < Rows - p_ch + - tabline_height() - global_stl_height() : wp->w_width != Columns) || (had_tab > 0 && wp != firstwin)) && !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... @@ -5039,7 +5013,6 @@ void ex_buffer_all(exarg_T *eap) goto_tabpage_tp(tpnext, true, true); } - // // Go through the buffer list. When a buffer doesn't have a window yet, // open one. Otherwise move the window to the right position. // Watch out for autocommands that delete buffers or windows! @@ -5103,7 +5076,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; @@ -5135,9 +5108,7 @@ void ex_buffer_all(exarg_T *eap) win_enter(firstwin, false); // back to first window autocmd_no_leave--; - /* - * Close superfluous windows. - */ + // Close superfluous windows. for (wp = lastwin; open_wins > count;) { r = (buf_hide(wp->w_buffer) || !bufIsChanged(wp->w_buffer) || autowrite(wp->w_buffer, false) == OK); @@ -5145,7 +5116,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 { @@ -5158,15 +5129,13 @@ void ex_buffer_all(exarg_T *eap) } -/* - * do_modelines() - process mode lines for the current file - * - * "flags" can be: - * OPT_WINONLY only set options local to window - * OPT_NOWIN don't set options local to window - * - * Returns immediately if the "ml" option isn't set. - */ +/// do_modelines() - process mode lines for the current file +/// +/// @param flags +/// OPT_WINONLY only set options local to window +/// OPT_NOWIN don't set options local to window +/// +/// Returns immediately if the "ml" option isn't set. void do_modelines(int flags) { linenr_T lnum; @@ -5272,10 +5241,8 @@ static int chk_modeline(linenr_T lnum, int flags) break; } - /* - * Find end of set command: ':' or end of line. - * Skip over "\:", replacing it with ":". - */ + // Find end of set command: ':' or end of line. + // Skip over "\:", replacing it with ":". for (e = s; *e != ':' && *e != NUL; e++) { if (e[0] == '\\' && e[1] == ':') { STRMOVE(e, e + 1); @@ -5285,13 +5252,11 @@ static int chk_modeline(linenr_T lnum, int flags) end = true; } - /* - * If there is a "set" command, require a terminating ':' and - * ignore the stuff after the ':'. - * "vi:set opt opt opt: foo" -- foo not interpreted - * "vi:opt opt opt: foo" -- foo interpreted - * Accept "se" for compatibility with Elvis. - */ + // If there is a "set" command, require a terminating ':' and + // ignore the stuff after the ':'. + // "vi:set opt opt opt: foo" -- foo not interpreted + // "vi:opt opt opt: foo" -- foo interpreted + // Accept "se" for compatibility with Elvis. if (STRNCMP(s, "set ", (size_t)4) == 0 || STRNCMP(s, "se ", (size_t)3) == 0) { if (*e != ':') { // no terminating ':'? @@ -5330,36 +5295,36 @@ static int chk_modeline(linenr_T lnum, int flags) return retval; } -// Return true if "buf" is a help buffer. +/// @return true if "buf" is a help buffer. bool bt_help(const buf_T *const buf) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { return buf != NULL && buf->b_help; } -// Return true if "buf" is a normal buffer, 'buftype' is empty. +/// @return true if "buf" is a normal buffer, 'buftype' is empty. bool bt_normal(const buf_T *const buf) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { return buf != NULL && buf->b_p_bt[0] == NUL; } -// Return true if "buf" is the quickfix buffer. +/// @return true if "buf" is the quickfix buffer. bool bt_quickfix(const buf_T *const buf) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { return buf != NULL && buf->b_p_bt[0] == 'q'; } -// Return true if "buf" is a terminal buffer. +/// @return true if "buf" is a terminal buffer. bool bt_terminal(const buf_T *const buf) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { return buf != NULL && buf->b_p_bt[0] == 't'; } -// Return true if "buf" is a "nofile", "acwrite", "terminal" or "prompt" -// buffer. This means the buffer name is not a file name. +/// @return true if "buf" is a "nofile", "acwrite", "terminal" or "prompt" / +/// buffer. This means the buffer name is not a file name. bool bt_nofile(const buf_T *const buf) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { @@ -5369,8 +5334,8 @@ bool bt_nofile(const buf_T *const buf) || buf->b_p_bt[0] == 'p'); } -// Return true if "buf" is a "nowrite", "nofile", "terminal" or "prompt" -// buffer. +/// @return true if "buf" is a "nowrite", "nofile", "terminal" or "prompt" +/// buffer. bool bt_dontwrite(const buf_T *const buf) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { @@ -5389,8 +5354,8 @@ bool bt_dontwrite_msg(const buf_T *const buf) return false; } -// Return true if the buffer should be hidden, according to 'hidden', ":hide" -// and 'bufhidden'. +/// @return true if the buffer should be hidden, according to 'hidden', ":hide" +/// and 'bufhidden'. bool buf_hide(const buf_T *const buf) { // 'bufhidden' overrules 'hidden' and ":hide", check it first @@ -5405,23 +5370,17 @@ bool buf_hide(const buf_T *const buf) return p_hid || cmdmod.hide; } -/* - * Return special buffer name. - * Returns NULL when the buffer has a normal file name. - */ +/// @return special buffer name or +/// NULL when the buffer has a normal file name. char_u *buf_spname(buf_T *buf) { if (bt_quickfix(buf)) { - win_T *win; - tabpage_T *tp; - - // For location list window, w_llist_ref points to the location list. - // For quickfix window, w_llist_ref is NULL. - if (find_win_for_buf(buf, &win, &tp) && win->w_llist_ref != NULL) { - return (char_u *)_(msg_loclist); - } else { + // Differentiate between the quickfix and location list buffers using + // the buffer number stored in the global quickfix stack. + if (buf->b_fnum == qf_stack_get_bufnr()) { return (char_u *)_(msg_qflist); } + return (char_u *)_(msg_loclist); } // There is no _file_ when 'buftype' is "nofile", b_sfname // contains the name as specified by the user. @@ -5449,7 +5408,7 @@ char_u *buf_spname(buf_T *buf) /// @param[out] wp stores the found window /// @param[out] tp stores the found tabpage /// -/// @return true if a window was found for the buffer. +/// @return true if a window was found for the buffer. bool find_win_for_buf(buf_T *buf, win_T **wp, tabpage_T **tp) { *wp = NULL; @@ -5464,43 +5423,156 @@ 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; + + buf->b_signcols.sentinel = 0; + + FOR_ALL_SIGNS_IN_BUF(buf, sign) { + if (sign->se_lnum > curline) { + // Counted all signs, now add extmark signs + if (curline > 0) { + linesum += decor_signcols(buf, &decor_state, (int)curline-1, (int)curline-1, + maximum-linesum); + } + curline = sign->se_lnum; + if (linesum > signcols) { + signcols = linesum; + buf->b_signcols.sentinel = curline; + if (signcols >= maximum) { + return maximum; } - curline = sign->se_lnum; - linesum = 0; - } - if (sign->se_has_text_or_icon) { - linesum++; } + linesum = 0; } - if (linesum > signcols) { - signcols = linesum; + if (sign->se_has_text_or_icon) { + linesum++; } + } + if (curline > 0) { + linesum += decor_signcols(buf, &decor_state, (int)curline-1, (int)curline-1, maximum-linesum); + } + if (linesum > signcols) { + signcols = linesum; + if (signcols >= maximum) { + return maximum; + } + } + + // Check extmarks between signs + linesum = decor_signcols(buf, &decor_state, 0, (int)buf->b_ml.ml_line_count-1, maximum); + + if (linesum > signcols) { + signcols = linesum; + buf->b_signcols.sentinel = curline; + if (signcols >= maximum) { + return maximum; + } + } + + return signcols; +} + +/// Invalidate the signcolumn if needed after deleting +/// signs between line1 and line2 (inclusive). +/// +/// @param buf buffer to check +/// @param line1 start of region being deleted +/// @param line2 end of region being deleted +void buf_signcols_del_check(buf_T *buf, linenr_T line1, linenr_T line2) +{ + if (!buf->b_signcols.valid) { + return; + } + + if (!buf->b_signcols.sentinel) { + buf->b_signcols.valid = false; + return; + } + + linenr_T sent = buf->b_signcols.sentinel; + + if (sent >= line1 && sent <= line2) { + // Only invalidate when removing signs at the sentinel line. + buf->b_signcols.valid = false; + } +} + +/// Re-calculate the signcolumn after adding a sign. +/// +/// @param buf buffer to check +/// @param added sign being added +void buf_signcols_add_check(buf_T *buf, sign_entry_T *added) +{ + if (!buf->b_signcols.valid) { + return; + } + + if (!added || !buf->b_signcols.sentinel) { + buf->b_signcols.valid = false; + return; + } + + if (added->se_lnum == buf->b_signcols.sentinel) { + if (buf->b_signcols.size == buf->b_signcols.max) { + buf->b_signcols.max++; + } + buf->b_signcols.size++; + redraw_buf_later(buf, NOT_VALID); + return; + } + + sign_entry_T *s; + + // Get first sign for added lnum + for (s = added; s->se_prev && s->se_lnum == s->se_prev->se_lnum; s = s->se_prev) {} + + // Count signs for lnum + int linesum = 1; + for (; s->se_next && s->se_lnum == s->se_next->se_lnum; s = s->se_next) { + linesum++; + } + linesum += decor_signcols(buf, &decor_state, (int)s->se_lnum-1, (int)s->se_lnum-1, + SIGN_SHOW_MAX-linesum); + + if (linesum > buf->b_signcols.size) { + buf->b_signcols.size = linesum; + buf->b_signcols.max = linesum; + buf->b_signcols.sentinel = added->se_lnum; + redraw_buf_later(buf, NOT_VALID); + } +} + +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; + if (signcols != buf->b_signcols.size) { + buf->b_signcols.size = signcols; + buf->b_signcols.max = maximum; redraw_buf_later(buf, NOT_VALID); } - buf->b_signcols_valid = true; + buf->b_signcols.valid = true; } - return buf->b_signcols; + return buf->b_signcols.size; } -// Get "buf->b_fname", use "[No Name]" if it is NULL. +/// Get "buf->b_fname", use "[No Name]" if it is NULL. char_u *buf_get_fname(const buf_T *buf) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL { @@ -5510,9 +5582,7 @@ char_u *buf_get_fname(const buf_T *buf) return buf->b_fname; } -/* - * Set 'buflisted' for curbuf to "on" and trigger autocommands if it changed. - */ +/// Set 'buflisted' for curbuf to "on" and trigger autocommands if it changed. void set_buflisted(int on) { if (on != curbuf->b_p_bl) { @@ -5530,7 +5600,7 @@ void set_buflisted(int on) /// /// @param buf buffer to check /// -/// @return true if the buffer's contents have changed +/// @return true if the buffer's contents have changed bool buf_contents_changed(buf_T *buf) FUNC_ATTR_NONNULL_ALL { @@ -5588,7 +5658,7 @@ void wipe_buffer(buf_T *buf, bool aucmd) // Don't trigger BufDelete autocommands here. block_autocmds(); } - close_buffer(NULL, buf, DOBUF_WIPE, false); + close_buffer(NULL, buf, DOBUF_WIPE, false, true); if (!aucmd) { unblock_autocmds(); } @@ -5611,4 +5681,3 @@ void buf_open_scratch(handle_T bufnr, char *bufname) set_option_value("swf", 0L, NULL, OPT_LOCAL); RESET_BINDING(curwin); } - diff --git a/src/nvim/buffer.h b/src/nvim/buffer.h index 9e2ca999e4..7f4bbcc9e5 100644 --- a/src/nvim/buffer.h +++ b/src/nvim/buffer.h @@ -23,7 +23,7 @@ enum getf_retvalues { GETFILE_ERROR = 1, // normal error GETFILE_NOT_WRITTEN = 2, // "not written" error GETFILE_SAME_FILE = 0, // success, same file - GETFILE_OPEN_OTHER = -1, // success, opened another file + GETFILE_OPEN_OTHER = (-1), // success, opened another file GETFILE_UNUSED = 8, }; @@ -58,9 +58,10 @@ enum dobuf_start_values { // flags for buf_freeall() enum bfa_values { - BFA_DEL = 1, // buffer is going to be deleted - BFA_WIPE = 2, // buffer is going to be wiped out - BFA_KEEP_UNDO = 4, // do not free undo information + BFA_DEL = 1, // buffer is going to be deleted + BFA_WIPE = 2, // buffer is going to be wiped out + BFA_KEEP_UNDO = 4, // do not free undo information + BFA_IGNORE_ABORT = 8, // do not abort for aborting() }; #ifdef INCLUDE_GENERATED_DECLARATIONS diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h index 49e527e98b..375bebc5ac 100644 --- a/src/nvim/buffer_defs.h +++ b/src/nvim/buffer_defs.h @@ -90,7 +90,6 @@ typedef struct { #define BF_NEW_W 0x20 // Warned for BF_NEW and file created #define BF_READERR 0x40 // got errors while reading the file #define BF_DUMMY 0x80 // dummy buffer, only used internally -#define BF_PRESERVED 0x100 // ":preserve" was used #define BF_SYN_SET 0x200 // 'syntax' option was set // Mask to check for flags that prevent normal writing @@ -204,6 +203,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 +355,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 +363,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 +586,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 @@ -654,7 +661,7 @@ struct file_buffer { // flags for use of ":lmap" and IM control long b_p_iminsert; // input mode for insert long b_p_imsearch; // input mode for search -#define B_IMODE_USE_INSERT -1 // Use b_p_iminsert value for search +#define B_IMODE_USE_INSERT (-1) // Use b_p_iminsert value for search #define B_IMODE_NONE 0 // Input via none #define B_IMODE_LMAP 1 // Input via langmap #define B_IMODE_LAST 1 @@ -689,6 +696,7 @@ struct file_buffer { char_u *b_p_cino; ///< 'cinoptions' char_u *b_p_cink; ///< 'cinkeys' char_u *b_p_cinw; ///< 'cinwords' + char_u *b_p_cinsd; ///< 'cinscopedecls' char_u *b_p_com; ///< 'comments' char_u *b_p_cms; ///< 'commentstring' char_u *b_p_cpt; ///< 'complete' @@ -854,8 +862,12 @@ struct file_buffer { // may use a different synblock_T. 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 + struct { + int size; // last calculated number of sign columns + bool valid; // calculated sign columns is valid + linenr_T sentinel; // a line number which is holding up the signcolumn + int max; // Maximum value size is valid for. + } b_signcols; Terminal *terminal; // Terminal instance associated with the buffer @@ -864,9 +876,9 @@ 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 + size_t b_signs; // number of sign extmarks // array of channel_id:s which have asked to receive updates for this // buffer. @@ -1200,6 +1212,8 @@ struct window_S { colnr_T w_old_visual_col; ///< last known start of visual part colnr_T w_old_curswant; ///< last known value of Curswant + linenr_T w_last_cursor_lnum_rnu; ///< cursor lnum when 'rnu' was last redrawn + // 'listchars' characters. Defaults set in set_chars_option(). struct { int eol; @@ -1220,7 +1234,13 @@ struct window_S { struct { int stl; int stlnc; + int horiz; + int horizup; + int horizdown; int vert; + int vertleft; + int vertright; + int verthoriz; int fold; int foldopen; ///< when fold is open int foldclosed; ///< when fold is closed @@ -1249,12 +1269,11 @@ struct window_S { colnr_T w_skipcol; // starting column when a single line // doesn't fit in the window - // "w_last_topline" and "w_last_leftcol" are used to determine if - // a Scroll autocommand should be emitted. - linenr_T w_last_topline; ///< last known value for topline - colnr_T w_last_leftcol; ///< last known value for leftcol - int w_last_width; ///< last known value for width - int w_last_height; ///< last known value for height + // four fields that are only used when there is a WinScrolled autocommand + linenr_T w_last_topline; ///< last known value for w_topline + colnr_T w_last_leftcol; ///< last known value for w_leftcol + int w_last_width; ///< last known value for w_width + int w_last_height; ///< last known value for w_height // // Layout of the window in the screen. @@ -1266,7 +1285,8 @@ struct window_S { int w_status_height; // number of status lines (0 or 1) int w_wincol; // Leftmost column of window in screen. int w_width; // Width of window, excluding separation. - int w_vsep_width; // Number of separator columns (0 or 1). + int w_hsep_height; // Number of horizontal separator rows (0 or 1) + int w_vsep_width; // Number of vertical separator columns (0 or 1). pos_save_T w_save_cursor; // backup of cursor pos and topline // inner size of window, which can be overridden by external UI @@ -1346,6 +1366,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 === @@ -1399,7 +1420,7 @@ struct window_S { int w_briopt_list; // additional indent for lists // transform a pointer to a "onebuf" option into a "allbuf" option -#define GLOBAL_WO(p) ((char *)p + sizeof(winopt_T)) +#define GLOBAL_WO(p) ((char *)(p) + sizeof(winopt_T)) long w_scbind_pos; diff --git a/src/nvim/change.c b/src/nvim/change.c index c52d992fbe..024969415d 100644 --- a/src/nvim/change.c +++ b/src/nvim/change.c @@ -19,7 +19,6 @@ #include "nvim/indent_c.h" #include "nvim/mark.h" #include "nvim/memline.h" -#include "nvim/misc1.h" #include "nvim/move.h" #include "nvim/option.h" #include "nvim/plines.h" @@ -43,7 +42,7 @@ /// Careful: may trigger autocommands that reload the buffer. void change_warning(buf_T *buf, int col) { - static char *w_readonly = N_("W10: Warning: Changing a readonly file"); + static const char *w_readonly = N_("W10: Warning: Changing a readonly file"); if (buf->b_did_warn == false && curbufIsChanged() == 0 @@ -131,7 +130,7 @@ void changed_internal(void) curbuf->b_changed = true; curbuf->b_changed_invalid = true; ml_setflags(curbuf); - check_status(curbuf); + redraw_buf_status_later(curbuf); redraw_tabline = true; need_maketitle = true; // set window title later } @@ -141,10 +140,6 @@ void changed_internal(void) /// Careful: may trigger autocommands that reload the buffer. static void changed_common(linenr_T lnum, colnr_T col, linenr_T lnume, long xtra) { - int i; - pos_T *p; - int add; - // mark the buffer as modified changed(); @@ -159,13 +154,14 @@ static void changed_common(linenr_T lnum, colnr_T col, linenr_T lnume, long xtra // Create a new entry if a new undo-able change was started or we // don't have an entry yet. if (curbuf->b_new_change || curbuf->b_changelistlen == 0) { + int add; if (curbuf->b_changelistlen == 0) { add = true; } else { // Don't create a new entry when the line number is the same // as the last one and the column is not too far away. Avoids // creating many entries for typing "xxxxx". - p = &curbuf->b_changelist[curbuf->b_changelistlen - 1].mark; + pos_T *p = &curbuf->b_changelist[curbuf->b_changelistlen - 1].mark; if (p->lnum != lnum) { add = true; } else { @@ -224,26 +220,27 @@ 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; } // If the changed line is in a range of previously folded lines, // compare with the first line in that range. if (wp->w_cursor.lnum <= lnum) { - i = find_wl_entry(wp, lnum); + int i = find_wl_entry(wp, lnum); if (i >= 0 && wp->w_cursor.lnum > wp->w_lines[i].wl_lnum) { changed_line_abv_curs_win(wp); } @@ -264,7 +261,7 @@ static void changed_common(linenr_T lnum, colnr_T col, linenr_T lnume, long xtra // For entries below the change: Correct the lnums for // inserted/deleted lines. Makes it possible to stop displaying // after the change. - for (i = 0; i < wp->w_lines_valid; i++) { + for (int i = 0; i < wp->w_lines_valid; i++) { if (wp->w_lines[i].wl_valid) { if (wp->w_lines[i].wl_lnum >= lnum) { if (wp->w_lines[i].wl_lnum < lnume) { @@ -289,9 +286,11 @@ static void changed_common(linenr_T lnum, colnr_T col, linenr_T lnume, long xtra set_topline(wp, wp->w_topline); } - // Relative numbering may require updating more. - if (wp->w_p_rnu) { - redraw_later(wp, SOME_VALID); + // If lines have been added or removed, relative numbering always + // requires a redraw. + if (wp->w_p_rnu && xtra != 0) { + wp->w_last_cursor_lnum_rnu = 0; + redraw_later(wp, VALID); } // Cursor line highlighting probably need to be updated with @@ -352,12 +351,10 @@ void changed_bytes(linenr_T lnum, colnr_T col) // Diff highlighting in other diff windows may need to be updated too. if (curwin->w_p_diff) { - linenr_T wlnum; - FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { if (wp->w_p_diff && wp != curwin) { redraw_later(wp, VALID); - wlnum = diff_lnum_win(lnum, wp); + linenr_T wlnum = diff_lnum_win(lnum, wp); if (wlnum > 0) { changedOneline(wp->w_buffer, wlnum); } @@ -413,7 +410,7 @@ void deleted_lines(linenr_T lnum, long count) /// be triggered to display the cursor. void deleted_lines_mark(linenr_T lnum, long count) { - // if we deleted the entire buffer, we need to implicity add a new empty line + // if we deleted the entire buffer, we need to implicitly add a new empty line bool made_empty = (count > 0) && curbuf->b_ml.ml_flags & ML_EMPTY; mark_adjust(lnum, (linenr_T)(lnum + count - 1), (long)MAXLNUM, @@ -517,7 +514,7 @@ void unchanged(buf_T *buf, int ff, bool always_inc_changedtick) if (ff) { save_file_ff(buf); } - check_status(buf); + redraw_buf_status_later(buf); redraw_tabline = true; need_maketitle = true; // set window title later buf_inc_changedtick(buf); @@ -625,7 +622,7 @@ void ins_char_bytes(char_u *buf, size_t charlen) } } - char_u *newp = xmalloc((size_t)(linelen + newlen - oldlen)); + char_u *newp = xmalloc(linelen + newlen - oldlen); // Copy bytes before the cursor. if (col > 0) { @@ -673,21 +670,18 @@ void ins_char_bytes(char_u *buf, size_t charlen) /// Caller must have prepared for undo. void ins_str(char_u *s) { - char_u *oldp, *newp; int newlen = (int)STRLEN(s); - int oldlen; - colnr_T col; linenr_T lnum = curwin->w_cursor.lnum; if (virtual_active() && curwin->w_cursor.coladd > 0) { coladvance_force(getviscol()); } - col = curwin->w_cursor.col; - oldp = ml_get(lnum); - oldlen = (int)STRLEN(oldp); + colnr_T col = curwin->w_cursor.col; + char_u *oldp = ml_get(lnum); + int oldlen = (int)STRLEN(oldp); - newp = (char_u *)xmalloc((size_t)oldlen + (size_t)newlen + 1); + char_u *newp = (char_u *)xmalloc((size_t)oldlen + (size_t)newlen + 1); if (col > 0) { memmove(newp, oldp, (size_t)col); } @@ -719,13 +713,9 @@ int del_char(bool fixpos) int del_chars(long count, int fixpos) { int bytes = 0; - long i; - char_u *p; - int l; - - p = get_cursor_pos_ptr(); - for (i = 0; i < count && *p != NUL; i++) { - l = utfc_ptr2len(p); + char_u *p = get_cursor_pos_ptr(); + for (long i = 0; i < count && *p != NUL; i++) { + int l = utfc_ptr2len(p); bytes += l; p += l; } @@ -768,12 +758,11 @@ int del_bytes(colnr_T count, bool fixpos_arg, bool use_delcombine) if (p_deco && use_delcombine && utfc_ptr2len(oldp + col) >= count) { int cc[MAX_MCO]; - int n; (void)utfc_ptr2char(oldp + col, cc); if (cc[0] != NUL) { // Find the last composing char, there can be several. - n = col; + int n = col; do { col = n; count = utf_ptr2len(oldp + n); @@ -790,7 +779,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); @@ -828,23 +817,18 @@ int copy_indent(int size, char_u *src) { char_u *p = NULL; char_u *line = NULL; - char_u *s; - int todo; int ind_len; int line_len = 0; int tab_pad; - int ind_done; - int round; - int ind_col; // Round 1: compute the number of characters needed for the indent // Round 2: copy the characters. - for (round = 1; round <= 2; round++) { - todo = size; + for (int round = 1; round <= 2; round++) { + int todo = size; ind_len = 0; - ind_done = 0; - ind_col = 0; - s = src; + int ind_done = 0; + int ind_col = 0; + char_u *s = src; // Count/copy the usable portion of the source line. while (todo > 0 && ascii_iswhite(*s)) { @@ -953,11 +937,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 @@ -970,6 +956,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 @@ -978,6 +965,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; @@ -1061,7 +1049,6 @@ int open_line(int dir, int flags, int second_line_indent) if (!trunc_line && do_si && *saved_line != NUL && (p_extra == NULL || first_char != '{')) { char_u *ptr; - char_u last_char; old_cursor = curwin->w_cursor; ptr = saved_line; @@ -1071,8 +1058,7 @@ int open_line(int dir, int flags, int second_line_indent) lead_len = 0; } if (dir == FORWARD) { - // Skip preprocessor directives, unless they are - // recognised as comments. + // Skip preprocessor directives, unless they are recognised as comments. if (lead_len == 0 && ptr[0] == '#') { while (ptr[0] == '#' && curwin->w_cursor.lnum > 1) { ptr = ml_get(--curwin->w_cursor.lnum); @@ -1115,7 +1101,7 @@ int open_line(int dir, int flags, int second_line_indent) while (p > ptr && ascii_iswhite(*p)) { p--; } - last_char = *p; + char_u last_char = *p; // find the character just before the '{' or ';' if (last_char == '{' || last_char == ';') { @@ -1190,11 +1176,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 && curbuf->b_p_cin && 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; } @@ -1350,6 +1355,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; @@ -1759,13 +1771,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(); } @@ -1863,3 +1869,287 @@ void del_lines(long nlines, bool undo) // adjust marks, mark the buffer as changed and prepare for displaying deleted_lines_mark(first, n); } + +/// Returns the length in bytes of the prefix of the given string which introduces a comment. +/// +/// If this string is not a comment then 0 is returned. +/// When "flags" is not NULL, it is set to point to the flags of the recognized comment leader. +/// "backward" must be true for the "O" command. +/// If "include_space" is set, include trailing whitespace while calculating the length. +int get_leader_len(char_u *line, char_u **flags, bool backward, bool include_space) +{ + int j; + int got_com = false; + char_u part_buf[COM_MAX_LEN]; // buffer for one option part + char_u *string; // pointer to comment string + char_u *list; + int middle_match_len = 0; + char_u *prev_list; + char_u *saved_flags = NULL; + + int result = 0; + int i = 0; + while (ascii_iswhite(line[i])) { // leading white space is ignored + i++; + } + + // Repeat to match several nested comment strings. + while (line[i] != NUL) { + // scan through the 'comments' option for a match + int found_one = false; + for (list = curbuf->b_p_com; *list;) { + // Get one option part into part_buf[]. Advance "list" to next + // one. Put "string" at start of string. + if (!got_com && flags != NULL) { + *flags = list; // remember where flags started + } + prev_list = list; + (void)copy_option_part(&list, part_buf, COM_MAX_LEN, ","); + string = vim_strchr(part_buf, ':'); + if (string == NULL) { // missing ':', ignore this part + continue; + } + *string++ = NUL; // isolate flags from string + + // If we found a middle match previously, use that match when this + // is not a middle or end. + if (middle_match_len != 0 + && vim_strchr(part_buf, COM_MIDDLE) == NULL + && vim_strchr(part_buf, COM_END) == NULL) { + break; + } + + // When we already found a nested comment, only accept further + // nested comments. + if (got_com && vim_strchr(part_buf, COM_NEST) == NULL) { + continue; + } + + // When 'O' flag present and using "O" command skip this one. + if (backward && vim_strchr(part_buf, COM_NOBACK) != NULL) { + continue; + } + + // Line contents and string must match. + // When string starts with white space, must have some white space + // (but the amount does not need to match, there might be a mix of + // TABs and spaces). + if (ascii_iswhite(string[0])) { + if (i == 0 || !ascii_iswhite(line[i - 1])) { + continue; // missing white space + } + while (ascii_iswhite(string[0])) { + string++; + } + } + for (j = 0; string[j] != NUL && string[j] == line[i + j]; j++) { + } + if (string[j] != NUL) { + continue; // string doesn't match + } + // When 'b' flag used, there must be white space or an + // end-of-line after the string in the line. + if (vim_strchr(part_buf, COM_BLANK) != NULL + && !ascii_iswhite(line[i + j]) && line[i + j] != NUL) { + continue; + } + + // We have found a match, stop searching unless this is a middle + // comment. The middle comment can be a substring of the end + // comment in which case it's better to return the length of the + // end comment and its flags. Thus we keep searching with middle + // and end matches and use an end match if it matches better. + if (vim_strchr(part_buf, COM_MIDDLE) != NULL) { + if (middle_match_len == 0) { + middle_match_len = j; + saved_flags = prev_list; + } + continue; + } + if (middle_match_len != 0 && j > middle_match_len) { + // Use this match instead of the middle match, since it's a + // longer thus better match. + middle_match_len = 0; + } + + if (middle_match_len == 0) { + i += j; + } + found_one = true; + break; + } + + if (middle_match_len != 0) { + // Use the previously found middle match after failing to find a + // match with an end. + if (!got_com && flags != NULL) { + *flags = saved_flags; + } + i += middle_match_len; + found_one = true; + } + + // No match found, stop scanning. + if (!found_one) { + break; + } + + result = i; + + // Include any trailing white space. + while (ascii_iswhite(line[i])) { + i++; + } + + if (include_space) { + result = i; + } + + // If this comment doesn't nest, stop here. + got_com = true; + if (vim_strchr(part_buf, COM_NEST) == NULL) { + break; + } + } + return result; +} + +/// Return the offset at which the last comment in line starts. If there is no +/// comment in the whole line, -1 is returned. +/// +/// When "flags" is not null, it is set to point to the flags describing the +/// recognized comment leader. +int get_last_leader_offset(char_u *line, char_u **flags) +{ + int result = -1; + int j; + int lower_check_bound = 0; + char_u *string; + char_u *com_leader; + char_u *com_flags; + char_u *list; + char_u part_buf[COM_MAX_LEN]; // buffer for one option part + + // Repeat to match several nested comment strings. + int i = (int)STRLEN(line); + while (--i >= lower_check_bound) { + // scan through the 'comments' option for a match + int found_one = false; + for (list = curbuf->b_p_com; *list;) { + char_u *flags_save = list; + + // Get one option part into part_buf[]. Advance list to next one. + // put string at start of string. + (void)copy_option_part(&list, part_buf, COM_MAX_LEN, ","); + string = vim_strchr(part_buf, ':'); + if (string == NULL) { // If everything is fine, this cannot actually + // happen. + continue; + } + *string++ = NUL; // Isolate flags from string. + com_leader = string; + + // Line contents and string must match. + // When string starts with white space, must have some white space + // (but the amount does not need to match, there might be a mix of + // TABs and spaces). + if (ascii_iswhite(string[0])) { + if (i == 0 || !ascii_iswhite(line[i - 1])) { + continue; + } + while (ascii_iswhite(*string)) { + string++; + } + } + for (j = 0; string[j] != NUL && string[j] == line[i + j]; j++) { + // do nothing + } + if (string[j] != NUL) { + continue; + } + + // When 'b' flag used, there must be white space or an + // end-of-line after the string in the line. + if (vim_strchr(part_buf, COM_BLANK) != NULL + && !ascii_iswhite(line[i + j]) && line[i + j] != NUL) { + continue; + } + + if (vim_strchr(part_buf, COM_MIDDLE) != NULL) { + // For a middlepart comment, only consider it to match if + // everything before the current position in the line is + // whitespace. Otherwise we would think we are inside a + // comment if the middle part appears somewhere in the middle + // of the line. E.g. for C the "*" appears often. + for (j = 0; j <= i && ascii_iswhite(line[j]); j++) { + } + if (j < i) { + continue; + } + } + + // We have found a match, stop searching. + found_one = true; + + if (flags) { + *flags = flags_save; + } + com_flags = flags_save; + + break; + } + + if (found_one) { + char_u part_buf2[COM_MAX_LEN]; // buffer for one option part + int len1, len2, off; + + result = i; + // If this comment nests, continue searching. + if (vim_strchr(part_buf, COM_NEST) != NULL) { + continue; + } + + lower_check_bound = i; + + // Let's verify whether the comment leader found is a substring + // of other comment leaders. If it is, let's adjust the + // lower_check_bound so that we make sure that we have determined + // the comment leader correctly. + + while (ascii_iswhite(*com_leader)) { + com_leader++; + } + len1 = (int)STRLEN(com_leader); + + for (list = curbuf->b_p_com; *list;) { + char_u *flags_save = list; + + (void)copy_option_part(&list, part_buf2, COM_MAX_LEN, ","); + if (flags_save == com_flags) { + continue; + } + string = vim_strchr(part_buf2, ':'); + string++; + while (ascii_iswhite(*string)) { + string++; + } + len2 = (int)STRLEN(string); + if (len2 == 0) { + continue; + } + + // Now we have to verify whether string ends with a substring + // beginning the com_leader. + for (off = (len2 > i ? i : len2); off > 0 && off + len1 > len2;) { + off--; + if (!STRNCMP(string + off, com_leader, len2 - off)) { + if (i - off < lower_check_bound) { + lower_check_bound = i - off; + } + } + } + } + } + } + return result; +} diff --git a/src/nvim/change.h b/src/nvim/change.h index e1a1bfba17..e7c8a2b031 100644 --- a/src/nvim/change.h +++ b/src/nvim/change.h @@ -4,6 +4,13 @@ #include "nvim/buffer_defs.h" // for buf_T #include "nvim/pos.h" // for linenr_T +// flags for open_line() +#define OPENLINE_DELSPACES 1 // delete spaces after cursor +#define OPENLINE_DO_COM 2 // format comments +#define OPENLINE_KEEPTRAIL 4 // keep trailing spaces +#define OPENLINE_MARKFIX 8 // fix mark positions +#define OPENLINE_COM_LIST 16 // format comments with list/2nd line indent + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "change.h.generated.h" #endif diff --git a/src/nvim/channel.c b/src/nvim/channel.c index 9662f6205f..ac6c9cd110 100644 --- a/src/nvim/channel.c +++ b/src/nvim/channel.c @@ -138,8 +138,14 @@ bool channel_close(uint64_t id, ChannelPart part, const char **error) *error = (const char *)e_invstream; return false; } - api_free_luaref(chan->stream.internal.cb); - chan->stream.internal.cb = LUA_NOREF; + if (chan->term) { + api_free_luaref(chan->stream.internal.cb); + chan->stream.internal.cb = LUA_NOREF; + chan->stream.internal.closed = true; + terminal_close(chan->term, 0); + } else { + channel_decref(chan); + } break; default: @@ -536,7 +542,11 @@ size_t channel_send(uint64_t id, char *data, size_t len, bool data_owned, const } if (chan->streamtype == kChannelStreamInternal) { - if (!chan->term) { + if (chan->is_rpc) { + *error = _("Can't send raw data to rpc channel"); + goto retfree; + } + if (!chan->term || chan->stream.internal.closed) { *error = _("Can't send data to closed stream"); goto retfree; } @@ -613,7 +623,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); @@ -821,15 +830,17 @@ static void set_info_event(void **argv) Channel *chan = argv[0]; event_T event = (event_T)(ptrdiff_t)argv[1]; - dict_T *dict = get_vim_var_dict(VV_EVENT); + save_v_event_T save_v_event; + dict_T *dict = get_v_event(&save_v_event); Dictionary info = channel_info(chan->id); typval_T retval; (void)object_to_vim(DICTIONARY_OBJ(info), &retval, NULL); tv_dict_add_dict(dict, S_LEN("info"), retval.vval.v_dict); + tv_dict_set_keys_readonly(dict); apply_autocmds(event, NULL, NULL, false, curbuf); - tv_dict_clear(dict); + restore_v_event(dict, &save_v_event); api_free_dictionary(info); channel_decref(chan); } diff --git a/src/nvim/channel.h b/src/nvim/channel.h index 81b75e2d31..5cec5731eb 100644 --- a/src/nvim/channel.h +++ b/src/nvim/channel.h @@ -44,6 +44,7 @@ typedef struct { typedef struct { LuaRef cb; + bool closed; } InternalState; typedef struct { @@ -96,6 +97,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 eb0903b594..ec1866e9cc 100644 --- a/src/nvim/charset.c +++ b/src/nvim/charset.c @@ -21,7 +21,6 @@ #include "nvim/mbyte.h" #include "nvim/memline.h" #include "nvim/memory.h" -#include "nvim/misc1.h" #include "nvim/move.h" #include "nvim/option.h" #include "nvim/os_unix.h" @@ -160,7 +159,7 @@ int buf_init_chartab(buf_T *buf, int global) if ((*p == '^') && (p[1] != NUL)) { tilde = true; - ++p; + p++; } if (ascii_isdigit(*p)) { @@ -171,7 +170,7 @@ int buf_init_chartab(buf_T *buf, int global) c2 = -1; if ((*p == '-') && (p[1] != NUL)) { - ++p; + p++; if (ascii_isdigit(*p)) { c2 = getdigits_int((char_u **)&p, true, 0); @@ -218,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)); @@ -246,7 +243,7 @@ int buf_init_chartab(buf_T *buf, int global) } } } - ++c; + c++; } c = *p; @@ -271,15 +268,12 @@ int buf_init_chartab(buf_T *buf, int global) /// @param bufsize void trans_characters(char_u *buf, int bufsize) { - int len; // length of string needing translation - int room; // room in buffer after string - char_u *trs; // translated character - int trs_len; // length of trs[] - - len = (int)STRLEN(buf); - room = bufsize - len; + char_u *trs; // translated character + int len = (int)STRLEN(buf); // length of string needing translation + int room = bufsize - len; // room in buffer after string while (*buf != 0) { + int trs_len; // length of trs[] // Assume a multi-byte character doesn't need translation. if ((trs_len = utfc_ptr2len(buf)) > 1) { len -= trs_len; @@ -295,7 +289,7 @@ void trans_characters(char_u *buf, int bufsize) memmove(buf + trs_len, buf + 1, (size_t)len); } memmove(buf, trs, (size_t)trs_len); - --len; + len--; } buf += trs_len; } @@ -431,9 +425,9 @@ char_u *str_foldcase(char_u *str, int orglen, char_u *buf, int buflen) int len = orglen; #define GA_CHAR(i) ((char_u *)ga.ga_data)[i] -#define GA_PTR(i) ((char_u *)ga.ga_data + i) +#define GA_PTR(i) ((char_u *)ga.ga_data + (i)) #define STR_CHAR(i) (buf == NULL ? GA_CHAR(i) : buf[i]) -#define STR_PTR(i) (buf == NULL ? GA_PTR(i) : buf + i) +#define STR_PTR(i) (buf == NULL ? GA_PTR(i) : buf + (i)) // Copy "str" into "buf" or allocated memory, unmodified. if (buf == NULL) { @@ -540,7 +534,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; @@ -876,14 +870,11 @@ bool vim_isprintc_strict(int c) bool in_win_border(win_T *wp, colnr_T vcol) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ARG(1) { - int width1; // width of first line (after line number) - int width2; // width of further lines - if (wp->w_width_inner == 0) { // there is no border return false; } - width1 = wp->w_width_inner - win_col_off(wp); + int width1 = wp->w_width_inner - win_col_off(wp); // width of first line (after line number) if ((int)vcol < width1 - 1) { return false; @@ -892,7 +883,7 @@ bool in_win_border(win_T *wp, colnr_T vcol) if ((int)vcol == width1 - 1) { return true; } - width2 = width1 + win_col_off2(wp); + int width2 = width1 + win_col_off2(wp); // width of further lines if (width2 <= 0) { return false; @@ -914,27 +905,26 @@ bool in_win_border(win_T *wp, colnr_T vcol) /// @param end void getvcol(win_T *wp, pos_T *pos, colnr_T *start, colnr_T *cursor, colnr_T *end) { - colnr_T vcol; char_u *ptr; // points to current char char_u *posptr; // points to char at pos->col - char_u *line; // start of the line int incr; int head; long *vts = wp->w_buffer->b_p_vts_array; int ts = (int)wp->w_buffer->b_p_ts; - int c; - vcol = 0; - line = ptr = ml_get_buf(wp->w_buffer, pos->lnum, false); + colnr_T vcol = 0; + char_u *line = ptr = ml_get_buf(wp->w_buffer, pos->lnum, false); // start of the line if (pos->col == MAXCOL) { // continue until the NUL posptr = NULL; } else { - // Special check for an empty line, which can happen on exit, when - // ml_get_buf() always returns an empty string. - if (*ptr == NUL) { - pos->col = 0; + // In a few cases the position can be beyond the end of the line. + for (colnr_T i = 0; i < pos->col; i++) { + if (ptr[i] == NUL) { + pos->col = i; + break; + } } posptr = ptr + pos->col; posptr -= utf_head_off(line, posptr); @@ -950,7 +940,7 @@ void getvcol(win_T *wp, pos_T *pos, colnr_T *start, colnr_T *cursor, colnr_T *en && !wp->w_p_bri) { for (;;) { head = 0; - c = *ptr; + int c = *ptr; // make sure we don't go past the end of the line if (c == NUL) { @@ -1067,19 +1057,16 @@ colnr_T getvcol_nolist(pos_T *posp) void getvvcol(win_T *wp, pos_T *pos, colnr_T *start, colnr_T *cursor, colnr_T *end) { colnr_T col; - colnr_T coladd; - colnr_T endadd; - char_u *ptr; if (virtual_active()) { // For virtual mode, only want one value getvcol(wp, pos, &col, NULL, NULL); - coladd = pos->coladd; - endadd = 0; + colnr_T coladd = pos->coladd; + colnr_T endadd = 0; // Cannot put the cursor on part of a wide character. - ptr = ml_get_buf(wp->w_buffer, pos->lnum, false); + char_u *ptr = ml_get_buf(wp->w_buffer, pos->lnum, false); if (pos->col < (colnr_T)STRLEN(ptr)) { int c = utf_ptr2char(ptr + pos->col); @@ -1315,9 +1302,9 @@ char_u *skiptowhite_esc(char_u *p) { while (*p != ' ' && *p != '\t' && *p != NUL) { if (((*p == '\\') || (*p == Ctrl_V)) && (*(p + 1) != NUL)) { - ++p; + p++; } - ++p; + p++; } return p; } @@ -1442,7 +1429,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, @@ -1503,7 +1490,7 @@ void vim_str2nr(const char_u *const start, int *const prep, int *const len, cons } else if ((what & (STR2NR_HEX | STR2NR_OCT | STR2NR_OOCT | STR2NR_BIN)) && !STRING_ENDED(ptr + 1) && ptr[0] == '0' && ptr[1] != '8' && ptr[1] != '9') { - pre = ptr[1]; + pre = (char_u)ptr[1]; // Detect hexadecimal: 0x or 0X followed by hex digit. if ((what & STR2NR_HEX) && !STRING_ENDED(ptr + 2) @@ -1588,7 +1575,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; @@ -1687,7 +1674,7 @@ bool rem_backslash(const char_u *str) /// @param p void backslash_halve(char_u *p) { - for (; *p; ++p) { + for (; *p; p++) { if (rem_backslash(p)) { STRMOVE(p, p + 1); } diff --git a/src/nvim/charset.h b/src/nvim/charset.h index 47d89717e8..c4e5d9522b 100644 --- a/src/nvim/charset.h +++ b/src/nvim/charset.h @@ -14,8 +14,8 @@ /// @return Folded variant. #define CH_FOLD(c) \ utf_fold((sizeof(c) == sizeof(char)) \ - ?((int)(uint8_t)(c)) \ - :((int)(c))) + ? ((int)(uint8_t)(c)) \ + : ((int)(c))) /// Flags for vim_str2nr() typedef enum { 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 e334fd166e..d924119fdf 100644 --- a/src/nvim/cursor.c +++ b/src/nvim/cursor.c @@ -14,8 +14,8 @@ #include "nvim/mark.h" #include "nvim/memline.h" #include "nvim/memory.h" -#include "nvim/misc1.h" #include "nvim/move.h" +#include "nvim/option.h" #include "nvim/plines.h" #include "nvim/screen.h" #include "nvim/state.h" @@ -25,9 +25,7 @@ # include "cursor.c.generated.h" #endif -/* - * Get the screen position of the cursor. - */ +/// @return the screen position of the cursor. int getviscol(void) { colnr_T x; @@ -36,9 +34,7 @@ int getviscol(void) return (int)x; } -/* - * Get the screen position of character col with a coladd in the cursor line. - */ +/// @return the screen position of character col with a coladd in the cursor line. int getviscol2(colnr_T col, colnr_T coladd) { colnr_T x; @@ -51,11 +47,9 @@ int getviscol2(colnr_T col, colnr_T coladd) return (int)x; } -/* - * Go to column "wcol", and add/insert white space as necessary to get the - * cursor in that column. - * The caller must have saved the cursor line for undo! - */ +/// Go to column "wcol", and add/insert white space as necessary to get the +/// cursor in that column. +/// The caller must have saved the cursor line for undo! int coladvance_force(colnr_T wcol) { int rc = coladvance2(&curwin->w_cursor, true, false, wcol); @@ -70,15 +64,13 @@ int coladvance_force(colnr_T wcol) return rc; } -/* - * Try to advance the Cursor to the specified screen column. - * If virtual editing: fine tune the cursor position. - * Note that all virtual positions off the end of a line should share - * a curwin->w_cursor.col value (n.b. this is equal to STRLEN(line)), - * beginning at coladd 0. - * - * return OK if desired column is reached, FAIL if not - */ +/// Try to advance the Cursor to the specified screen column. +/// If virtual editing: fine tune the cursor position. +/// Note that all virtual positions off the end of a line should share +/// a curwin->w_cursor.col value (n.b. this is equal to STRLEN(line)), +/// beginning at coladd 0. +/// +/// @return OK if desired column is reached, FAIL if not int coladvance(colnr_T wcol) { int rc = getvpos(&curwin->w_cursor, wcol); @@ -100,18 +92,16 @@ static int coladvance2(pos_T *pos, bool addspaces, bool finetune, colnr_T wcol_a { colnr_T wcol = wcol_arg; int idx; - char_u *ptr; - char_u *line; colnr_T col = 0; - int csize = 0; - int one_more; int head = 0; - one_more = (State & INSERT) - || restart_edit != NUL - || (VIsual_active && *p_sel != 'o') - || ((ve_flags & VE_ONEMORE) && wcol < MAXCOL); - line = ml_get_buf(curbuf, pos->lnum, false); + int one_more = (State & INSERT) + || (State & TERM_FOCUS) + || restart_edit != NUL + || (VIsual_active && *p_sel != 'o') + || ((get_ve_flags() & VE_ONEMORE) && wcol < MAXCOL); + + char_u *line = ml_get_buf(curbuf, pos->lnum, false); if (wcol >= MAXCOL) { idx = (int)STRLEN(line) - 1 + one_more; @@ -120,11 +110,12 @@ static int coladvance2(pos_T *pos, bool addspaces, bool finetune, colnr_T wcol_a if ((addspaces || finetune) && !VIsual_active) { curwin->w_curswant = linetabsize(line) + one_more; if (curwin->w_curswant > 0) { - --curwin->w_curswant; + curwin->w_curswant--; } } } else { int width = curwin->w_width_inner - win_col_off(curwin); + int csize = 0; if (finetune && curwin->w_p_wrap @@ -138,15 +129,15 @@ static int coladvance2(pos_T *pos, bool addspaces, bool finetune, colnr_T wcol_a if (wcol / width > (colnr_T)csize / width && ((State & INSERT) == 0 || (int)wcol > csize + 1)) { - /* In case of line wrapping don't move the cursor beyond the - * right screen edge. In Insert mode allow going just beyond - * the last character (like what happens when typing and - * reaching the right window edge). */ + // In case of line wrapping don't move the cursor beyond the + // right screen edge. In Insert mode allow going just beyond + // the last character (like what happens when typing and + // reaching the right window edge). wcol = (csize / width + 1) * width - 1; } } - ptr = line; + char_u *ptr = line; while (col <= wcol && *ptr != NUL) { // Count a tab for what it's worth (if list mode not on) csize = win_lbr_chartabsize(curwin, line, ptr, col, &head); @@ -154,12 +145,10 @@ static int coladvance2(pos_T *pos, bool addspaces, bool finetune, colnr_T wcol_a col += csize; } idx = (int)(ptr - line); - /* - * Handle all the special cases. The virtual_active() check - * is needed to ensure that a virtual position off the end of - * a line has the correct indexing. The one_more comparison - * replaces an explicit add of one_more later on. - */ + // Handle all the special cases. The virtual_active() check + // is needed to ensure that a virtual position off the end of + // a line has the correct indexing. The one_more comparison + // replaces an explicit add of one_more later on. if (col > wcol || (!virtual_active() && one_more == 0)) { idx -= 1; // Don't count the chars from 'showbreak'. @@ -171,8 +160,7 @@ static int coladvance2(pos_T *pos, bool addspaces, bool finetune, colnr_T wcol_a && addspaces && wcol >= 0 && ((col != wcol && col != wcol + 1) || csize > 1)) { - /* 'virtualedit' is set: The difference between wcol and col is - * filled with spaces. */ + // 'virtualedit' is set: The difference between wcol and col is filled with spaces. if (line[idx] == NUL) { // Append spaces @@ -255,29 +243,23 @@ static int coladvance2(pos_T *pos, bool addspaces, bool finetune, colnr_T wcol_a return OK; } -/* - * Return in "pos" the position of the cursor advanced to screen column "wcol". - * return OK if desired column is reached, FAIL if not - */ +/// Return in "pos" the position of the cursor advanced to screen column "wcol". +/// +/// @return OK if desired column is reached, FAIL if not int getvpos(pos_T *pos, colnr_T wcol) { return coladvance2(pos, false, virtual_active(), wcol); } -/* - * Increment the cursor position. See inc() for return values. - */ +/// Increment the cursor position. See inc() for return values. int inc_cursor(void) { return inc(&curwin->w_cursor); } -/* - * dec(p) - * - * Decrement the line pointer 'p' crossing line boundaries as necessary. - * Return 1 when crossing a line, -1 when at start of file, 0 otherwise. - */ +/// Decrement the line pointer 'p' crossing line boundaries as necessary. +/// +/// @return 1 when crossing a line, -1 when at start of file, 0 otherwise. int dec_cursor(void) { return dec(&curwin->w_cursor); @@ -313,34 +295,29 @@ linenr_T get_cursor_rel_lnum(win_T *wp, linenr_T lnum) return (lnum < cursor) ? -retval : retval; } -// Make sure "pos.lnum" and "pos.col" are valid in "buf". -// This allows for the col to be on the NUL byte. +/// Make sure "pos.lnum" and "pos.col" are valid in "buf". +/// This allows for the col to be on the NUL byte. void check_pos(buf_T *buf, pos_T *pos) { - char_u *line; - colnr_T len; - if (pos->lnum > buf->b_ml.ml_line_count) { pos->lnum = buf->b_ml.ml_line_count; } if (pos->col > 0) { - line = ml_get_buf(buf, pos->lnum, false); - len = (colnr_T)STRLEN(line); + char_u *line = ml_get_buf(buf, pos->lnum, false); + colnr_T len = (colnr_T)STRLEN(line); if (pos->col > len) { pos->col = len; } } } -/* - * Make sure curwin->w_cursor.lnum is valid. - */ +/// Make sure curwin->w_cursor.lnum is valid. void check_cursor_lnum(void) { if (curwin->w_cursor.lnum > curbuf->b_ml.ml_line_count) { - /* If there is a closed fold at the end of the file, put the cursor in - * its first line. Otherwise in the last line. */ + // If there is a closed fold at the end of the file, put the cursor in + // its first line. Otherwise in the last line. if (!hasFolding(curbuf->b_ml.ml_line_count, &curwin->w_cursor.lnum, NULL)) { curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count; @@ -351,9 +328,7 @@ void check_cursor_lnum(void) } } -/* - * Make sure curwin->w_cursor.col is valid. - */ +/// Make sure curwin->w_cursor.col is valid. void check_cursor_col(void) { check_cursor_col_win(curwin); @@ -363,21 +338,21 @@ void check_cursor_col(void) /// @see mb_check_adjust_col 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)); + colnr_T len = (colnr_T)STRLEN(ml_get_buf(win->w_buffer, win->w_cursor.lnum, false)); if (len == 0) { win->w_cursor.col = 0; } else if (win->w_cursor.col >= len) { - /* Allow cursor past end-of-line when: - * - in Insert mode or restarting Insert mode - * - in Visual mode and 'selection' isn't "old" - * - 'virtualedit' is set */ + // Allow cursor past end-of-line when: + // - in Insert mode or restarting Insert mode + // - in Visual mode and 'selection' isn't "old" + // - '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 +369,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; @@ -417,32 +392,27 @@ void check_cursor_col_win(win_T *win) } } -/* - * make sure curwin->w_cursor in on a valid character - */ +/// Make sure curwin->w_cursor in on a valid character void check_cursor(void) { check_cursor_lnum(); check_cursor_col(); } -/* - * Make sure curwin->w_cursor is not on the NUL at the end of the line. - * Allow it when in Visual mode and 'selection' is not "old". - */ +/// Make sure curwin->w_cursor is not on the NUL at the end of the line. +/// Allow it when in Visual mode and 'selection' is not "old". void adjust_cursor_col(void) { if (curwin->w_cursor.col > 0 && (!VIsual_active || *p_sel == 'o') && gchar_cursor() == NUL) { - --curwin->w_cursor.col; + curwin->w_cursor.col--; } } -/* - * When curwin->w_leftcol has changed, adjust the cursor position. - * Return true if the cursor was moved. - */ +/// When curwin->w_leftcol has changed, adjust the cursor position. +/// +/// @return true if the cursor was moved. bool leftcol_changed(void) { // TODO(hinidu): I think it should be colnr_T or int, but p_siso is long. @@ -455,10 +425,8 @@ bool leftcol_changed(void) lastcol = curwin->w_leftcol + curwin->w_width_inner - curwin_col_off() - 1; validate_virtcol(); - /* - * If the cursor is right or left of the screen, move it to last or first - * character. - */ + // If the cursor is right or left of the screen, move it to last or first + // character. if (curwin->w_virtcol > (colnr_T)(lastcol - p_siso)) { retval = true; coladvance((colnr_T)(lastcol - p_siso)); @@ -467,11 +435,9 @@ bool leftcol_changed(void) coladvance((colnr_T)(curwin->w_leftcol + p_siso)); } - /* - * If the start of the character under the cursor is not on the screen, - * advance the cursor one more char. If this fails (last char of the - * line) adjust the scrolling. - */ + // If the start of the character under the cursor is not on the screen, + // advance the cursor one more char. If this fails (last char of the + // line) adjust the scrolling. getvvcol(curwin, &curwin->w_cursor, &s, NULL, &e); if (e > (colnr_T)lastcol) { retval = true; @@ -496,27 +462,21 @@ int gchar_cursor(void) return utf_ptr2char(get_cursor_pos_ptr()); } -/* - * Write a character at the current cursor position. - * It is directly written into the block. - */ +/// Write a character at the current cursor position. +/// It is directly written into the block. void pchar_cursor(char_u c) { *(ml_get_buf(curbuf, curwin->w_cursor.lnum, true) + curwin->w_cursor.col) = c; } -/* - * Return pointer to cursor line. - */ +/// @return pointer to cursor line. char_u *get_cursor_line_ptr(void) { return ml_get_buf(curbuf, curwin->w_cursor.lnum, false); } -/* - * Return pointer to cursor position. - */ +/// @return pointer to cursor position. char_u *get_cursor_pos_ptr(void) { return ml_get_buf(curbuf, curwin->w_cursor.lnum, false) + diff --git a/src/nvim/cursor_shape.c b/src/nvim/cursor_shape.c index 6b0a5dfe12..0e4a4bcfb0 100644 --- a/src/nvim/cursor_shape.c +++ b/src/nvim/cursor_shape.c @@ -9,8 +9,8 @@ #include "nvim/charset.h" #include "nvim/cursor_shape.h" #include "nvim/ex_getln.h" +#include "nvim/highlight_group.h" #include "nvim/strings.h" -#include "nvim/syntax.h" #include "nvim/ui.h" #include "nvim/vim.h" @@ -74,8 +74,7 @@ Array mode_style_array(void) PUT(dic, "hl_id", INTEGER_OBJ(cur->id)); PUT(dic, "id_lm", INTEGER_OBJ(cur->id_lm)); PUT(dic, "attr_id", INTEGER_OBJ(cur->id ? syn_id2attr(cur->id) : 0)); - PUT(dic, "attr_id_lm", INTEGER_OBJ(cur->id_lm ? syn_id2attr(cur->id_lm) - : 0)); + PUT(dic, "attr_id_lm", INTEGER_OBJ(cur->id_lm ? syn_id2attr(cur->id_lm) : 0)); } PUT(dic, "name", STRING_OBJ(cstr_to_string(cur->full_name))); PUT(dic, "short_name", STRING_OBJ(cstr_to_string(cur->name))); @@ -95,7 +94,6 @@ Array mode_style_array(void) /// @returns error message for an illegal option, NULL otherwise. char *parse_shape_opt(int what) { - char_u *modep; char_u *colonp; char_u *commap; char_u *slashp; @@ -120,7 +118,7 @@ char *parse_shape_opt(int what) } } // Repeat for all comma separated parts. - modep = p_guicursor; + char_u *modep = p_guicursor; while (modep != NULL && *modep != NUL) { colonp = vim_strchr(modep, ':'); commap = vim_strchr(modep, ','); @@ -147,7 +145,7 @@ char *parse_shape_opt(int what) if (len == 1 && TOLOWER_ASC(modep[0]) == 'a') { all_idx = SHAPE_IDX_COUNT - 1; } else { - for (idx = 0; idx < SHAPE_IDX_COUNT; ++idx) { + for (idx = 0; idx < SHAPE_IDX_COUNT; idx++) { if (STRNICMP(modep, shape_table[idx].name, len) == 0) { break; } @@ -170,9 +168,7 @@ char *parse_shape_opt(int what) // Parse the part after the colon for (p = colonp + 1; *p && *p != ',';) { { - /* - * First handle the ones with a number argument. - */ + // First handle the ones with a number argument. i = *p; len = 0; if (STRNICMP(p, "ver", 3) == 0) { @@ -230,11 +226,11 @@ char *parse_shape_opt(int what) slashp = vim_strchr(p, '/'); if (slashp != NULL && slashp < endp) { // "group/langmap_group" - i = syn_check_group((char *)p, (int)(slashp - p)); + i = syn_check_group((char *)p, (size_t)(slashp - p)); p = slashp + 1; } if (round == 2) { - shape_table[idx].id = syn_check_group((char *)p, (int)(endp - p)); + shape_table[idx].id = syn_check_group((char *)p, (size_t)(endp - p)); shape_table[idx].id_lm = shape_table[idx].id; if (slashp != NULL && slashp < endp) { shape_table[idx].id = i; @@ -245,7 +241,7 @@ char *parse_shape_opt(int what) } // if (what != SHAPE_MOUSE) if (*p == '-') { - ++p; + p++; } } } diff --git a/src/nvim/debugger.c b/src/nvim/debugger.c index b6e35f3047..cd823546b4 100644 --- a/src/nvim/debugger.c +++ b/src/nvim/debugger.c @@ -58,7 +58,6 @@ void do_debug(char_u *cmd) tasave_T typeaheadbuf; bool typeahead_saved = false; int save_ignore_script = 0; - int save_ex_normal_busy; int n; char_u *cmdline = NULL; char_u *p; @@ -117,7 +116,7 @@ void do_debug(char_u *cmd) // with the commands being executed. Reset "ex_normal_busy" to avoid // the side effects of using ":normal". Save the stuff buffer and make // it empty. Set ignore_script to avoid reading from script input. - save_ex_normal_busy = ex_normal_busy; + int save_ex_normal_busy = ex_normal_busy; ex_normal_busy = 0; if (!debug_greedy) { save_typeahead(&typeaheadbuf); @@ -201,7 +200,7 @@ void do_debug(char_u *cmd) if (last_cmd != 0) { // Check that the tail matches. p++; - while (*p != NUL && *p == *tail) { + while (*p != NUL && *p == (char_u)(*tail)) { p++; tail++; } @@ -268,7 +267,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 +276,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; @@ -390,11 +389,10 @@ static char_u *debug_skipped_name; /// Called from do_one_cmd() before executing a command. void dbg_check_breakpoint(exarg_T *eap) { - char *p; - debug_skipped = false; if (debug_breakpoint_name != NULL) { if (!eap->skip) { + char *p; // replace K_SNR with "<SNR>" if (debug_breakpoint_name[0] == K_SPECIAL && debug_breakpoint_name[1] == KS_EXTRA @@ -430,12 +428,10 @@ void dbg_check_breakpoint(exarg_T *eap) /// @return true when the debug mode is entered this time. bool dbg_check_skipped(exarg_T *eap) { - int prev_got_int; - if (debug_skipped) { // Save the value of got_int and reset it. We don't want a previous // interruption cause flushing the input buffer. - prev_got_int = got_int; + int prev_got_int = got_int; got_int = false; debug_breakpoint_name = debug_skipped_name; // eap->skip is true @@ -482,12 +478,11 @@ static int dbg_parsearg(char_u *arg, garray_T *gap) { char_u *p = arg; char_u *q; - struct debuggy *bp; bool here = false; ga_grow(gap, 1); - bp = &DEBUGGY(gap, gap->ga_len); + struct debuggy *bp = &DEBUGGY(gap, gap->ga_len); // Find "func" or "file". if (STRNCMP(p, "func", 4) == 0) { @@ -564,16 +559,13 @@ static int dbg_parsearg(char_u *arg, garray_T *gap) /// ":breakadd". Also used for ":profile". void ex_breakadd(exarg_T *eap) { - struct debuggy *bp; - garray_T *gap; - - gap = &dbg_breakp; + garray_T *gap = &dbg_breakp; if (eap->cmdidx == CMD_profile) { gap = &prof_ga; } if (dbg_parsearg(eap->arg, gap) == OK) { - bp = &DEBUGGY(gap, gap->ga_len); + struct debuggy *bp = &DEBUGGY(gap, gap->ga_len); bp->dbg_forceit = eap->forceit; if (bp->dbg_type != DBG_EXPR) { @@ -616,20 +608,18 @@ void ex_debuggreedy(exarg_T *eap) void ex_breakdel(exarg_T *eap) { struct debuggy *bp, *bpi; - int nr; int todel = -1; bool del_all = false; linenr_T best_lnum = 0; - garray_T *gap; + garray_T *gap = &dbg_breakp; - gap = &dbg_breakp; if (eap->cmdidx == CMD_profdel) { gap = &prof_ga; } if (ascii_isdigit(*eap->arg)) { // ":breakdel {nr}" - nr = atoi((char *)eap->arg); + int nr = atoi((char *)eap->arg); for (int i = 0; i < gap->ga_len; i++) { if (DEBUGGY(gap, i).dbg_nr == nr) { todel = i; @@ -693,13 +683,11 @@ void ex_breakdel(exarg_T *eap) /// ":breaklist". void ex_breaklist(exarg_T *eap) { - struct debuggy *bp; - if (GA_EMPTY(&dbg_breakp)) { msg(_("No breakpoints defined")); } else { for (int i = 0; i < dbg_breakp.ga_len; i++) { - bp = &BREAKP(i); + struct debuggy *bp = &BREAKP(i); if (bp->dbg_type == DBG_FILE) { home_replace(NULL, bp->dbg_name, NameBuff, MAXPATHL, true); } diff --git a/src/nvim/decoration.c b/src/nvim/decoration.c index c0f3c32f93..a0f189ca38 100644 --- a/src/nvim/decoration.c +++ b/src/nvim/decoration.c @@ -1,20 +1,20 @@ // 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/buffer.h" #include "nvim/decoration.h" #include "nvim/extmark.h" #include "nvim/highlight.h" +#include "nvim/highlight_group.h" #include "nvim/lua/executor.h" +#include "nvim/move.h" #include "nvim/screen.h" -#include "nvim/syntax.h" #include "nvim/vim.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # 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 +33,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 +59,25 @@ 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); + 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_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; - } - - 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) { - redraw_buf_range_later(buf, row1+1, row2+1); + if (row2 >= row1) { + if (!decor || decor->hl_id || decor_has_sign(decor)) { + 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,22 +85,32 @@ 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)) { - assert(buf->b_virt_line_blocks > 0); - buf->b_virt_line_blocks--; - } decor_redraw(buf, row, row2, decor); + if (decor) { + if (kv_size(decor->virt_lines)) { + assert(buf->b_virt_line_blocks > 0); + buf->b_virt_line_blocks--; + } + if (decor_has_sign(decor)) { + assert(buf->b_signs > 0); + buf->b_signs--; + } + if (row2 >= row && decor->sign_text) { + buf_signcols_del_check(buf, row+1, row2+1); + } + } 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); } kv_destroy(decor->virt_lines); + xfree(decor->sign_text); xfree(decor); } } @@ -134,17 +129,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 +157,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 +183,44 @@ 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) { + Decoration decor = get_decor(mark); + + // Exclude non-paired marks unless they contain virt_text or a sign + if (!mt_paired(mark) + && !kv_size(decor.virt_text) + && !decor_has_sign(&decor)) { goto next_mark; } - Decoration *decor = item->decor; - 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)) { + // Exclude start marks if the end mark position is above the top row + // Exclude end marks if we have already added the start mark + if ((mt_start(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 +275,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); @@ -310,6 +312,10 @@ next_mark: int attr = 0; size_t j = 0; + bool conceal = 0; + int conceal_char = 0; + int conceal_attr = 0; + for (size_t i = 0; i < kv_size(state->active); i++) { DecorRange item = kv_A(state->active, i); bool active = false, keep = true; @@ -334,6 +340,14 @@ next_mark: if (active && item.attr_id > 0) { attr = hl_combine_attr(attr, item.attr_id); } + if (active && item.decor.conceal) { + conceal = true; + if (item.start_row == state->row && item.start_col == col && item.decor.conceal_char) { + conceal_char = item.decor.conceal_char; + state->col_until = MIN(state->col_until, item.start_col); + conceal_attr = item.attr_id; + } + } if ((item.start_row == state->row && item.start_col <= col) && kv_size(item.decor.virt_text) && item.decor.virt_text_pos == kVTOverlay && item.win_col == -1) { @@ -347,9 +361,150 @@ next_mark: } kv_size(state->active) = j; state->current = attr; + state->conceal = conceal; + state->conceal_char = conceal_char; + state->conceal_attr = conceal_attr; return attr; } +void decor_redraw_signs(buf_T *buf, int row, int *num_signs, sign_attrs_T sattrs[]) +{ + if (!buf->b_signs) { + return; + } + + MarkTreeIter itr[1] = { 0 }; + marktree_itr_get(buf->b_marktree, row, 0, itr); + + while (true) { + mtkey_t mark = marktree_itr_current(itr); + if (mark.pos.row < 0 || mark.pos.row > row) { + break; + } + + if (mt_end(mark) || marktree_decor_level(mark) < kDecorLevelVisible) { + goto next_mark; + } + + Decoration *decor = mark.decor_full; + + if (!decor || !decor_has_sign(decor)) { + goto next_mark; + } + + int j; + for (j = (*num_signs); j > 0; j--) { + if (sattrs[j].sat_prio <= decor->priority) { + break; + } + sattrs[j] = sattrs[j-1]; + } + if (j < SIGN_SHOW_MAX) { + memset(&sattrs[j], 0, sizeof(sign_attrs_T)); + sattrs[j].sat_text = decor->sign_text; + if (decor->sign_hl_id != 0) { + sattrs[j].sat_texthl = syn_id2attr(decor->sign_hl_id); + } + if (decor->number_hl_id != 0) { + sattrs[j].sat_numhl = syn_id2attr(decor->number_hl_id); + } + if (decor->line_hl_id != 0) { + sattrs[j].sat_linehl = syn_id2attr(decor->line_hl_id); + } + if (decor->cursorline_hl_id != 0) { + sattrs[j].sat_culhl = syn_id2attr(decor->cursorline_hl_id); + } + sattrs[j].sat_prio = decor->priority; + (*num_signs)++; + } + +next_mark: + marktree_itr_next(buf->b_marktree, itr); + } +} + +// Get the maximum required amount of sign columns needed between row and +// end_row. +int decor_signcols(buf_T *buf, DecorState *state, int row, int end_row, int max) +{ + int count = 0; // count for the number of signs on a given row + int count_remove = 0; // how much to decrement count by when iterating marks for a new row + int signcols = 0; // highest value of count + int currow = -1; // current row + + if (max <= 1 && buf->b_signs >= (size_t)max) { + return max; + } + + if (buf->b_signs == 0) { + return 0; + } + + MarkTreeIter itr[1] = { 0 }; + marktree_itr_get(buf->b_marktree, 0, -1, itr); + while (true) { + mtkey_t mark = marktree_itr_current(itr); + if (mark.pos.row < 0 || mark.pos.row > end_row) { + break; + } + + if ((mark.pos.row < row && mt_end(mark)) + || marktree_decor_level(mark) < kDecorLevelVisible + || !mark.decor_full) { + goto next_mark; + } + + Decoration decor = get_decor(mark); + + if (!decor.sign_text) { + goto next_mark; + } + + if (mark.pos.row > currow) { + count -= count_remove; + count_remove = 0; + currow = mark.pos.row; + } + + if (!mt_paired(mark)) { + if (mark.pos.row >= row) { + count++; + if (count > signcols) { + signcols = count; + if (signcols >= max) { + return max; + } + } + count_remove++; + } + goto next_mark; + } + + mtpos_t altpos = marktree_get_altpos(buf->b_marktree, mark, NULL); + + if (mt_end(mark)) { + if (mark.pos.row >= row && altpos.row <= end_row) { + count_remove++; + } + } else { + if (altpos.row >= row) { + count++; + if (count > signcols) { + signcols = count; + if (signcols >= max) { + return max; + } + } + } + } + +next_mark: + marktree_itr_next(buf->b_marktree, itr); + } + + return signcols; +} + void decor_redraw_end(DecorState *state) { state->buf = NULL; @@ -383,59 +538,6 @@ void decor_add_ephemeral(int start_row, int start_col, int end_row, int end_col, } -DecorProvider *get_decor_provider(NS ns_id, bool force) -{ - size_t i; - size_t len = kv_size(decor_providers); - for (i = 0; i < len; i++) { - DecorProvider *item = &kv_A(decor_providers, i); - if (item->ns_id == ns_id) { - return item; - } else if (item->ns_id > ns_id) { - break; - } - } - - if (!force) { - return NULL; - } - - // Adding a new provider, so allocate room in the vector - (void)kv_a(decor_providers, len); - if (i < len) { - // New ns_id needs to be inserted between existing providers to maintain - // ordering, so shift other providers with larger ns_id - memmove(&kv_A(decor_providers, i + 1), - &kv_A(decor_providers, i), - (len - i) * sizeof(kv_a(decor_providers, i))); - } - DecorProvider *item = &kv_a(decor_providers, i); - *item = DECORATION_PROVIDER_INIT(ns_id); - - return item; -} - -void decor_provider_clear(DecorProvider *p) -{ - if (p == NULL) { - return; - } - NLUA_CLEAR_REF(p->redraw_start); - NLUA_CLEAR_REF(p->redraw_buf); - NLUA_CLEAR_REF(p->redraw_win); - NLUA_CLEAR_REF(p->redraw_line); - NLUA_CLEAR_REF(p->redraw_end); - p->active = false; -} - -void decor_free_all_mem(void) -{ - for (size_t i = 0; i < kv_size(decor_providers); i++) { - decor_provider_clear(&kv_A(decor_providers, i)); - } - kv_destroy(decor_providers); -} - int decor_virt_lines(win_T *wp, linenr_T lnum, VirtLines *lines) { @@ -452,18 +554,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..97ab218f86 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,15 +46,24 @@ 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 + bool conceal; + // TODO(bfredl): style, etc DecorPriority priority; int col; // fixed col value, like win_col int virt_text_width; // width of virt_text + char_u *sign_text; + int sign_hl_id; + int number_hl_id; + int line_hl_id; + int cursorline_hl_id; + // TODO(bfredl): in principle this should be a schar_T, but we + // probably want some kind of glyph cache for that.. + int conceal_char; }; -#define DECORATION_INIT { KV_INITIAL_VALUE, KV_INITIAL_VALUE, 0, kVTEndOfLine, kHlModeUnknown, \ - false, false, false, false, DECOR_PRIORITY_BASE, 0, 0 } +#define DECORATION_INIT { KV_INITIAL_VALUE, KV_INITIAL_VALUE, 0, kVTEndOfLine, \ + kHlModeUnknown, false, false, false, false, DECOR_PRIORITY_BASE, \ + 0, 0, NULL, 0, 0, 0, 0, 0 } typedef struct { int start_row; @@ -72,28 +85,22 @@ typedef struct { int col_until; int current; int eol_col; + + bool conceal; + int conceal_char; + int conceal_attr; } DecorState; -typedef struct { - NS ns_id; - bool active; - LuaRef redraw_start; - LuaRef redraw_buf; - LuaRef redraw_win; - LuaRef redraw_line; - LuaRef redraw_end; - LuaRef hl_def; - int hl_valid; -} DecorProvider; - -EXTERN kvec_t(DecorProvider) decor_providers INIT(= KV_INITIAL_VALUE); EXTERN DecorState decor_state INIT(= { 0 }); -EXTERN bool provider_active INIT(= false); -#define DECORATION_PROVIDER_INIT(ns_id) (DecorProvider) \ - { ns_id, false, LUA_NOREF, LUA_NOREF, \ - LUA_NOREF, LUA_NOREF, LUA_NOREF, \ - LUA_NOREF, -1 } +static inline bool decor_has_sign(Decoration *decor) +{ + return decor->sign_text + || decor->sign_hl_id + || decor->number_hl_id + || decor->line_hl_id + || decor->cursorline_hl_id; +} #ifdef INCLUDE_GENERATED_DECLARATIONS # include "decoration.h.generated.h" diff --git a/src/nvim/decoration_provider.c b/src/nvim/decoration_provider.c new file mode 100644 index 0000000000..6efcb08b9d --- /dev/null +++ b/src/nvim/decoration_provider.c @@ -0,0 +1,231 @@ +// 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/api/extmark.h" +#include "nvim/api/private/helpers.h" +#include "nvim/buffer.h" +#include "nvim/decoration.h" +#include "nvim/decoration_provider.h" +#include "nvim/highlight.h" +#include "nvim/lua/executor.h" + +static kvec_t(DecorProvider) decor_providers = KV_INITIAL_VALUE; + +#define DECORATION_PROVIDER_INIT(ns_id) (DecorProvider) \ + { ns_id, false, LUA_NOREF, LUA_NOREF, \ + LUA_NOREF, LUA_NOREF, LUA_NOREF, \ + LUA_NOREF, -1 } + +static bool decor_provider_invoke(NS ns_id, const char *name, LuaRef ref, + Array args, bool default_true, char **perr) +{ + Error err = ERROR_INIT; + + textlock++; + provider_active = true; + Object ret = nlua_call_ref(ref, name, args, true, &err); + provider_active = false; + textlock--; + + if (!ERROR_SET(&err) + && api_object_to_bool(ret, "provider %s retval", default_true, &err)) { + return true; + } + + if (ERROR_SET(&err)) { + const char *ns_name = describe_ns(ns_id); + ELOG("error in provider %s:%s: %s", ns_name, name, err.msg); + bool verbose_errs = true; // TODO(bfredl): + if (verbose_errs && perr && *perr == NULL) { + static char errbuf[IOSIZE]; + snprintf(errbuf, sizeof errbuf, "%s: %s", ns_name, err.msg); + *perr = xstrdup(errbuf); + } + } + + api_free_object(ret); + return false; +} + +/// For each provider invoke the 'start' callback +/// +/// @param[out] providers Decoration providers +/// @param[out] err Provider err +void decor_providers_start(DecorProviders *providers, int type, char **err) +{ + kvi_init(*providers); + + for (size_t i = 0; i < kv_size(decor_providers); i++) { + DecorProvider *p = &kv_A(decor_providers, i); + if (!p->active) { + continue; + } + + bool active; + if (p->redraw_start != LUA_NOREF) { + FIXED_TEMP_ARRAY(args, 2); + args.items[0] = INTEGER_OBJ((int)display_tick); + args.items[1] = INTEGER_OBJ(type); + active = decor_provider_invoke(p->ns_id, "start", p->redraw_start, args, true, err); + } else { + active = true; + } + + if (active) { + kvi_push(*providers, p); + } + } +} + +/// For each provider run 'win'. If result is not false, then collect the +/// 'on_line' callback to call inside win_line +/// +/// @param wp Window +/// @param providers Decoration providers +/// @param[out] line_providers Enabled line providers to invoke in win_line +/// @param[out] err Provider error +void decor_providers_invoke_win(win_T *wp, DecorProviders *providers, + DecorProviders *line_providers, char **err) +{ + kvi_init(*line_providers); + + linenr_T knownmax = ((wp->w_valid & VALID_BOTLINE) + ? wp->w_botline + : (wp->w_topline + wp->w_height_inner)); + + for (size_t k = 0; k < kv_size(*providers); k++) { + DecorProvider *p = kv_A(*providers, k); + if (p && p->redraw_win != LUA_NOREF) { + FIXED_TEMP_ARRAY(args, 4); + args.items[0] = WINDOW_OBJ(wp->handle); + args.items[1] = BUFFER_OBJ(wp->w_buffer->handle); + // TODO(bfredl): we are not using this, but should be first drawn line? + args.items[2] = INTEGER_OBJ(wp->w_topline-1); + args.items[3] = INTEGER_OBJ(knownmax); + if (decor_provider_invoke(p->ns_id, "win", p->redraw_win, args, true, err)) { + kvi_push(*line_providers, p); + } + } + } + + win_check_ns_hl(wp); +} + +/// For each provider invoke the 'line' callback for a given window row. +/// +/// @param wp Window +/// @param providers Decoration providers +/// @param row Row to invoke line callback for +/// @param[out] has_decor Set when at least one provider invokes a line callback +/// @param[out] err Provider error +void providers_invoke_line(win_T *wp, DecorProviders *providers, int row, bool *has_decor, + char **err) +{ + for (size_t k = 0; k < kv_size(*providers); k++) { + DecorProvider *p = kv_A(*providers, k); + if (p && p->redraw_line != LUA_NOREF) { + FIXED_TEMP_ARRAY(args, 3); + args.items[0] = WINDOW_OBJ(wp->handle); + args.items[1] = BUFFER_OBJ(wp->w_buffer->handle); + args.items[2] = INTEGER_OBJ(row); + if (decor_provider_invoke(p->ns_id, "line", p->redraw_line, args, true, err)) { + *has_decor = true; + } else { + // return 'false' or error: skip rest of this window + kv_A(*providers, k) = NULL; + } + + win_check_ns_hl(wp); + } + } +} + + +/// For each provider invoke the 'buf' callback for a given buffer. +/// +/// @param buf Buffer +/// @param providers Decoration providers +/// @param[out] err Provider error +void decor_providers_invoke_buf(buf_T *buf, DecorProviders *providers, char **err) +{ + for (size_t i = 0; i < kv_size(*providers); i++) { + DecorProvider *p = kv_A(*providers, i); + if (p && p->redraw_buf != LUA_NOREF) { + FIXED_TEMP_ARRAY(args, 1); + args.items[0] = BUFFER_OBJ(buf->handle); + decor_provider_invoke(p->ns_id, "buf", p->redraw_buf, args, true, err); + } + } +} + + +/// For each provider invoke the 'end' callback +/// +/// @param providers Decoration providers +/// @param displaytick Display tick +/// @param[out] err Provider error +void decor_providers_invoke_end(DecorProviders *providers, char **err) +{ + for (size_t i = 0; i < kv_size(*providers); i++) { + DecorProvider *p = kv_A(*providers, i); + if (p && p->active && p->redraw_end != LUA_NOREF) { + FIXED_TEMP_ARRAY(args, 1); + args.items[0] = INTEGER_OBJ((int)display_tick); + decor_provider_invoke(p->ns_id, "end", p->redraw_end, args, true, err); + } + } +} + +DecorProvider *get_decor_provider(NS ns_id, bool force) +{ + size_t i; + size_t len = kv_size(decor_providers); + for (i = 0; i < len; i++) { + DecorProvider *item = &kv_A(decor_providers, i); + if (item->ns_id == ns_id) { + return item; + } else if (item->ns_id > ns_id) { + break; + } + } + + if (!force) { + return NULL; + } + + // Adding a new provider, so allocate room in the vector + (void)kv_a(decor_providers, len); + if (i < len) { + // New ns_id needs to be inserted between existing providers to maintain + // ordering, so shift other providers with larger ns_id + memmove(&kv_A(decor_providers, i + 1), + &kv_A(decor_providers, i), + (len - i) * sizeof(kv_a(decor_providers, i))); + } + DecorProvider *item = &kv_a(decor_providers, i); + *item = DECORATION_PROVIDER_INIT(ns_id); + + return item; +} + +void decor_provider_clear(DecorProvider *p) +{ + if (p == NULL) { + return; + } + NLUA_CLEAR_REF(p->redraw_start); + NLUA_CLEAR_REF(p->redraw_buf); + NLUA_CLEAR_REF(p->redraw_win); + NLUA_CLEAR_REF(p->redraw_line); + NLUA_CLEAR_REF(p->redraw_end); + p->active = false; +} + +void decor_free_all_mem(void) +{ + for (size_t i = 0; i < kv_size(decor_providers); i++) { + decor_provider_clear(&kv_A(decor_providers, i)); + } + kv_destroy(decor_providers); +} + diff --git a/src/nvim/decoration_provider.h b/src/nvim/decoration_provider.h new file mode 100644 index 0000000000..3ec7c80357 --- /dev/null +++ b/src/nvim/decoration_provider.h @@ -0,0 +1,26 @@ +#ifndef NVIM_DECORATION_PROVIDER_H +#define NVIM_DECORATION_PROVIDER_H + +#include "nvim/buffer_defs.h" + +typedef struct { + NS ns_id; + bool active; + LuaRef redraw_start; + LuaRef redraw_buf; + LuaRef redraw_win; + LuaRef redraw_line; + LuaRef redraw_end; + LuaRef hl_def; + int hl_valid; +} DecorProvider; + +typedef kvec_withinit_t(DecorProvider *, 4) DecorProviders; + +EXTERN bool provider_active INIT(= false); + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "decoration_provider.h.generated.h" +#endif + +#endif // NVIM_DECORATION_PROVIDER_H diff --git a/src/nvim/diff.c b/src/nvim/diff.c index 1f8acd8c79..0b55fb877c 100644 --- a/src/nvim/diff.c +++ b/src/nvim/diff.c @@ -29,7 +29,6 @@ #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/message.h" -#include "nvim/misc1.h" #include "nvim/move.h" #include "nvim/normal.h" #include "nvim/option.h" @@ -38,6 +37,7 @@ #include "nvim/path.h" #include "nvim/screen.h" #include "nvim/strings.h" +#include "nvim/ui.h" #include "nvim/undo.h" #include "nvim/vim.h" #include "nvim/window.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 @@ -158,8 +166,7 @@ void diff_buf_add(buf_T *buf) return; } - int i; - for (i = 0; i < DB_COUNT; ++i) { + for (int i = 0; i < DB_COUNT; i++) { if (curtab->tp_diffbuf[i] == NULL) { curtab->tp_diffbuf[i] = buf; curtab->tp_diff_invalid = true; @@ -193,7 +200,7 @@ static void diff_buf_clear(void) static int diff_buf_idx(buf_T *buf) { int idx; - for (idx = 0; idx < DB_COUNT; ++idx) { + for (idx = 0; idx < DB_COUNT; idx++) { if (curtab->tp_diffbuf[idx] == buf) { break; } @@ -210,7 +217,7 @@ static int diff_buf_idx(buf_T *buf) static int diff_buf_idx_tp(buf_T *buf, tabpage_T *tp) { int idx; - for (idx = 0; idx < DB_COUNT; ++idx) { + for (idx = 0; idx < DB_COUNT; idx++) { if (tp->tp_diffbuf[idx] == buf) { break; } @@ -296,7 +303,6 @@ static void diff_mark_adjust_tp(tabpage_T *tp, int idx, linenr_T line1, linenr_T diff_T *dprev = NULL; diff_T *dp = tp->tp_first_diff; - linenr_T last; linenr_T lnum_deleted = line1; // lnum of remaining deletion int n; int off; @@ -315,7 +321,7 @@ static void diff_mark_adjust_tp(tabpage_T *tp, int idx, linenr_T line1, linenr_T dnext->df_lnum[idx] = line1; dnext->df_count[idx] = inserted; int i; - for (i = 0; i < DB_COUNT; ++i) { + for (i = 0; i < DB_COUNT; i++) { if ((tp->tp_diffbuf[i] != NULL) && (i != idx)) { if (dprev == NULL) { dnext->df_lnum[i] = line1; @@ -346,7 +352,7 @@ static void diff_mark_adjust_tp(tabpage_T *tp, int idx, linenr_T line1, linenr_T // 3 5 6 // compute last line of this change - last = dp->df_lnum[idx] + dp->df_count[idx] - 1; + linenr_T last = dp->df_lnum[idx] + dp->df_count[idx] - 1; // 1. change completely above line1: nothing to do if (last >= line1 - 1) { @@ -413,7 +419,7 @@ static void diff_mark_adjust_tp(tabpage_T *tp, int idx, linenr_T line1, linenr_T } int i; - for (i = 0; i < DB_COUNT; ++i) { + for (i = 0; i < DB_COUNT; i++) { if ((tp->tp_diffbuf[i] != NULL) && (i != idx)) { dp->df_lnum[i] -= off; dp->df_count[i] += n; @@ -434,7 +440,7 @@ static void diff_mark_adjust_tp(tabpage_T *tp, int idx, linenr_T line1, linenr_T // Check if inserted lines are equal, may reduce the size of the // diff. // - // TODO: also check for equal lines in the middle and perhaps split + // TODO(unknown): also check for equal lines in the middle and perhaps split // the block. diff_check_unchanged(tp, dp); } @@ -445,7 +451,7 @@ static void diff_mark_adjust_tp(tabpage_T *tp, int idx, linenr_T line1, linenr_T if ((dprev != NULL) && (dprev->df_lnum[idx] + dprev->df_count[idx] == dp->df_lnum[idx])) { int i; - for (i = 0; i < DB_COUNT; ++i) { + for (i = 0; i < DB_COUNT; i++) { if (tp->tp_diffbuf[i] != NULL) { dprev->df_count[i] += dp->df_count[i]; } @@ -466,7 +472,7 @@ static void diff_mark_adjust_tp(tabpage_T *tp, int idx, linenr_T line1, linenr_T while (dp != NULL) { // All counts are zero, remove this entry. int i; - for (i = 0; i < DB_COUNT; ++i) { + for (i = 0; i < DB_COUNT; i++) { if ((tp->tp_diffbuf[i] != NULL) && (dp->df_count[i] != 0)) { break; } @@ -534,7 +540,7 @@ static void diff_check_unchanged(tabpage_T *tp, diff_T *dp) // Find the first buffers, use it as the original, compare the other // buffer lines against this one. int i_org; - for (i_org = 0; i_org < DB_COUNT; ++i_org) { + for (i_org = 0; i_org < DB_COUNT; i_org++) { if (tp->tp_diffbuf[i_org] != NULL) { break; } @@ -566,7 +572,7 @@ static void diff_check_unchanged(tabpage_T *tp, diff_T *dp) false)); int i_new; - for (i_new = i_org + 1; i_new < DB_COUNT; ++i_new) { + for (i_new = i_org + 1; i_new < DB_COUNT; i_new++) { if (tp->tp_diffbuf[i_new] == NULL) { continue; } @@ -594,7 +600,7 @@ static void diff_check_unchanged(tabpage_T *tp, diff_T *dp) } // Line matched in all buffers, remove it from the diff. - for (i_new = i_org; i_new < DB_COUNT; ++i_new) { + for (i_new = i_org; i_new < DB_COUNT; i_new++) { if (tp->tp_diffbuf[i_new] != NULL) { if (dir == FORWARD) { dp->df_lnum[i_new]++; @@ -620,8 +626,7 @@ static void diff_check_unchanged(tabpage_T *tp, diff_T *dp) /// @return OK if the diff block doesn't contain invalid line numbers. static int diff_check_sanity(tabpage_T *tp, diff_T *dp) { - int i; - for (i = 0; i < DB_COUNT; ++i) { + for (int i = 0; i < DB_COUNT; i++) { if (tp->tp_diffbuf[i] != NULL) { if (dp->df_lnum[i] + dp->df_count[i] - 1 > tp->tp_diffbuf[i]->b_ml.ml_line_count) { @@ -674,11 +679,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); } @@ -711,16 +716,13 @@ static void clear_diffout(diffout_T *dout) /// @return FAIL for failure. static int diff_write_buffer(buf_T *buf, diffin_T *din) { - linenr_T lnum; - char_u *s; long len = 0; - char_u *ptr; // xdiff requires one big block of memory with all the text. - for (lnum = 1; lnum <= buf->b_ml.ml_line_count; lnum++) { + for (linenr_T lnum = 1; lnum <= buf->b_ml.ml_line_count; lnum++) { len += (long)STRLEN(ml_get_buf(buf, lnum, false)) + 1; } - ptr = try_malloc(len); + char_u *ptr = try_malloc(len); if (ptr == NULL) { // Allocating memory failed. This can happen, because we try to read // the whole buffer text into memory. Set the failed flag, the diff @@ -738,14 +740,19 @@ static int diff_write_buffer(buf_T *buf, diffin_T *din) din->din_mmfile.size = len; len = 0; - for (lnum = 1; lnum <= buf->b_ml.ml_line_count; lnum++) { - for (s = ml_get_buf(buf, lnum, false); *s != NUL;) { + for (linenr_T lnum = 1; lnum <= buf->b_ml.ml_line_count; lnum++) { + for (char_u *s = ml_get_buf(buf, lnum, false); *s != NUL;) { if (diff_flags & DIFF_ICASE) { + int c; char_u cbuf[MB_MAXBYTES + 1]; - // xdiff doesn't support ignoring case, fold-case the text. - int c = utf_ptr2char(s); - c = utf_fold(c); + if (*s == NL) { + c = NUL; + } else { + // xdiff doesn't support ignoring case, fold-case the text. + c = utf_ptr2char(s); + c = utf_fold(c); + } const int orig_len = utfc_ptr2len(s); if (utf_char2bytes(c, cbuf) != orig_len) { // TODO(Bram): handle byte length difference @@ -757,7 +764,8 @@ static int diff_write_buffer(buf_T *buf, diffin_T *din) s += orig_len; len += orig_len; } else { - ptr[len++] = *s++; + ptr[len++] = *s == NL ? NUL : *s; + s++; } } ptr[len++] = NL; @@ -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; @@ -798,9 +811,6 @@ static int diff_write(buf_T *buf, diffin_T *din) /// @param eap can be NULL static void diff_try_update(diffio_T *dio, int idx_orig, exarg_T *eap) { - buf_T *buf; - int idx_new; - if (dio->dio_internal) { ga_init(&dio->dio_diff.dout_ga, sizeof(char *), 1000); } else { @@ -820,9 +830,11 @@ static void diff_try_update(diffio_T *dio, int idx_orig, exarg_T *eap) goto theend; } + buf_T *buf; + // :diffupdate! if (eap != NULL && eap->forceit) { - for (idx_new = idx_orig; idx_new < DB_COUNT; idx_new++) { + for (int idx_new = idx_orig; idx_new < DB_COUNT; idx_new++) { buf = curtab->tp_diffbuf[idx_new]; if (buf_valid(buf)) { buf_check_timestamp(buf); @@ -837,7 +849,7 @@ static void diff_try_update(diffio_T *dio, int idx_orig, exarg_T *eap) } // Make a difference between the first buffer and every other. - for (idx_new = idx_orig + 1; idx_new < DB_COUNT; idx_new++) { + for (int idx_new = idx_orig + 1; idx_new < DB_COUNT; idx_new++) { buf = curtab->tp_diffbuf[idx_new]; if (buf == NULL || buf->b_ml.ml_mfp == NULL) { continue; // skip buffer that isn't loaded @@ -852,7 +864,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); @@ -880,10 +892,8 @@ int diff_internal(void) /// static int diff_internal_failed(void) { - int idx; - // Only need to do something when there is another buffer. - for (idx = 0; idx < DB_COUNT; idx++) { + for (int idx = 0; idx < DB_COUNT; idx++) { if (curtab->tp_diffbuf[idx] != NULL && curtab->tp_diffbuf[idx]->b_diff_failed) { return true; @@ -914,7 +924,7 @@ void ex_diffupdate(exarg_T *eap) // Use the first buffer as the original text. int idx_orig; - for (idx_orig = 0; idx_orig < DB_COUNT; ++idx_orig) { + for (idx_orig = 0; idx_orig < DB_COUNT; idx_orig++) { if (curtab->tp_diffbuf[idx_orig] != NULL) { break; } @@ -926,7 +936,7 @@ void ex_diffupdate(exarg_T *eap) // Only need to do something when there is another buffer. int idx_new; - for (idx_new = idx_orig + 1; idx_new < DB_COUNT; ++idx_new) { + for (idx_new = idx_orig + 1; idx_new < DB_COUNT; idx_new++) { if (curtab->tp_diffbuf[idx_new] != NULL) { break; } @@ -1078,7 +1088,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 +1282,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 +1518,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 +1529,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 = NULL; // init to avoid gcc warning enum { DIFF_ED, DIFF_UNIFIED, @@ -1549,70 +1559,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,35 +1641,35 @@ 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) { + for (i = idx_orig; i < idx_new; i++) { if (curtab->tp_diffbuf[i] != NULL) { 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 +1678,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) { @@ -1671,7 +1690,7 @@ static void diff_read(int idx_orig, int idx_new, diffout_T *dout) off = 0; } - for (i = idx_orig; i < idx_new; ++i) { + for (i = idx_orig; i < idx_new; i++) { if (curtab->tp_diffbuf[i] != NULL) { dp->df_count[i] = dpl->df_lnum[i] + dpl->df_count[i] - dp->df_lnum[i] + off; @@ -1691,15 +1710,15 @@ 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 // already. - for (i = idx_orig + 1; i < idx_new; ++i) { + for (i = idx_orig + 1; i < idx_new; i++) { if (curtab->tp_diffbuf[i] != NULL) { diff_copy_entry(dprev, dp, idx_orig, i); } @@ -1718,6 +1737,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); } @@ -1749,9 +1772,8 @@ static void diff_copy_entry(diff_T *dprev, diff_T *dp, int idx_orig, int idx_new void diff_clear(tabpage_T *tp) FUNC_ATTR_NONNULL_ALL { - diff_T *p; diff_T *next_p; - for (p = tp->tp_first_diff; p != NULL; p = next_p) { + for (diff_T *p = tp->tp_first_diff; p != NULL; p = next_p) { next_p = p->df_next; xfree(p); } @@ -1773,12 +1795,8 @@ void diff_clear(tabpage_T *tp) /// @return diff status. int diff_check(win_T *wp, linenr_T lnum) { - int idx; // index in tp_diffbuf[] for this buffer diff_T *dp; - int maxcount; - int i; buf_T *buf = wp->w_buffer; - int cmp; if (curtab->tp_diff_invalid) { // update after a big change @@ -1795,7 +1813,7 @@ int diff_check(win_T *wp, linenr_T lnum) return 0; } - idx = diff_buf_idx(buf); + int idx = diff_buf_idx(buf); // index in tp_diffbuf[] for this buffer if (idx == DB_COUNT) { // no diffs for buffer "buf" @@ -1824,9 +1842,9 @@ int diff_check(win_T *wp, linenr_T lnum) // Changed or inserted line. If the other buffers have a count of // zero, the lines were inserted. If the other buffers have the same // count, check if the lines are identical. - cmp = false; + int cmp = false; - for (i = 0; i < DB_COUNT; ++i) { + for (int i = 0; i < DB_COUNT; i++) { if ((i != idx) && (curtab->tp_diffbuf[i] != NULL)) { if (dp->df_count[i] == 0) { zero = true; @@ -1843,7 +1861,7 @@ int diff_check(win_T *wp, linenr_T lnum) if (cmp) { // Compare all lines. If they are equal the lines were inserted // in some buffers, deleted in others, but not changed. - for (i = 0; i < DB_COUNT; ++i) { + for (int i = 0; i < DB_COUNT; i++) { if ((i != idx) && (curtab->tp_diffbuf[i] != NULL) && (dp->df_count[i] != 0)) { @@ -1872,8 +1890,8 @@ int diff_check(win_T *wp, linenr_T lnum) // Insert filler lines above the line just below the change. Will return // 0 when this buf had the max count. - maxcount = 0; - for (i = 0; i < DB_COUNT; ++i) { + int maxcount = 0; + for (int i = 0; i < DB_COUNT; i++) { if ((curtab->tp_diffbuf[i] != NULL) && (dp->df_count[i] > maxcount)) { maxcount = dp->df_count[i]; } @@ -2004,8 +2022,6 @@ void diff_set_topline(win_T *fromwin, win_T *towin) buf_T *frombuf = fromwin->w_buffer; linenr_T lnum = fromwin->w_topline; diff_T *dp; - int max_count; - int i; int fromidx = diff_buf_idx(frombuf); if (fromidx == DB_COUNT) { @@ -2045,9 +2061,9 @@ void diff_set_topline(win_T *fromwin, win_T *towin) if (lnum >= dp->df_lnum[fromidx]) { // Inside a change: compute filler lines. With three or more // buffers we need to know the largest count. - max_count = 0; + int max_count = 0; - for (i = 0; i < DB_COUNT; ++i) { + for (int i = 0; i < DB_COUNT; i++) { if ((curtab->tp_diffbuf[i] != NULL) && (max_count < dp->df_count[i])) { max_count = dp->df_count[i]; } @@ -2193,7 +2209,7 @@ int diffopt_changed(void) } if (*p == ',') { - ++p; + p++; } } @@ -2296,7 +2312,7 @@ bool diff_find_change(win_T *wp, linenr_T lnum, int *startp, int *endp) int off = lnum - dp->df_lnum[idx]; int i; - for (i = 0; i < DB_COUNT; ++i) { + for (i = 0; i < DB_COUNT; i++) { if ((curtab->tp_diffbuf[i] != NULL) && (i != idx)) { // Skip lines that are not in the other change (filler lines). if (off >= dp->df_count[i]) { @@ -2395,7 +2411,6 @@ bool diff_infold(win_T *wp, linenr_T lnum) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ARG(1) { bool other = false; - diff_T *dp; // Return if 'diff' isn't set. if (!wp->w_p_diff) { @@ -2404,7 +2419,7 @@ bool diff_infold(win_T *wp, linenr_T lnum) int idx = -1; int i; - for (i = 0; i < DB_COUNT; ++i) { + for (i = 0; i < DB_COUNT; i++) { if (curtab->tp_diffbuf[i] == wp->w_buffer) { idx = i; } else if (curtab->tp_diffbuf[i] != NULL) { @@ -2427,7 +2442,7 @@ bool diff_infold(win_T *wp, linenr_T lnum) return true; } - for (dp = curtab->tp_first_diff; dp != NULL; dp = dp->df_next) { + for (diff_T *dp = curtab->tp_first_diff; dp != NULL; dp = dp->df_next) { // If this change is below the line there can't be any further match. if (dp->df_lnum[idx] - diff_context > lnum) { break; @@ -2454,7 +2469,7 @@ void nv_diffgetput(bool put, size_t count) if (count == 0) { ea.arg = (char_u *)""; } else { - vim_snprintf(buf, 30, "%zu", count); + vim_snprintf(buf, sizeof(buf), "%zu", count); ea.arg = (char_u *)buf; } @@ -2479,7 +2494,6 @@ void ex_diffgetput(exarg_T *eap) int count; linenr_T off = 0; diff_T *dp; - diff_T *dprev; diff_T *dfree; int i; int added; @@ -2503,7 +2517,7 @@ void ex_diffgetput(exarg_T *eap) if (*eap->arg == NUL) { // No argument: Find the other buffer in the list of diff buffers. - for (idx_other = 0; idx_other < DB_COUNT; ++idx_other) { + for (idx_other = 0; idx_other < DB_COUNT; idx_other++) { if ((curtab->tp_diffbuf[idx_other] != curbuf) && (curtab->tp_diffbuf[idx_other] != NULL)) { if ((eap->cmdidx != CMD_diffput) @@ -2524,7 +2538,7 @@ void ex_diffgetput(exarg_T *eap) } // Check that there isn't a third buffer in the list - for (i = idx_other + 1; i < DB_COUNT; ++i) { + for (i = idx_other + 1; i < DB_COUNT; i++) { if ((curtab->tp_diffbuf[i] != curbuf) && (curtab->tp_diffbuf[i] != NULL) && ((eap->cmdidx != CMD_diffput) @@ -2541,7 +2555,7 @@ void ex_diffgetput(exarg_T *eap) p--; } - for (i = 0; ascii_isdigit(eap->arg[i]) && eap->arg + i < p; ++i) { + for (i = 0; ascii_isdigit(eap->arg[i]) && eap->arg + i < p; i++) { } if (eap->arg + i == p) { @@ -2584,9 +2598,9 @@ void ex_diffgetput(exarg_T *eap) && (eap->line1 == curbuf->b_ml.ml_line_count) && (diff_check(curwin, eap->line1) == 0) && ((eap->line1 == 1) || (diff_check(curwin, eap->line1 - 1) == 0))) { - ++eap->line2; + eap->line2++; } else if (eap->line1 > 0) { - --eap->line1; + eap->line1--; } } @@ -2615,7 +2629,7 @@ void ex_diffgetput(exarg_T *eap) } } - dprev = NULL; + diff_T *dprev = NULL; for (dp = curtab->tp_first_diff; dp != NULL;) { if (dp->df_lnum[idx_cur] > eap->line2 + off) { @@ -2677,14 +2691,14 @@ void ex_diffgetput(exarg_T *eap) buf_empty = buf_is_empty(curbuf); added = 0; - for (i = 0; i < count; ++i) { + for (i = 0; i < count; i++) { // remember deleting the last line of the buffer buf_empty = curbuf->b_ml.ml_line_count == 1; ml_delete(lnum, false); added--; } - for (i = 0; i < dp->df_count[idx_from] - start_skip - end_skip; ++i) { + for (i = 0; i < dp->df_count[idx_from] - start_skip - end_skip; i++) { linenr_T nr = dp->df_lnum[idx_from] + start_skip + i; if (nr > curtab->tp_diffbuf[idx_from]->b_ml.ml_line_count) { break; @@ -2706,7 +2720,7 @@ void ex_diffgetput(exarg_T *eap) if ((start_skip == 0) && (end_skip == 0)) { // Check if there are any other buffers and if the diff is // equal in them. - for (i = 0; i < DB_COUNT; ++i) { + for (i = 0; i < DB_COUNT; i++) { if ((curtab->tp_diffbuf[i] != NULL) && (i != idx_from) && (i != idx_to) @@ -2809,7 +2823,7 @@ theend: static void diff_fold_update(diff_T *dp, int skip_idx) { FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - for (int i = 0; i < DB_COUNT; ++i) { + for (int i = 0; i < DB_COUNT; i++) { if ((curtab->tp_diffbuf[i] == wp->w_buffer) && (i != skip_idx)) { foldUpdate(wp, dp->df_lnum[i], dp->df_lnum[i] + dp->df_count[i]); } @@ -2898,13 +2912,10 @@ int diff_move_to(int dir, long count) /// "buf1" in diff mode. static linenr_T diff_get_corresponding_line_int(buf_T *buf1, linenr_T lnum1) { - int idx1; - int idx2; - diff_T *dp; int baseline = 0; - idx1 = diff_buf_idx(buf1); - idx2 = diff_buf_idx(curbuf); + int idx1 = diff_buf_idx(buf1); + int idx2 = diff_buf_idx(curbuf); if ((idx1 == DB_COUNT) || (idx2 == DB_COUNT) @@ -2922,7 +2933,7 @@ static linenr_T diff_get_corresponding_line_int(buf_T *buf1, linenr_T lnum1) return lnum1; } - for (dp = curtab->tp_first_diff; dp != NULL; dp = dp->df_next) { + for (diff_T *dp = curtab->tp_first_diff; dp != NULL; dp = dp->df_next) { if (dp->df_lnum[idx1] > lnum1) { return lnum1 - baseline; } @@ -2978,11 +2989,8 @@ linenr_T diff_get_corresponding_line(buf_T *buf1, linenr_T lnum1) linenr_T diff_lnum_win(linenr_T lnum, win_T *wp) { diff_T *dp; - int idx; - int i; - linenr_T n; - idx = diff_buf_idx(curbuf); + int idx = diff_buf_idx(curbuf); if (idx == DB_COUNT) { // safety check @@ -3008,14 +3016,14 @@ linenr_T diff_lnum_win(linenr_T lnum, win_T *wp) } // Find index for "wp". - i = diff_buf_idx(wp->w_buffer); + int i = diff_buf_idx(wp->w_buffer); if (i == DB_COUNT) { // safety check return (linenr_T)0; } - n = lnum + (dp->df_lnum[i] - dp->df_lnum[idx]); + linenr_T n = lnum + (dp->df_lnum[i] - dp->df_lnum[idx]); if (n > dp->df_lnum[i] + dp->df_count[i]) { n = dp->df_lnum[i] + dp->df_count[i]; } @@ -3026,19 +3034,16 @@ 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; - int difftype; + long l1, l2; // The line must be one of three formats: // change: {first}[,{last}]c{first}[,{last}] // append: {first}a{first}[,{last}] // delete: {first}[,{last}]d{first} - p = line; - f1 = getdigits(&p, true, 0); + char_u *p = line; + long f1 = getdigits(&p, true, 0); if (*p == ',') { p++; l1 = getdigits(&p, true, 0); @@ -3048,8 +3053,8 @@ static int parse_diff_ed(char_u *line, linenr_T *lnum_orig, long *count_orig, li if (*p != 'a' && *p != 'c' && *p != 'd') { return FAIL; // invalid diff format } - difftype = *p++; - f2 = getdigits(&p, true, 0); + int difftype = *p++; + long f2 = getdigits(&p, true, 0); if (*p == ',') { p++; l2 = getdigits(&p, true, 0); @@ -3061,18 +3066,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,17 +3086,16 @@ 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; - // Parse unified diff hunk header: // @@ -oldline,oldcount +newline,newcount @@ - p = line; + char_u *p = line; if (*p++ == '@' && *p++ == '@' && *p++ == ' ' && *p++ == '-') { - oldline = getdigits(&p, true, 0); + long oldcount; + long newline; + long newcount; + long oldline = getdigits(&p, true, 0); if (*p == ',') { p++; oldcount = getdigits(&p, true, 0); @@ -3120,10 +3124,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 +3139,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/digraph.c b/src/nvim/digraph.c index d1dd9b8309..083c868607 100644 --- a/src/nvim/digraph.c +++ b/src/nvim/digraph.c @@ -12,6 +12,7 @@ #include "nvim/ascii.h" #include "nvim/charset.h" #include "nvim/digraph.h" +#include "nvim/eval/typval.h" #include "nvim/ex_cmds2.h" #include "nvim/ex_docmd.h" #include "nvim/ex_getln.h" @@ -20,7 +21,6 @@ #include "nvim/mbyte.h" #include "nvim/memory.h" #include "nvim/message.h" -#include "nvim/misc1.h" #include "nvim/normal.h" #include "nvim/os/input.h" #include "nvim/screen.h" @@ -35,6 +35,12 @@ typedef struct digraph { result_T result; } digr_T; +static char e_digraph_must_be_just_two_characters_str[] + = N_("E1214: Digraph must be just two characters: %s"); +static char e_digraph_argument_must_be_one_character_str[] + = N_("E1215: Digraph must be one character: %s"); +static char e_digraph_setlist_argument_must_be_list_of_lists_with_two_items[] + = N_("E1216: digraph_setlist() argument must be a list of lists with two items"); #ifdef INCLUDE_GENERATED_DECLARATIONS # include "digraph.c.generated.h" @@ -1459,7 +1465,7 @@ int do_digraph(int c) backspaced = -1; } else if (p_dg) { if (backspaced >= 0) { - c = getdigraph(backspaced, c, false); + c = digraph_get(backspaced, c, false); } backspaced = -1; @@ -1476,17 +1482,16 @@ int do_digraph(int c) char_u *get_digraph_for_char(int val_arg) { const int val = val_arg; - digr_T *dp; + const digr_T *dp; static char_u r[3]; for (int use_defaults = 0; use_defaults <= 1; use_defaults++) { if (use_defaults == 0) { - dp = (digr_T *)user_digraphs.ga_data; + dp = (const digr_T *)user_digraphs.ga_data; } else { dp = digraphdefault; } - for (int i = 0; - use_defaults ? dp->char1 != NUL : i < user_digraphs.ga_len; i++) { + for (int i = 0; use_defaults ? dp->char1 != NUL : i < user_digraphs.ga_len; i++) { if (dp->result == val) { r[0] = dp->char1; r[1] = dp->char2; @@ -1507,7 +1512,6 @@ char_u *get_digraph_for_char(int val_arg) /// @returns composed character, or NUL when ESC was used. int get_digraph(bool cmdline) { - int cc; no_mapping++; int c = plain_vgetc(); no_mapping--; @@ -1527,12 +1531,12 @@ int get_digraph(bool cmdline) add_to_showcmd(c); } no_mapping++; - cc = plain_vgetc(); + int cc = plain_vgetc(); no_mapping--; if (cc != ESC) { // ESC cancels CTRL-K - return getdigraph(c, cc, true); + return digraph_get(c, cc, true); } } return NUL; @@ -1555,25 +1559,25 @@ static int getexactdigraph(int char1, int char2, bool meta_char) } // Search user digraphs first. - digr_T *dp = (digr_T *)user_digraphs.ga_data; - for (int i = 0; i < user_digraphs.ga_len; ++i) { + const digr_T *dp = (const digr_T *)user_digraphs.ga_data; + for (int i = 0; i < user_digraphs.ga_len; i++) { if (((int)dp->char1 == char1) && ((int)dp->char2 == char2)) { retval = dp->result; break; } - ++dp; + dp++; } // Search default digraphs. if (retval == 0) { dp = digraphdefault; - for (int i = 0; dp->char1 != 0; ++i) { + for (int i = 0; dp->char1 != 0; i++) { if (((int)dp->char1 == char1) && ((int)dp->char2 == char2)) { retval = dp->result; break; } - ++dp; + dp++; } } @@ -1596,7 +1600,7 @@ static int getexactdigraph(int char1, int char2, bool meta_char) /// @param meta_char /// /// @return The digraph. -int getdigraph(int char1, int char2, bool meta_char) +int digraph_get(int char1, int char2, bool meta_char) { int retval; @@ -1609,33 +1613,63 @@ int getdigraph(int char1, int char2, bool meta_char) return retval; } +/// Add a digraph to the digraph table. +static void registerdigraph(int char1, int char2, int n) +{ + // If the digraph already exists, replace "result". + digr_T *dp = (digr_T *)user_digraphs.ga_data; + for (int i = 0; i < user_digraphs.ga_len; i++) { + if ((int)dp->char1 == char1 && (int)dp->char2 == char2) { + dp->result = n; + return; + } + dp++; + } + + // Add a new digraph to the table. + dp = GA_APPEND_VIA_PTR(digr_T, &user_digraphs); + dp->char1 = (char_u)char1; + dp->char2 = (char_u)char2; + dp->result = n; +} + +/// Check the characters are valid for a digraph. +/// If they are valid, returns true; otherwise, give an error message and +/// returns false. +bool check_digraph_chars_valid(int char1, int char2) +{ + if (char2 == 0) { + char_u msg[MB_MAXBYTES + 1]; + msg[utf_char2bytes(char1, msg)] = NUL; + semsg(_(e_digraph_must_be_just_two_characters_str), msg); + return false; + } + if (char1 == ESC || char2 == ESC) { + emsg(_("E104: Escape not allowed in digraph")); + return false; + } + return true; +} + /// Add the digraphs in the argument to the digraph table. /// format: {c1}{c2} char {c1}{c2} char ... /// /// @param str void putdigraph(char_u *str) { - char_u char1, char2; - digr_T *dp; - while (*str != NUL) { str = skipwhite(str); if (*str == NUL) { return; } - char1 = *str++; - char2 = *str++; + char_u char1 = *str++; + char_u char2 = *str++; - if (char2 == 0) { - emsg(_(e_invarg)); + if (!check_digraph_chars_valid(char1, char2)) { return; } - if ((char1 == ESC) || (char2 == ESC)) { - emsg(_("E104: Escape not allowed in digraph")); - return; - } str = skipwhite(str); if (!ascii_isdigit(*str)) { @@ -1644,25 +1678,7 @@ void putdigraph(char_u *str) } int n = getdigits_int(&str, true, 0); - // If the digraph already exists, replace the result. - dp = (digr_T *)user_digraphs.ga_data; - - int i; - for (i = 0; i < user_digraphs.ga_len; ++i) { - if (((int)dp->char1 == char1) && ((int)dp->char2 == char2)) { - dp->result = n; - break; - } - ++dp; - } - - // Add a new digraph to the table. - if (i == user_digraphs.ga_len) { - dp = GA_APPEND_VIA_PTR(digr_T, &user_digraphs); - dp->char1 = char1; - dp->char2 = char2; - dp->result = n; - } + registerdigraph(char1, char2, n); } } @@ -1678,14 +1694,13 @@ static void digraph_header(const char *msg) void listdigraphs(bool use_headers) { - digr_T *dp; result_T previous = 0; msg_putchar('\n'); - dp = digraphdefault; + const digr_T *dp = digraphdefault; - for (int i = 0; dp->char1 != NUL && !got_int; ++i) { + for (int i = 0; dp->char1 != NUL && !got_int; i++) { digr_T tmp; // May need to convert the result to 'encoding'. @@ -1693,15 +1708,14 @@ void listdigraphs(bool use_headers) tmp.char2 = dp->char2; tmp.result = getexactdigraph(tmp.char1, tmp.char2, false); - if ((tmp.result != 0) - && (tmp.result != tmp.char2)) { + if (tmp.result != 0 && tmp.result != tmp.char2) { printdigraph(&tmp, use_headers ? &previous : NULL); } dp++; fast_breakcheck(); } - dp = (digr_T *)user_digraphs.ga_data; + dp = (const digr_T *)user_digraphs.ga_data; for (int i = 0; i < user_digraphs.ga_len && !got_int; i++) { if (previous >= 0 && use_headers) { digraph_header(_("Custom")); @@ -1713,6 +1727,50 @@ void listdigraphs(bool use_headers) } } +static void digraph_getlist_appendpair(const digr_T *dp, list_T *l) +{ + list_T *l2 = tv_list_alloc(2); + tv_list_append_list(l, l2); + + char_u buf[30]; + buf[0] = dp->char1; + buf[1] = dp->char2; + buf[2] = NUL; + tv_list_append_string(l2, (char *)buf, -1); + + char_u *p = buf; + p += utf_char2bytes(dp->result, p); + *p = NUL; + tv_list_append_string(l2, (char *)buf, -1); +} + +void digraph_getlist_common(bool list_all, typval_T *rettv) +{ + tv_list_alloc_ret(rettv, (int)sizeof(digraphdefault) + user_digraphs.ga_len); + + const digr_T *dp; + + if (list_all) { + dp = digraphdefault; + for (int i = 0; dp->char1 != NUL && !got_int; i++) { + digr_T tmp; + tmp.char1 = dp->char1; + tmp.char2 = dp->char2; + tmp.result = getexactdigraph(tmp.char1, tmp.char2, false); + if (tmp.result != 0 && tmp.result != tmp.char2) { + digraph_getlist_appendpair(&tmp, rettv->vval.v_list); + } + dp++; + } + } + + dp = (const digr_T *)user_digraphs.ga_data; + for (int i = 0; i < user_digraphs.ga_len && !got_int; i++) { + digraph_getlist_appendpair(dp, rettv->vval.v_list); + dp++; + } +} + struct dg_header_entry { int dg_start; const char *dg_header; @@ -1750,11 +1808,7 @@ static void printdigraph(const digr_T *dp, result_T *previous) FUNC_ATTR_NONNULL_ARG(1) { char_u buf[30]; - char_u *p; - - int list_width; - - list_width = 13; + int list_width = 13; if (dp->result != 0) { if (previous != NULL) { @@ -1781,7 +1835,7 @@ static void printdigraph(const digr_T *dp, result_T *previous) } } - p = &buf[0]; + char_u *p = &buf[0]; *p++ = dp->char1; *p++ = dp->char2; *p++ = ' '; @@ -1807,6 +1861,147 @@ static void printdigraph(const digr_T *dp, result_T *previous) } } +/// Get the two digraph characters from a typval. +/// @return OK or FAIL. +static int get_digraph_chars(const typval_T *arg, int *char1, int *char2) +{ + char buf_chars[NUMBUFLEN]; + const char *chars = tv_get_string_buf_chk(arg, buf_chars); + const char_u *p = (const char_u *)chars; + + if (p != NULL) { + if (*p != NUL) { + *char1 = mb_cptr2char_adv(&p); + if (*p != NUL) { + *char2 = mb_cptr2char_adv(&p); + if (*p == NUL) { + if (check_digraph_chars_valid(*char1, *char2)) { + return OK; + } + return FAIL; + } + } + } + } + semsg(_(e_digraph_must_be_just_two_characters_str), chars); + return FAIL; +} + +static bool digraph_set_common(const typval_T *argchars, const typval_T *argdigraph) +{ + int char1, char2; + if (get_digraph_chars(argchars, &char1, &char2) == FAIL) { + return false; + } + + char buf_digraph[NUMBUFLEN]; + const char *digraph = tv_get_string_buf_chk(argdigraph, buf_digraph); + if (digraph == NULL) { + return false; + } + const char_u *p = (const char_u *)digraph; + int n = mb_cptr2char_adv(&p); + if (*p != NUL) { + semsg(_(e_digraph_argument_must_be_one_character_str), digraph); + return false; + } + + registerdigraph(char1, char2, n); + return true; +} + +/// "digraph_get()" function +void f_digraph_get(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->v_type = VAR_STRING; + rettv->vval.v_string = NULL; // Return empty string for failure + const char *digraphs = tv_get_string_chk(&argvars[0]); + + if (digraphs == NULL) { + return; + } + if (STRLEN(digraphs) != 2) { + semsg(_(e_digraph_must_be_just_two_characters_str), digraphs); + return; + } + int code = digraph_get(digraphs[0], digraphs[1], false); + + char_u buf[NUMBUFLEN]; + buf[utf_char2bytes(code, buf)] = NUL; + rettv->vval.v_string = vim_strsave(buf); +} + +/// "digraph_getlist()" function +void f_digraph_getlist(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + bool flag_list_all; + + if (argvars[0].v_type == VAR_UNKNOWN) { + flag_list_all = false; + } else { + bool error = false; + varnumber_T flag = tv_get_number_chk(&argvars[0], &error); + if (error) { + return; + } + flag_list_all = flag != 0; + } + + digraph_getlist_common(flag_list_all, rettv); +} + +/// "digraph_set()" function +void f_digraph_set(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->v_type = VAR_BOOL; + rettv->vval.v_bool = kBoolVarFalse; + + if (!digraph_set_common(&argvars[0], &argvars[1])) { + return; + } + + rettv->vval.v_bool = kBoolVarTrue; +} + +/// "digraph_setlist()" function +void f_digraph_setlist(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->v_type = VAR_BOOL; + rettv->vval.v_bool = kBoolVarFalse; + + if (argvars[0].v_type != VAR_LIST) { + emsg(_(e_digraph_setlist_argument_must_be_list_of_lists_with_two_items)); + return; + } + + list_T *pl = argvars[0].vval.v_list; + if (pl == NULL) { + // Empty list always results in success. + rettv->vval.v_bool = kBoolVarTrue; + return; + } + + TV_LIST_ITER_CONST(pl, pli, { + if (TV_LIST_ITEM_TV(pli)->v_type != VAR_LIST) { + emsg(_(e_digraph_setlist_argument_must_be_list_of_lists_with_two_items)); + return; + } + + list_T *l = TV_LIST_ITEM_TV(pli)->vval.v_list; + if (l == NULL || tv_list_len(l) != 2) { + emsg(_(e_digraph_setlist_argument_must_be_list_of_lists_with_two_items)); + return; + } + + if (!digraph_set_common(TV_LIST_ITEM_TV(tv_list_first(l)), + TV_LIST_ITEM_TV(TV_LIST_ITEM_NEXT(l, tv_list_first(l))))) { + return; + } + }); + + rettv->vval.v_bool = kBoolVarTrue; +} + /// structure used for b_kmap_ga.ga_data typedef struct { char_u *from; @@ -1864,8 +2059,6 @@ char *keymap_init(void) /// @param eap void ex_loadkeymap(exarg_T *eap) { - char_u *line; - char_u *p; char_u *s; #define KMAP_LLEN 200 // max length of "to" and "from" together @@ -1888,13 +2081,13 @@ void ex_loadkeymap(exarg_T *eap) // Get each line of the sourced file, break at the end. for (;;) { - line = eap->getline(0, eap->cookie, 0, true); + char_u *line = eap->getline(0, eap->cookie, 0, true); if (line == NULL) { break; } - p = skipwhite(line); + char_u *p = skipwhite(line); if ((*p != '"') && (*p != NUL)) { kmap_T *kp = GA_APPEND_VIA_PTR(kmap_T, &curbuf->b_kmap_ga); @@ -1912,7 +2105,7 @@ void ex_loadkeymap(exarg_T *eap) } xfree(kp->from); xfree(kp->to); - --curbuf->b_kmap_ga.ga_len; + curbuf->b_kmap_ga.ga_len--; } } xfree(line); @@ -1947,7 +2140,6 @@ static void keymap_unload(void) { char_u buf[KMAP_MAXLEN + 10]; char_u *save_cpo = p_cpo; - kmap_T *kp; if (!(curbuf->b_kmap_state & KEYMAP_LOADED)) { return; @@ -1957,7 +2149,7 @@ static void keymap_unload(void) p_cpo = (char_u *)"C"; // clear the ":lmap"s - kp = (kmap_T *)curbuf->b_kmap_ga.ga_data; + kmap_T *kp = (kmap_T *)curbuf->b_kmap_ga.ga_data; for (int i = 0; i < curbuf->b_kmap_ga.ga_len; i++) { vim_snprintf((char *)buf, sizeof(buf), "<buffer> %s", kp[i].from); diff --git a/src/nvim/digraph.h b/src/nvim/digraph.h index 71330ae9b1..80b4148213 100644 --- a/src/nvim/digraph.h +++ b/src/nvim/digraph.h @@ -2,6 +2,7 @@ #define NVIM_DIGRAPH_H #include "nvim/ex_cmds_defs.h" +#include "nvim/eval/funcs.h" #include "nvim/types.h" #ifdef INCLUDE_GENERATED_DECLARATIONS diff --git a/src/nvim/edit.c b/src/nvim/edit.c index d505b40935..47d491033b 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -26,6 +26,7 @@ #include "nvim/fileio.h" #include "nvim/fold.h" #include "nvim/getchar.h" +#include "nvim/highlight_group.h" #include "nvim/indent.h" #include "nvim/indent_c.h" #include "nvim/keymap.h" @@ -35,7 +36,6 @@ #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/message.h" -#include "nvim/misc1.h" #include "nvim/mouse.h" #include "nvim/move.h" #include "nvim/normal.h" @@ -227,6 +227,7 @@ typedef struct insert_state { cmdarg_T *ca; int mincol; int cmdchar; + int cmdchar_todo; // cmdchar to handle once in init_prompt int startln; long count; int c; @@ -260,7 +261,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() @@ -290,6 +291,7 @@ static void insert_enter(InsertState *s) s->did_backspace = true; s->old_topfill = -1; s->replaceState = REPLACE; + s->cmdchar_todo = s->cmdchar; // Remember whether editing was restarted after CTRL-O did_restart_edit = restart_edit; // sleep before redrawing, needed for "CTRL-O :" that results in an @@ -385,11 +387,13 @@ static void insert_enter(InsertState *s) State = INSERT; } + may_trigger_modechanged(); stop_insert_mode = false; - // Need to recompute the cursor position, it might move when the cursor is - // on a TAB or special character. - curs_columns(curwin, true); + // need to position cursor again when on a TAB + if (gchar_cursor() == TAB) { + curwin->w_valid &= ~(VALID_WROW|VALID_WCOL|VALID_VIRTCOL); + } // Enable langmap or IME, indicated by 'iminsert'. // Note that IME may enabled/disabled without us noticing here, thus the @@ -584,7 +588,8 @@ static int insert_check(VimState *state) } if (bt_prompt(curbuf)) { - init_prompt(s->cmdchar); + init_prompt(s->cmdchar_todo); + s->cmdchar_todo = NUL; } // If we inserted a character at the last position of the last line in the @@ -640,7 +645,10 @@ static int insert_check(VimState *state) update_curswant(); s->old_topline = curwin->w_topline; s->old_topfill = curwin->w_topfill; - s->lastc = s->c; // remember previous char for CTRL-D + + if (s->c != K_EVENT) { + s->lastc = s->c; // remember previous char for CTRL-D + } // After using CTRL-G U the next cursor key will not break undo. if (dont_sync_undo == kNone) { @@ -654,10 +662,21 @@ static int insert_check(VimState *state) 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; + } + if (key == K_IGNORE || key == K_NOP) { return -1; // get another key } - InsertState *s = (InsertState *)state; s->c = key; // Don't want K_EVENT with cursorhold for the second key, e.g., after CTRL-V. @@ -821,6 +840,16 @@ static int insert_execute(VimState *state, int key) return insert_handle_key(s); } + +/// Return true when need to go to Insert mode because of 'insertmode'. +/// +/// Don't do this when still processing a command or a mapping. +/// Don't do this when inside a ":normal" command. +bool goto_im(void) +{ + return p_im && stuff_empty() && typebuf_typed(); +} + static int insert_handle_key(InsertState *s) { // The big switch to handle a character in insert mode. @@ -886,7 +915,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; } @@ -983,6 +1012,15 @@ static int insert_handle_key(InsertState *s) break; case Ctrl_W: // delete word before the cursor + if (bt_prompt(curbuf) && (mod_mask & MOD_MASK_SHIFT) == 0) { + // In a prompt window CTRL-W is used for window commands. + // Use Shift-CTRL-W to delete a word. + stuffcharReadbuff(Ctrl_W); + restart_edit = 'A'; + s->nomove = true; + s->count = 0; + return 0; + } s->did_backspace = ins_bs(s->c, BACKSPACE_WORD, &s->inserted_space); auto_format(false, true); break; @@ -1044,13 +1082,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 @@ -1347,6 +1393,7 @@ static void insert_do_complete(InsertState *s) compl_cont_status = 0; } compl_busy = false; + can_si = true; // allow smartindenting } static void insert_do_cindent(InsertState *s) @@ -1435,15 +1482,13 @@ bool edit(int cmdchar, bool startln, long count) /// @param ready not busy with something static void ins_redraw(bool ready) { - bool conceal_cursor_moved = false; - if (char_avail()) { return; } // Trigger CursorMoved if the cursor moved. Not when the popup menu is // visible, the command might delete it. - if (ready && (has_event(EVENT_CURSORMOVEDI) || curwin->w_p_cole > 0) + if (ready && has_event(EVENT_CURSORMOVEDI) && !equalpos(curwin->w_last_cursormoved, curwin->w_cursor) && !pum_visible()) { // Need to update the screen first, to make sure syntax @@ -1453,13 +1498,10 @@ static void ins_redraw(bool ready) if (syntax_present(curwin) && must_redraw) { update_screen(0); } - if (has_event(EVENT_CURSORMOVEDI)) { - // Make sure curswant is correct, an autocommand may call - // getcurpos() - update_curswant(); - ins_apply_autocmds(EVENT_CURSORMOVEDI); - } - conceal_cursor_moved = true; + // Make sure curswant is correct, an autocommand may call + // getcurpos() + update_curswant(); + ins_apply_autocmds(EVENT_CURSORMOVEDI); curwin->w_last_cursormoved = curwin->w_cursor; } @@ -1501,10 +1543,9 @@ static void ins_redraw(bool ready) } } - // Trigger Scroll if viewport changed. - if (ready && has_event(EVENT_WINSCROLLED) - && win_did_scroll(curwin)) { - do_autocmd_winscrolled(curwin); + if (ready) { + // Trigger Scroll if viewport changed. + may_trigger_winscrolled(); } // Trigger BufModified if b_changed_invalid is set. @@ -1515,11 +1556,6 @@ static void ins_redraw(bool ready) curbuf->b_changed_invalid = false; } - if (curwin->w_p_cole > 0 && conceal_cursor_line(curwin) - && conceal_cursor_moved) { - redrawWinline(curwin, curwin->w_cursor.lnum); - } - pum_check_clear(); if (must_redraw) { update_screen(0); @@ -1568,8 +1604,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; @@ -1652,11 +1688,22 @@ static void init_prompt(int cmdchar_todo) coladvance(MAXCOL); changed_bytes(curbuf->b_ml.ml_line_count, 0); } + + // Insert always starts after the prompt, allow editing text after it. + if (Insstart_orig.lnum != curwin->w_cursor.lnum || Insstart_orig.col != (colnr_T)STRLEN(prompt)) { + Insstart.lnum = curwin->w_cursor.lnum; + Insstart.col = (colnr_T)STRLEN(prompt); + Insstart_orig = Insstart; + Insstart_textlen = Insstart.col; + Insstart_blank_vcol = MAXCOL; + arrow_used = false; + } + if (cmdchar_todo == 'A') { coladvance(MAXCOL); } - if (cmdchar_todo == 'I' || curwin->w_cursor.col <= (int)STRLEN(prompt)) { - curwin->w_cursor.col = STRLEN(prompt); + if (curwin->w_cursor.col < (colnr_T)STRLEN(prompt)) { + curwin->w_cursor.col = (colnr_T)STRLEN(prompt); } // Make sure the cursor is in a valid position. check_cursor(); @@ -2048,6 +2095,8 @@ static void ins_ctrl_x(void) // CTRL-V look like CTRL-N ctrl_x_mode = CTRL_X_CMDLINE_CTRL_X; } + + may_trigger_modechanged(); } // Whether other than default completion has been selected. @@ -2660,6 +2709,7 @@ void set_completion(colnr_T startcol, list_T *list) show_pum(save_w_wrow, save_w_leftcol); } + may_trigger_modechanged(); ui_flush(); } @@ -2715,12 +2765,13 @@ static bool pum_enough_matches(void) static void trigger_complete_changed_event(int cur) { static bool recursive = false; + save_v_event_T save_v_event; if (recursive) { return; } - dict_T *v_event = get_vim_var_dict(VV_EVENT); + dict_T *v_event = get_v_event(&save_v_event); if (cur < 0) { tv_dict_add_dict(v_event, S_LEN("completed_item"), tv_dict_alloc()); } else { @@ -2736,7 +2787,7 @@ static void trigger_complete_changed_event(int cur) textlock--; recursive = false; - tv_dict_clear(v_event); + restore_v_event(v_event, &save_v_event); } /// Show the popup menu for the list of matches. @@ -3569,7 +3620,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; } @@ -3838,6 +3889,8 @@ static bool ins_compl_prep(int c) ins_apply_autocmds(EVENT_COMPLETEDONE); } + may_trigger_modechanged(); + /* reset continue_* if we left expansion-mode, if we stay they'll be * (re)set properly in ins_complete() */ if (!vim_is_ctrl_x_key(c)) { @@ -3919,7 +3972,8 @@ static buf_T *ins_compl_next_buf(buf_T *buf, int flag) /// Get the user-defined completion function name for completion 'type' -static char_u *get_complete_funcname(int type) { +static char_u *get_complete_funcname(int type) +{ switch (type) { case CTRL_X_FUNCTION: return curbuf->b_p_cfu; @@ -4586,6 +4640,8 @@ static int ins_compl_get_exp(pos_T *ini) compl_curr_match = compl_old_match; } } + may_trigger_modechanged(); + return i; } @@ -4930,7 +4986,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 @@ -4960,7 +5016,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); } @@ -4994,6 +5050,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; @@ -5051,10 +5108,10 @@ static int ins_complete(int c, bool enable_pum) || ctrl_x_mode == CTRL_X_PATH_PATTERNS || ctrl_x_mode == CTRL_X_PATH_DEFINES) { if (compl_startpos.lnum != curwin->w_cursor.lnum) { - /* line (probably) wrapped, set compl_startpos to the - * first non_blank in the line, if it is not a wordchar - * include it to get a better pattern, but then we don't - * want the "\\<" prefix, check it bellow */ + // line (probably) wrapped, set compl_startpos to the + // first non_blank in the line, if it is not a wordchar + // include it to get a better pattern, but then we don't + // want the "\\<" prefix, check it below. compl_col = (colnr_T)getwhitecols(line); compl_startpos.col = compl_col; compl_startpos.lnum = curwin->w_cursor.lnum; @@ -5231,7 +5288,7 @@ static int ins_complete(int c, bool enable_pum) funcname = get_complete_funcname(ctrl_x_mode); if (*funcname == NUL) { semsg(_(e_notset), ctrl_x_mode == CTRL_X_FUNCTION - ? "completefunc" : "omnifunc"); + ? "completefunc" : "omnifunc"); // restore did_ai, so that adding comment leader works did_ai = save_did_ai; return FAIL; @@ -5572,8 +5629,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') { @@ -5639,6 +5700,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; @@ -5950,6 +6013,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()); @@ -6065,8 +6129,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; @@ -6223,11 +6286,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)) { @@ -6564,7 +6634,7 @@ static void spell_back_to_badword(void) int stop_arrow(void) { if (arrow_used) { - Insstart = curwin->w_cursor; //new insertion starts here + Insstart = curwin->w_cursor; // new insertion starts here if (Insstart.col > Insstart_orig.col && !ins_need_undo) { // Don't update the original insert position when moved to the // right, except when nothing was inserted yet. @@ -6758,7 +6828,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. @@ -6772,7 +6842,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; @@ -6846,8 +6916,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; @@ -7054,9 +7123,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); @@ -7260,21 +7327,21 @@ static void mb_replace_pop_ins(int cc) // Not a multi-byte char, put it back. replace_push(c); break; + } + + buf[0] = c; + assert(n > 1); + for (i = 1; i < n; i++) { + buf[i] = replace_pop(); + } + if (utf_iscomposing(utf_ptr2char(buf))) { + ins_bytes_len(buf, n); } else { - buf[0] = c; - assert(n > 1); - for (i = 1; i < n; i++) { - buf[i] = replace_pop(); - } - if (utf_iscomposing(utf_ptr2char(buf))) { - ins_bytes_len(buf, n); - } else { - // Not a composing char, put it back. - for (i = n - 1; i >= 0; i--) { - replace_push(buf[i]); - } - break; + // Not a composing char, put it back. + for (i = n - 1; i >= 0; i--) { + replace_push(buf[i]); } + break; } } } @@ -7630,16 +7697,34 @@ int hkmap(int c) KAFsofit, hKAF, LAMED, MEMsofit, MEM, NUNsofit, NUN, SAMEH, AIN, PEIsofit, PEI, ZADIsofit, ZADI, KOF, RESH, hSHIN, TAV, }; - static char_u map[26] = - { (char_u)hALEF /*a*/, (char_u)BET /*b*/, (char_u)hKAF /*c*/, - (char_u)DALET /*d*/, (char_u)-1 /*e*/, (char_u)PEIsofit /*f*/, - (char_u)GIMEL /*g*/, (char_u)HEI /*h*/, (char_u)IUD /*i*/, - (char_u)HET /*j*/, (char_u)KOF /*k*/, (char_u)LAMED /*l*/, - (char_u)MEM /*m*/, (char_u)NUN /*n*/, (char_u)SAMEH /*o*/, - (char_u)PEI /*p*/, (char_u)-1 /*q*/, (char_u)RESH /*r*/, - (char_u)ZAIN /*s*/, (char_u)TAV /*t*/, (char_u)TET /*u*/, - (char_u)VAV /*v*/, (char_u)hSHIN /*w*/, (char_u)-1 /*x*/, - (char_u)AIN /*y*/, (char_u)ZADI /*z*/ }; + static char_u map[26] = { + (char_u)hALEF, // a + (char_u)BET, // b + (char_u)hKAF, // c + (char_u)DALET, // d + (char_u)-1, // e + (char_u)PEIsofit, // f + (char_u)GIMEL, // g + (char_u)HEI, // h + (char_u)IUD, // i + (char_u)HET, // j + (char_u)KOF, // k + (char_u)LAMED, // l + (char_u)MEM, // m + (char_u)NUN, // n + (char_u)SAMEH, // o + (char_u)PEI, // p + (char_u)-1, // q + (char_u)RESH, // r + (char_u)ZAIN, // s + (char_u)TAV, // t + (char_u)TET, // u + (char_u)VAV, // v + (char_u)hSHIN, // w + (char_u)-1, // x + (char_u)AIN, // y + (char_u)ZADI, // z + }; if (c == 'N' || c == 'M' || c == 'P' || c == 'C' || c == 'Z') { return (int)(map[CharOrd(c)] - 1 + p_aleph); @@ -7683,7 +7768,7 @@ int hkmap(int c) case ';': c = 't'; break; default: { - static char str[] = "zqbcxlsjphmkwonu ydafe rig"; + static char_u str[] = "zqbcxlsjphmkwonu ydafe rig"; if (c < 'a' || c > 'z') { return c; @@ -7951,7 +8036,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++; @@ -7965,8 +8050,11 @@ static bool ins_esc(long *count, int cmdchar, bool nomove) State = NORMAL; - // need to position cursor again (e.g. when on a TAB ) - changed_cline_bef_curs(); + may_trigger_modechanged(); + // need to position cursor again when on a TAB + if (gchar_cursor() == TAB) { + curwin->w_valid &= ~(VALID_WROW|VALID_WCOL|VALID_VIRTCOL); + } setmouse(); ui_cursor_shape(); // may show different cursor shape @@ -8066,6 +8154,7 @@ static void ins_insert(int replaceState) } else { State = replaceState | (State & LANGMAP); } + may_trigger_modechanged(); AppendCharToRedobuff(K_INS); showmode(); ui_cursor_shape(); // may show different cursor shape @@ -8202,6 +8291,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 @@ -8211,7 +8301,7 @@ static bool ins_bs(int c, int mode, int *inserted_space_p) || (!revins_on && ((curwin->w_cursor.lnum == 1 && curwin->w_cursor.col == 0) || (!can_bs(BS_START) - && (arrow_used + && ((arrow_used && !bt_prompt(curbuf)) || (curwin->w_cursor.lnum == Insstart_orig.lnum && curwin->w_cursor.col <= Insstart_orig.col))) || (!can_bs(BS_INDENT) && !arrow_used && ai_col > 0 @@ -8345,6 +8435,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; } @@ -8479,6 +8571,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. @@ -9093,7 +9190,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. @@ -9163,7 +9260,7 @@ static int ins_digraph(void) } if (cc != ESC) { AppendToRedobuff(CTRL_V_STR); - c = getdigraph(c, cc, true); + c = digraph_get(c, cc, true); clear_showcmd(); return c; } diff --git a/src/nvim/eval.c b/src/nvim/eval.c index b97282dd1a..3e855ece15 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" @@ -27,20 +28,18 @@ #include "nvim/eval/typval.h" #include "nvim/eval/userfunc.h" #include "nvim/ex_cmds2.h" -#include "nvim/ex_docmd.h" #include "nvim/ex_getln.h" #include "nvim/ex_session.h" #include "nvim/fileio.h" #include "nvim/getchar.h" +#include "nvim/highlight_group.h" #include "nvim/lua/executor.h" #include "nvim/mark.h" #include "nvim/memline.h" -#include "nvim/misc1.h" #include "nvim/move.h" #include "nvim/ops.h" #include "nvim/option.h" #include "nvim/os/input.h" -#include "nvim/os/os.h" #include "nvim/os/shell.h" #include "nvim/path.h" #include "nvim/quickfix.h" @@ -67,9 +66,9 @@ static char *e_illvar = N_("E461: Illegal variable name: %s"); static char *e_cannot_mod = N_("E995: Cannot modify existing variable"); static char *e_nowhitespace = N_("E274: No white space allowed before parenthesis"); -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="); @@ -113,9 +112,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: @@ -134,13 +135,15 @@ typedef struct { .vv_flags = flags, \ } +#define VIMVAR_KEY_LEN 16 // Maximum length of the key of v:variables + // Array to hold the value of v: variables. // The value is in a dictitem, so that it can also be used in the v: scope. // The reason to use this table anyway is for very quick access to the // variables with the VV_ defines. static struct vimvar { char *vv_name; ///< Name of the variable, without v:. - TV_DICTITEM_STRUCT(17) vv_di; ///< Value and name for key (max 16 chars). + TV_DICTITEM_STRUCT(VIMVAR_KEY_LEN + 1) vv_di; ///< Value and name for key (max 16 chars). char vv_flags; ///< Flags: #VV_COMPAT, #VV_RO, #VV_RO_SBX. } vimvars[] = { @@ -150,100 +153,103 @@ static struct vimvar { // VV_SEND_SERVER "servername" // VV_REG "register" // VV_OP "operator" - VV(VV_COUNT, "count", VAR_NUMBER, VV_RO), - VV(VV_COUNT1, "count1", VAR_NUMBER, VV_RO), - VV(VV_PREVCOUNT, "prevcount", VAR_NUMBER, VV_RO), - VV(VV_ERRMSG, "errmsg", VAR_STRING, 0), - VV(VV_WARNINGMSG, "warningmsg", VAR_STRING, 0), - VV(VV_STATUSMSG, "statusmsg", VAR_STRING, 0), - VV(VV_SHELL_ERROR, "shell_error", VAR_NUMBER, VV_RO), - VV(VV_THIS_SESSION, "this_session", VAR_STRING, 0), - VV(VV_VERSION, "version", VAR_NUMBER, VV_COMPAT+VV_RO), - VV(VV_LNUM, "lnum", VAR_NUMBER, VV_RO_SBX), - VV(VV_TERMRESPONSE, "termresponse", VAR_STRING, VV_RO), - VV(VV_FNAME, "fname", VAR_STRING, VV_RO), - VV(VV_LANG, "lang", VAR_STRING, VV_RO), - VV(VV_LC_TIME, "lc_time", VAR_STRING, VV_RO), - VV(VV_CTYPE, "ctype", VAR_STRING, VV_RO), - VV(VV_CC_FROM, "charconvert_from", VAR_STRING, VV_RO), - VV(VV_CC_TO, "charconvert_to", VAR_STRING, VV_RO), - VV(VV_FNAME_IN, "fname_in", VAR_STRING, VV_RO), - VV(VV_FNAME_OUT, "fname_out", VAR_STRING, VV_RO), - VV(VV_FNAME_NEW, "fname_new", VAR_STRING, VV_RO), - VV(VV_FNAME_DIFF, "fname_diff", VAR_STRING, VV_RO), - VV(VV_CMDARG, "cmdarg", VAR_STRING, VV_RO), - VV(VV_FOLDSTART, "foldstart", VAR_NUMBER, VV_RO_SBX), - VV(VV_FOLDEND, "foldend", VAR_NUMBER, VV_RO_SBX), - VV(VV_FOLDDASHES, "folddashes", VAR_STRING, VV_RO_SBX), - VV(VV_FOLDLEVEL, "foldlevel", VAR_NUMBER, VV_RO_SBX), - VV(VV_PROGNAME, "progname", VAR_STRING, VV_RO), - VV(VV_SEND_SERVER, "servername", VAR_STRING, VV_RO), - VV(VV_DYING, "dying", VAR_NUMBER, VV_RO), - VV(VV_EXCEPTION, "exception", VAR_STRING, VV_RO), - VV(VV_THROWPOINT, "throwpoint", VAR_STRING, VV_RO), - VV(VV_REG, "register", VAR_STRING, VV_RO), - VV(VV_CMDBANG, "cmdbang", VAR_NUMBER, VV_RO), - VV(VV_INSERTMODE, "insertmode", VAR_STRING, VV_RO), - VV(VV_VAL, "val", VAR_UNKNOWN, VV_RO), - VV(VV_KEY, "key", VAR_UNKNOWN, VV_RO), - VV(VV_PROFILING, "profiling", VAR_NUMBER, VV_RO), - VV(VV_FCS_REASON, "fcs_reason", VAR_STRING, VV_RO), - VV(VV_FCS_CHOICE, "fcs_choice", VAR_STRING, 0), - VV(VV_BEVAL_BUFNR, "beval_bufnr", VAR_NUMBER, VV_RO), - VV(VV_BEVAL_WINNR, "beval_winnr", VAR_NUMBER, VV_RO), - VV(VV_BEVAL_WINID, "beval_winid", VAR_NUMBER, VV_RO), - VV(VV_BEVAL_LNUM, "beval_lnum", VAR_NUMBER, VV_RO), - VV(VV_BEVAL_COL, "beval_col", VAR_NUMBER, VV_RO), - VV(VV_BEVAL_TEXT, "beval_text", VAR_STRING, VV_RO), - VV(VV_SCROLLSTART, "scrollstart", VAR_STRING, 0), - VV(VV_SWAPNAME, "swapname", VAR_STRING, VV_RO), - VV(VV_SWAPCHOICE, "swapchoice", VAR_STRING, 0), - VV(VV_SWAPCOMMAND, "swapcommand", VAR_STRING, VV_RO), - VV(VV_CHAR, "char", VAR_STRING, 0), - VV(VV_MOUSE_WIN, "mouse_win", VAR_NUMBER, 0), - VV(VV_MOUSE_WINID, "mouse_winid", VAR_NUMBER, 0), - VV(VV_MOUSE_LNUM, "mouse_lnum", VAR_NUMBER, 0), - VV(VV_MOUSE_COL, "mouse_col", VAR_NUMBER, 0), - VV(VV_OP, "operator", VAR_STRING, VV_RO), - VV(VV_SEARCHFORWARD, "searchforward", VAR_NUMBER, 0), - VV(VV_HLSEARCH, "hlsearch", VAR_NUMBER, 0), - VV(VV_OLDFILES, "oldfiles", VAR_LIST, 0), - VV(VV_WINDOWID, "windowid", VAR_NUMBER, VV_RO_SBX), - VV(VV_PROGPATH, "progpath", VAR_STRING, VV_RO), - VV(VV_COMPLETED_ITEM, "completed_item", VAR_DICT, VV_RO), - VV(VV_OPTION_NEW, "option_new", VAR_STRING, VV_RO), - VV(VV_OPTION_OLD, "option_old", VAR_STRING, VV_RO), - VV(VV_OPTION_TYPE, "option_type", VAR_STRING, VV_RO), - VV(VV_ERRORS, "errors", VAR_LIST, 0), - VV(VV_FALSE, "false", VAR_BOOL, VV_RO), - VV(VV_TRUE, "true", VAR_BOOL, VV_RO), - VV(VV_NULL, "null", VAR_SPECIAL, VV_RO), - VV(VV_NUMBERMAX, "numbermax", VAR_NUMBER, VV_RO), - VV(VV_NUMBERMIN, "numbermin", VAR_NUMBER, VV_RO), - VV(VV_NUMBERSIZE, "numbersize", VAR_NUMBER, VV_RO), - VV(VV_VIM_DID_ENTER, "vim_did_enter", VAR_NUMBER, VV_RO), - VV(VV_TESTING, "testing", VAR_NUMBER, 0), - VV(VV_TYPE_NUMBER, "t_number", VAR_NUMBER, VV_RO), - VV(VV_TYPE_STRING, "t_string", VAR_NUMBER, VV_RO), - VV(VV_TYPE_FUNC, "t_func", VAR_NUMBER, VV_RO), - VV(VV_TYPE_LIST, "t_list", VAR_NUMBER, VV_RO), - VV(VV_TYPE_DICT, "t_dict", VAR_NUMBER, VV_RO), - VV(VV_TYPE_FLOAT, "t_float", VAR_NUMBER, VV_RO), - VV(VV_TYPE_BOOL, "t_bool", VAR_NUMBER, VV_RO), - VV(VV_TYPE_BLOB, "t_blob", VAR_NUMBER, VV_RO), - VV(VV_EVENT, "event", VAR_DICT, VV_RO), - VV(VV_ECHOSPACE, "echospace", VAR_NUMBER, VV_RO), - VV(VV_ARGV, "argv", VAR_LIST, VV_RO), - VV(VV_COLLATE, "collate", VAR_STRING, VV_RO), - VV(VV_EXITING, "exiting", VAR_NUMBER, VV_RO), + VV(VV_COUNT, "count", VAR_NUMBER, VV_RO), + VV(VV_COUNT1, "count1", VAR_NUMBER, VV_RO), + VV(VV_PREVCOUNT, "prevcount", VAR_NUMBER, VV_RO), + VV(VV_ERRMSG, "errmsg", VAR_STRING, 0), + VV(VV_WARNINGMSG, "warningmsg", VAR_STRING, 0), + VV(VV_STATUSMSG, "statusmsg", VAR_STRING, 0), + VV(VV_SHELL_ERROR, "shell_error", VAR_NUMBER, VV_RO), + VV(VV_THIS_SESSION, "this_session", VAR_STRING, 0), + VV(VV_VERSION, "version", VAR_NUMBER, VV_COMPAT+VV_RO), + VV(VV_LNUM, "lnum", VAR_NUMBER, VV_RO_SBX), + VV(VV_TERMRESPONSE, "termresponse", VAR_STRING, VV_RO), + VV(VV_FNAME, "fname", VAR_STRING, VV_RO), + VV(VV_LANG, "lang", VAR_STRING, VV_RO), + VV(VV_LC_TIME, "lc_time", VAR_STRING, VV_RO), + VV(VV_CTYPE, "ctype", VAR_STRING, VV_RO), + VV(VV_CC_FROM, "charconvert_from", VAR_STRING, VV_RO), + VV(VV_CC_TO, "charconvert_to", VAR_STRING, VV_RO), + VV(VV_FNAME_IN, "fname_in", VAR_STRING, VV_RO), + VV(VV_FNAME_OUT, "fname_out", VAR_STRING, VV_RO), + VV(VV_FNAME_NEW, "fname_new", VAR_STRING, VV_RO), + VV(VV_FNAME_DIFF, "fname_diff", VAR_STRING, VV_RO), + VV(VV_CMDARG, "cmdarg", VAR_STRING, VV_RO), + VV(VV_FOLDSTART, "foldstart", VAR_NUMBER, VV_RO_SBX), + VV(VV_FOLDEND, "foldend", VAR_NUMBER, VV_RO_SBX), + VV(VV_FOLDDASHES, "folddashes", VAR_STRING, VV_RO_SBX), + VV(VV_FOLDLEVEL, "foldlevel", VAR_NUMBER, VV_RO_SBX), + VV(VV_PROGNAME, "progname", VAR_STRING, VV_RO), + VV(VV_SEND_SERVER, "servername", VAR_STRING, VV_RO), + VV(VV_DYING, "dying", VAR_NUMBER, VV_RO), + VV(VV_EXCEPTION, "exception", VAR_STRING, VV_RO), + VV(VV_THROWPOINT, "throwpoint", VAR_STRING, VV_RO), + VV(VV_REG, "register", VAR_STRING, VV_RO), + VV(VV_CMDBANG, "cmdbang", VAR_NUMBER, VV_RO), + VV(VV_INSERTMODE, "insertmode", VAR_STRING, VV_RO), + VV(VV_VAL, "val", VAR_UNKNOWN, VV_RO), + VV(VV_KEY, "key", VAR_UNKNOWN, VV_RO), + VV(VV_PROFILING, "profiling", VAR_NUMBER, VV_RO), + VV(VV_FCS_REASON, "fcs_reason", VAR_STRING, VV_RO), + VV(VV_FCS_CHOICE, "fcs_choice", VAR_STRING, 0), + VV(VV_BEVAL_BUFNR, "beval_bufnr", VAR_NUMBER, VV_RO), + VV(VV_BEVAL_WINNR, "beval_winnr", VAR_NUMBER, VV_RO), + VV(VV_BEVAL_WINID, "beval_winid", VAR_NUMBER, VV_RO), + VV(VV_BEVAL_LNUM, "beval_lnum", VAR_NUMBER, VV_RO), + VV(VV_BEVAL_COL, "beval_col", VAR_NUMBER, VV_RO), + VV(VV_BEVAL_TEXT, "beval_text", VAR_STRING, VV_RO), + VV(VV_SCROLLSTART, "scrollstart", VAR_STRING, 0), + VV(VV_SWAPNAME, "swapname", VAR_STRING, VV_RO), + VV(VV_SWAPCHOICE, "swapchoice", VAR_STRING, 0), + VV(VV_SWAPCOMMAND, "swapcommand", VAR_STRING, VV_RO), + VV(VV_CHAR, "char", VAR_STRING, 0), + VV(VV_MOUSE_WIN, "mouse_win", VAR_NUMBER, 0), + VV(VV_MOUSE_WINID, "mouse_winid", VAR_NUMBER, 0), + VV(VV_MOUSE_LNUM, "mouse_lnum", VAR_NUMBER, 0), + VV(VV_MOUSE_COL, "mouse_col", VAR_NUMBER, 0), + VV(VV_OP, "operator", VAR_STRING, VV_RO), + VV(VV_SEARCHFORWARD, "searchforward", VAR_NUMBER, 0), + VV(VV_HLSEARCH, "hlsearch", VAR_NUMBER, 0), + VV(VV_OLDFILES, "oldfiles", VAR_LIST, 0), + VV(VV_WINDOWID, "windowid", VAR_NUMBER, VV_RO_SBX), + VV(VV_PROGPATH, "progpath", VAR_STRING, VV_RO), + VV(VV_COMPLETED_ITEM, "completed_item", VAR_DICT, VV_RO), + VV(VV_OPTION_NEW, "option_new", VAR_STRING, VV_RO), + VV(VV_OPTION_OLD, "option_old", VAR_STRING, VV_RO), + VV(VV_OPTION_OLDLOCAL, "option_oldlocal", VAR_STRING, VV_RO), + VV(VV_OPTION_OLDGLOBAL, "option_oldglobal", VAR_STRING, VV_RO), + VV(VV_OPTION_COMMAND, "option_command", VAR_STRING, VV_RO), + VV(VV_OPTION_TYPE, "option_type", VAR_STRING, VV_RO), + VV(VV_ERRORS, "errors", VAR_LIST, 0), + VV(VV_FALSE, "false", VAR_BOOL, VV_RO), + VV(VV_TRUE, "true", VAR_BOOL, VV_RO), + VV(VV_NULL, "null", VAR_SPECIAL, VV_RO), + VV(VV_NUMBERMAX, "numbermax", VAR_NUMBER, VV_RO), + VV(VV_NUMBERMIN, "numbermin", VAR_NUMBER, VV_RO), + VV(VV_NUMBERSIZE, "numbersize", VAR_NUMBER, VV_RO), + VV(VV_VIM_DID_ENTER, "vim_did_enter", VAR_NUMBER, VV_RO), + VV(VV_TESTING, "testing", VAR_NUMBER, 0), + VV(VV_TYPE_NUMBER, "t_number", VAR_NUMBER, VV_RO), + VV(VV_TYPE_STRING, "t_string", VAR_NUMBER, VV_RO), + VV(VV_TYPE_FUNC, "t_func", VAR_NUMBER, VV_RO), + VV(VV_TYPE_LIST, "t_list", VAR_NUMBER, VV_RO), + VV(VV_TYPE_DICT, "t_dict", VAR_NUMBER, VV_RO), + VV(VV_TYPE_FLOAT, "t_float", VAR_NUMBER, VV_RO), + VV(VV_TYPE_BOOL, "t_bool", VAR_NUMBER, VV_RO), + VV(VV_TYPE_BLOB, "t_blob", VAR_NUMBER, VV_RO), + VV(VV_EVENT, "event", VAR_DICT, VV_RO), + VV(VV_ECHOSPACE, "echospace", VAR_NUMBER, VV_RO), + VV(VV_ARGV, "argv", VAR_LIST, VV_RO), + VV(VV_COLLATE, "collate", VAR_STRING, VV_RO), + VV(VV_EXITING, "exiting", VAR_NUMBER, VV_RO), // Neovim - VV(VV_STDERR, "stderr", VAR_NUMBER, VV_RO), - VV(VV_MSGPACK_TYPES, "msgpack_types", VAR_DICT, VV_RO), - VV(VV__NULL_STRING, "_null_string", VAR_STRING, VV_RO), - VV(VV__NULL_LIST, "_null_list", VAR_LIST, VV_RO), - VV(VV__NULL_DICT, "_null_dict", VAR_DICT, VV_RO), - VV(VV__NULL_BLOB, "_null_blob", VAR_BLOB, VV_RO), - VV(VV_LUA, "lua", VAR_PARTIAL, VV_RO), + VV(VV_STDERR, "stderr", VAR_NUMBER, VV_RO), + VV(VV_MSGPACK_TYPES, "msgpack_types", VAR_DICT, VV_RO), + VV(VV__NULL_STRING, "_null_string", VAR_STRING, VV_RO), + VV(VV__NULL_LIST, "_null_list", VAR_LIST, VV_RO), + VV(VV__NULL_DICT, "_null_dict", VAR_DICT, VV_RO), + VV(VV__NULL_BLOB, "_null_blob", VAR_BLOB, VV_RO), + VV(VV_LUA, "lua", VAR_PARTIAL, VV_RO), }; #undef VV @@ -298,7 +304,32 @@ const list_T *eval_msgpack_type_lists[] = { [kMPExt] = NULL, }; -// Return "n1" divided by "n2", taking care of dividing by zero. +dict_T *get_v_event(save_v_event_T *sve) +{ + dict_T *v_event = get_vim_var_dict(VV_EVENT); + + if (v_event->dv_hashtab.ht_used > 0) { + // recursive use of v:event, save, make empty and restore later + sve->sve_did_save = true; + sve->sve_hashtab = v_event->dv_hashtab; + hash_init(&v_event->dv_hashtab); + } else { + sve->sve_did_save = false; + } + return v_event; +} + +void restore_v_event(dict_T *v_event, save_v_event_T *sve) +{ + tv_dict_free_contents(v_event); + if (sve->sve_did_save) { + v_event->dv_hashtab = sve->sve_hashtab; + } else { + hash_init(&v_event->dv_hashtab); + } +} + +/// @return "n1" divided by "n2", taking care of dividing by zero. varnumber_T num_divide(varnumber_T n1, varnumber_T n2) FUNC_ATTR_CONST FUNC_ATTR_WARN_UNUSED_RESULT { @@ -319,7 +350,7 @@ varnumber_T num_divide(varnumber_T n1, varnumber_T n2) return result; } -// Return "n1" modulus "n2", taking care of dividing by zero. +/// @return "n1" modulus "n2", taking care of dividing by zero. varnumber_T num_modulus(varnumber_T n1, varnumber_T n2) FUNC_ATTR_CONST FUNC_ATTR_WARN_UNUSED_RESULT { @@ -327,9 +358,7 @@ varnumber_T num_modulus(varnumber_T n1, varnumber_T n2) return (n2 == 0) ? 0 : (n1 % n2); } -/* - * Initialize the global and v: variables. - */ +/// Initialize the global and v: variables. void eval_init(void) { vimvars[VV_VERSION].vv_nr = VIM_VERSION_100; @@ -344,7 +373,7 @@ void eval_init(void) for (size_t i = 0; i < ARRAY_SIZE(vimvars); i++) { p = &vimvars[i]; - assert(STRLEN(p->vv_name) <= 16); + assert(STRLEN(p->vv_name) <= VIMVAR_KEY_LEN); STRCPY(p->vv_di.di_key, p->vv_name); if (p->vv_flags & VV_RO) { p->vv_di.di_flags = DI_FLAGS_RO | DI_FLAGS_FIX; @@ -469,10 +498,8 @@ void eval_clear(void) #endif -/* - * Set an internal variable to a string value. Creates the variable if it does - * not already exist. - */ +/// Set an internal variable to a string value. Creates the variable if it does +/// not already exist. void set_internal_string_var(const char *name, char_u *value) FUNC_ATTR_NONNULL_ARG(1) { @@ -490,9 +517,10 @@ static char_u *redir_endp = NULL; static char_u *redir_varname = NULL; /// Start recording command output to a variable -/// Returns OK if successfully completed the setup. FAIL otherwise. /// /// @param append append to an existing variable +/// +/// @return OK if successfully completed the setup. FAIL otherwise. int var_redir_start(char_u *name, int append) { int save_emsg; @@ -553,15 +581,13 @@ int var_redir_start(char_u *name, int append) return OK; } -/* - * Append "value[value_len]" to the variable set by var_redir_start(). - * The actual appending is postponed until redirection ends, because the value - * appended may in fact be the string we write to, changing it may cause freed - * memory to be used: - * :redir => foo - * :let foo - * :redir END - */ +/// Append "value[value_len]" to the variable set by var_redir_start(). +/// The actual appending is postponed until redirection ends, because the value +/// appended may in fact be the string we write to, changing it may cause freed +/// memory to be used: +/// :redir => foo +/// :let foo +/// :redir END void var_redir_str(char_u *value, int value_len) { int len; @@ -581,10 +607,8 @@ void var_redir_str(char_u *value, int value_len) redir_ga.ga_len += len; } -/* - * Stop redirecting command output to a variable. - * Frees the allocated memory. - */ +/// Stop redirecting command output to a variable. +/// Frees the allocated memory. void var_redir_stop(void) { typval_T tv; @@ -711,7 +735,7 @@ int eval_to_bool(char_u *arg, bool *error, char_u **nextcmd, int skip) return retval; } -// Call eval1() and give an error message if not done at a lower level. +/// Call eval1() and give an error message if not done at a lower level. static int eval1_emsg(char_u **arg, typval_T *rettv, bool evaluate) FUNC_ATTR_NONNULL_ARG(1, 2) { @@ -734,6 +758,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) { @@ -825,10 +858,9 @@ char *eval_to_string_skip(const char *arg, const char **nextcmd, const bool skip return retval; } -/* - * Skip over an expression at "*pp". - * Return FAIL for an error, OK otherwise. - */ +/// Skip over an expression at "*pp". +/// +/// @return FAIL for an error, OK otherwise. int skip_expr(char_u **pp) { typval_T rettv; @@ -875,10 +907,10 @@ char_u *eval_to_string(char_u *arg, char_u **nextcmd, bool convert) return (char_u *)retval; } -/* - * Call eval_to_string() without using current local variables and using - * textlock. When "use_sandbox" is TRUE use the sandbox. - */ +/// Call eval_to_string() without using current local variables and using +/// textlock. +/// +/// @param use_sandbox when TRUE, use the sandbox. char_u *eval_to_string_safe(char_u *arg, char_u **nextcmd, int use_sandbox) { char_u *retval; @@ -898,11 +930,10 @@ char_u *eval_to_string_safe(char_u *arg, char_u **nextcmd, int use_sandbox) return retval; } -/* - * Top level evaluation function, returning a number. - * Evaluates "expr" silently. - * Returns -1 for an error. - */ +/// Top level evaluation function, returning a number. +/// Evaluates "expr" silently. +/// +/// @return -1 for an error. varnumber_T eval_to_number(char_u *expr) { typval_T rettv; @@ -922,9 +953,10 @@ varnumber_T eval_to_number(char_u *expr) return retval; } -// Top level evaluation function. -// Returns an allocated typval_T with the result. -// Returns NULL when there is an error. +/// Top level evaluation function. +/// +/// @return an allocated typval_T with the result or +/// NULL when there is an error. typval_T *eval_expr(char_u *arg) { typval_T *tv = xmalloc(sizeof(*tv)); @@ -934,11 +966,9 @@ typval_T *eval_expr(char_u *arg) return tv; } -/* - * Prepare v: variable "idx" to be used. - * Save the current typeval in "save_tv". - * When not used yet add the variable to the v: hashtable. - */ +/// Prepare v: variable "idx" to be used. +/// Save the current typeval in "save_tv". +/// When not used yet add the variable to the v: hashtable. void prepare_vimvar(int idx, typval_T *save_tv) { *save_tv = vimvars[idx].vv_tv; @@ -947,10 +977,8 @@ void prepare_vimvar(int idx, typval_T *save_tv) } } -/* - * Restore v: variable "idx" to typeval "save_tv". - * When no longer defined, remove the variable from the v: hashtable. - */ +/// Restore v: variable "idx" to typeval "save_tv". +/// When no longer defined, remove the variable from the v: hashtable. void restore_vimvar(int idx, typval_T *save_tv) { hashitem_T *hi; @@ -977,11 +1005,10 @@ void find_win_for_curbuf(void) } } -/* - * Evaluate an expression to a list with suggestions. - * For the "expr:" part of 'spellsuggest'. - * Returns NULL when there is an error. - */ +/// Evaluate an expression to a list with suggestions. +/// For the "expr:" part of 'spellsuggest'. +/// +/// @return NULL when there is an error. list_T *eval_spell_expr(char_u *badword, char_u *expr) { typval_T save_val; @@ -1045,7 +1072,7 @@ int get_spellword(list_T *const list, const char **ret_word) // Uses argv[0] to argv[argc-1] for the function arguments. argv[argc] // should have type VAR_UNKNOWN. // -// Return OK or FAIL. +// @return OK or FAIL. int call_vim_function(const char_u *func, int argc, typval_T *argv, typval_T *rettv) FUNC_ATTR_NONNULL_ALL { @@ -1183,10 +1210,8 @@ void prof_child_exit(proftime_T *tm) } -/* - * Evaluate 'foldexpr'. Returns the foldlevel, and any character preceding - * it in "*cp". Doesn't give error messages. - */ +/// Evaluate 'foldexpr'. Returns the foldlevel, and any character preceding +/// it in "*cp". Doesn't give error messages. int eval_foldexpr(char_u *arg, int *cp) { typval_T tv; @@ -1227,26 +1252,27 @@ int eval_foldexpr(char_u *arg, int *cp) return (int)retval; } -// ":cons[t] var = expr1" define constant -// ":cons[t] [name1, name2, ...] = expr1" define constants unpacking list -// ":cons[t] [name, ..., ; lastname] = expr" define constants unpacking list +/// ":cons[t] var = expr1" define constant +/// ":cons[t] [name1, name2, ...] = expr1" define constants unpacking list +/// ":cons[t] [name, ..., ; lastname] = expr" define constants unpacking list void ex_const(exarg_T *eap) { ex_let_const(eap, true); } -// Get a list of lines from a HERE document. The here document is a list of -// lines surrounded by a marker. -// cmd << {marker} -// {line1} -// {line2} -// .... -// {marker} -// -// The {marker} is a string. If the optional 'trim' word is supplied before the -// marker, then the leading indentation before the lines (matching the -// indentation in the 'cmd' line) is stripped. -// Returns a List with {lines} or NULL. +/// Get a list of lines from a HERE document. The here document is a list of +/// lines surrounded by a marker. +/// cmd << {marker} +/// {line1} +/// {line2} +/// .... +/// {marker} +/// +/// The {marker} is a string. If the optional 'trim' word is supplied before the +/// marker, then the leading indentation before the lines (matching the +/// indentation in the 'cmd' line) is stripped. +/// +/// @return a List with {lines} or NULL. static list_T *heredoc_get(exarg_T *eap, char_u *cmd) { char_u *marker; @@ -1344,18 +1370,18 @@ static list_T *heredoc_get(exarg_T *eap, char_u *cmd) return l; } -// ":let" list all variable values -// ":let var1 var2" list variable values -// ":let var = expr" assignment command. -// ":let var += expr" assignment command. -// ":let var -= expr" assignment command. -// ":let var *= expr" assignment command. -// ":let var /= expr" assignment command. -// ":let var %= expr" assignment command. -// ":let var .= expr" assignment command. -// ":let var ..= expr" assignment command. -// ":let [var1, var2] = expr" unpack list. -// ":let [name, ..., ; lastname] = expr" unpack list. +/// ":let" list all variable values +/// ":let var1 var2" list variable values +/// ":let var = expr" assignment command. +/// ":let var += expr" assignment command. +/// ":let var -= expr" assignment command. +/// ":let var *= expr" assignment command. +/// ":let var /= expr" assignment command. +/// ":let var %= expr" assignment command. +/// ":let var .= expr" assignment command. +/// ":let var ..= expr" assignment command. +/// ":let [var1, var2] = expr" unpack list. +/// ":let [name, ..., ; lastname] = expr" unpack list. void ex_let(exarg_T *eap) { ex_let_const(eap, false); @@ -1536,13 +1562,12 @@ static int ex_let_vars(char_u *arg_start, typval_T *tv, int copy, int semicolon, return OK; } -/* - * Skip over assignable variable "var" or list of variables "[var, var]". - * Used for ":let varvar = expr" and ":for varvar in expr". - * For "[var, var]" increment "*var_count" for each variable. - * for "[var, var; var]" set "semicolon". - * Return NULL for an error. - */ +/// Skip over assignable variable "var" or list of variables "[var, var]". +/// Used for ":let varvar = expr" and ":for varvar in expr". +/// For "[var, var]" increment "*var_count" for each variable. +/// for "[var, var; var]" set "semicolon". +/// +/// @return NULL for an error. static const char_u *skip_var_list(const char_u *arg, int *var_count, int *semicolon) { const char_u *p; @@ -1580,10 +1605,8 @@ static const char_u *skip_var_list(const char_u *arg, int *var_count, int *semic } } -/* - * Skip one (assignable) variable name, including @r, $VAR, &option, d.key, - * l[idx]. - */ +/// Skip one (assignable) variable name, including @r, $VAR, &option, d.key, +/// l[idx]. static const char_u *skip_var_one(const char_u *arg) { if (*arg == '@' && arg[1] != NUL) { @@ -1593,10 +1616,9 @@ static const char_u *skip_var_one(const char_u *arg) NULL, NULL, FNE_INCL_BR | FNE_CHECK_START); } -/* - * List variables for hashtab "ht" with prefix "prefix". - * If "empty" is TRUE also list NULL strings as empty strings. - */ +/// List variables for hashtab "ht" with prefix "prefix". +/// +/// @param empty if TRUE also list NULL strings as empty strings. void list_hashtable_vars(hashtab_T *ht, const char *prefix, int empty, int *first) { hashitem_T *hi; @@ -1625,47 +1647,37 @@ void list_hashtable_vars(hashtab_T *ht, const char *prefix, int empty, int *firs } } -/* - * List global variables. - */ +/// List global variables. static void list_glob_vars(int *first) { list_hashtable_vars(&globvarht, "", true, first); } -/* - * List buffer variables. - */ +/// List buffer variables. static void list_buf_vars(int *first) { list_hashtable_vars(&curbuf->b_vars->dv_hashtab, "b:", true, first); } -/* - * List window variables. - */ +/// List window variables. static void list_win_vars(int *first) { list_hashtable_vars(&curwin->w_vars->dv_hashtab, "w:", true, first); } -/* - * List tab page variables. - */ +/// List tab page variables. static void list_tab_vars(int *first) { list_hashtable_vars(&curtab->tp_vars->dv_hashtab, "t:", true, first); } -/* - * List Vim variables. - */ +/// List Vim variables. static void list_vim_vars(int *first) { list_hashtable_vars(&vimvarht, "v:", false, first); } -// List script-local variables, if there is a script. +/// List script-local variables, if there is a script. static void list_script_vars(int *first) { if (current_sctx.sc_sid > 0 && current_sctx.sc_sid <= ga_scripts.ga_len) { @@ -1673,9 +1685,7 @@ static void list_script_vars(int *first) } } -/* - * List variables in "arg". - */ +/// List variables in "arg". static const char *list_arg_vars(exarg_T *eap, const char *arg, int *first) { int error = FALSE; @@ -2338,9 +2348,7 @@ char_u *get_lval(char_u *const name, typval_T *const rettv, lval_T *const lp, co // TODO(ZyX-I): move to eval/executor -/* - * Clear lval "lp" that was filled by get_lval(). - */ +/// Clear lval "lp" that was filled by get_lval(). void clear_lval(lval_T *lp) { xfree(lp->ll_exp_name); @@ -2349,12 +2357,11 @@ void clear_lval(lval_T *lp) // TODO(ZyX-I): move to eval/executor -/* - * Set a variable that was parsed by get_lval() to "rettv". - * "endp" points to just after the parsed name. - * "op" is NULL, "+" for "+=", "-" for "-=", "*" for "*=", "/" for "/=", - * "%" for "%=", "." for ".=" or "=" for "=". - */ +/// Set a variable that was parsed by get_lval() to "rettv". +/// +/// @param endp points to just after the parsed name. +/// @param op NULL, "+" for "+=", "-" for "-=", "*" for "*=", "/" for "/=", +/// "%" for "%=", "." for ".=" or "=" for "=". static void set_var_lval(lval_T *lp, char_u *endp, typval_T *rettv, int copy, const bool is_const, const char *op) { @@ -2557,12 +2564,12 @@ notify: // TODO(ZyX-I): move to eval/ex_cmds -/* - * Evaluate the expression used in a ":for var in expr" command. - * "arg" points to "var". - * Set "*errp" to TRUE for an error, FALSE otherwise; - * Return a pointer that holds the info. Null when there is an error. - */ +/// Evaluate the expression used in a ":for var in expr" command. +/// "arg" points to "var". +/// +/// @param[out] *errp set to TRUE for an error, FALSE otherwise; +/// +/// @return a pointer that holds the info. Null when there is an error. void *eval_for_line(const char_u *arg, bool *errp, char_u **nextcmdp, int skip) { forinfo_T *fi = xcalloc(1, sizeof(forinfo_T)); @@ -2612,8 +2619,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); } } @@ -2627,12 +2641,11 @@ void *eval_for_line(const char_u *arg, bool *errp, char_u **nextcmdp, int skip) // TODO(ZyX-I): move to eval/ex_cmds -/* - * Use the first item in a ":for" list. Advance to the next. - * Assign the values to the variable (list). "arg" points to the first one. - * Return TRUE when a valid item was found, FALSE when at end of list or - * something wrong. - */ +/// Use the first item in a ":for" list. Advance to the next. +/// Assign the values to the variable (list). "arg" points to the first one. +/// +/// @return true when a valid item was found, false when at end of list or +/// something wrong. bool next_for_item(void *fi_void, char_u *arg) { forinfo_T *fi = (forinfo_T *)fi_void; @@ -2650,6 +2663,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; @@ -2662,19 +2691,21 @@ bool next_for_item(void *fi_void, char_u *arg) // TODO(ZyX-I): move to eval/ex_cmds -/* - * Free the structure used to store info used by ":for". - */ +/// Free the structure used to store info used by ":for". 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); } @@ -3109,9 +3140,7 @@ static int do_lock_var(lval_T *lp, char_u *name_end FUNC_ATTR_UNUSED, exarg_T *e return ret; } -/* - * Delete all "menutrans_" variables. - */ +/// Delete all "menutrans_" variables. void del_menutrans_vars(void) { hash_lock(&globvarht); @@ -3133,9 +3162,7 @@ void del_menutrans_vars(void) static char_u *varnamebuf = NULL; static size_t varnamebuflen = 0; -/* - * Function to concatenate a prefix and a variable name. - */ +/// Function to concatenate a prefix and a variable name. char_u *cat_prefix_varname(int prefix, const char_u *name) FUNC_ATTR_NONNULL_ALL { @@ -3153,10 +3180,8 @@ char_u *cat_prefix_varname(int prefix, const char_u *name) return varnamebuf; } -/* - * Function given to ExpandGeneric() to obtain the list of user defined - * (global/buffer/window/built-in) variable names. - */ +/// Function given to ExpandGeneric() to obtain the list of user defined +/// (global/buffer/window/built-in) variable names. char_u *get_user_var_name(expand_T *xp, int idx) { static size_t gdone; @@ -3188,10 +3213,7 @@ 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; + const hashtab_T *ht = &prevwin_curwin()->w_buffer->b_vars->dv_hashtab; if (bdone < ht->ht_used) { if (bdone++ == 0) { hi = ht->ht_array; @@ -3205,10 +3227,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 = &prevwin_curwin()->w_vars->dv_hashtab; if (wdone < ht->ht_used) { if (wdone++ == 0) { hi = ht->ht_array; @@ -3247,9 +3266,10 @@ char_u *get_user_var_name(expand_T *xp, int idx) // TODO(ZyX-I): move to eval/expressions -/// Return TRUE if "pat" matches "text". /// Does not use 'cpo' and always uses 'magic'. -static int pattern_match(char_u *pat, char_u *text, bool ic) +/// +/// @return TRUE if "pat" matches "text". +int pattern_match(char_u *pat, char_u *text, bool ic) { int matches = 0; regmatch_T regmatch; @@ -3269,10 +3289,10 @@ static int pattern_match(char_u *pat, char_u *text, bool ic) /// Handle a name followed by "(". Both for just "name(arg)" and for /// "expr->name(arg)". -// +/// /// @param arg Points to "(", will be advanced /// @param basetv "expr" for "expr->name(arg)" -// +/// /// @return OK or FAIL. static int eval_func(char_u **const arg, char_u *const name, const int name_len, typval_T *const rettv, const bool evaluate, typval_T *const basetv) @@ -3333,13 +3353,12 @@ static int eval_func(char_u **const arg, char_u *const name, const int name_len, * VAR_UNKNOWN. The function still returns FAIL for a syntax error. */ -/* - * Handle zero level expression. - * This calls eval1() and handles error message and nextcmd. - * Put the result in "rettv" when returning OK and "evaluate" is TRUE. - * Note: "rettv.v_lock" is not set. - * Return OK or FAIL. - */ +/// Handle zero level expression. +/// This calls eval1() and handles error message and nextcmd. +/// Put the result in "rettv" when returning OK and "evaluate" is TRUE. +/// Note: "rettv.v_lock" is not set. +/// +/// @return OK or FAIL. int eval0(char_u *arg, typval_T *rettv, char_u **nextcmd, int evaluate) { int ret; @@ -3372,17 +3391,15 @@ int eval0(char_u *arg, typval_T *rettv, char_u **nextcmd, int evaluate) // TODO(ZyX-I): move to eval/expressions -/* - * Handle top level expression: - * expr2 ? expr1 : expr1 - * - * "arg" must point to the first non-white of the expression. - * "arg" is advanced to the next non-white after the recognized expression. - * - * Note: "rettv.v_lock" is not set. - * - * Return OK or FAIL. - */ +/// Handle top level expression: +/// expr2 ? expr1 : expr1 +/// +/// "arg" must point to the first non-white of the expression. +/// "arg" is advanced to the next non-white after the recognized expression. +/// +/// Note: "rettv.v_lock" is not set. +/// +/// @return OK or FAIL. int eval1(char_u **arg, typval_T *rettv, int evaluate) { int result; @@ -3448,15 +3465,13 @@ int eval1(char_u **arg, typval_T *rettv, int evaluate) // TODO(ZyX-I): move to eval/expressions -/* - * Handle first level expression: - * expr2 || expr2 || expr2 logical OR - * - * "arg" must point to the first non-white of the expression. - * "arg" is advanced to the next non-white after the recognized expression. - * - * Return OK or FAIL. - */ +/// Handle first level expression: +/// expr2 || expr2 || expr2 logical OR +/// +/// "arg" must point to the first non-white of the expression. +/// "arg" is advanced to the next non-white after the recognized expression. +/// +/// @return OK or FAIL. static int eval2(char_u **arg, typval_T *rettv, int evaluate) { typval_T var2; @@ -3519,15 +3534,13 @@ static int eval2(char_u **arg, typval_T *rettv, int evaluate) // TODO(ZyX-I): move to eval/expressions -/* - * Handle second level expression: - * expr3 && expr3 && expr3 logical AND - * - * "arg" must point to the first non-white of the expression. - * "arg" is advanced to the next non-white after the recognized expression. - * - * Return OK or FAIL. - */ +/// Handle second level expression: +/// expr3 && expr3 && expr3 logical AND +/// +/// @param arg must point to the first non-white of the expression. +/// `arg` is advanced to the next non-white after the recognized expression. +/// +/// @return OK or FAIL. static int eval3(char_u **arg, typval_T *rettv, int evaluate) { typval_T var2; @@ -3590,24 +3603,22 @@ static int eval3(char_u **arg, typval_T *rettv, int evaluate) // TODO(ZyX-I): move to eval/expressions -/* - * Handle third level expression: - * var1 == var2 - * var1 =~ var2 - * var1 != var2 - * var1 !~ var2 - * var1 > var2 - * var1 >= var2 - * var1 < var2 - * var1 <= var2 - * var1 is var2 - * var1 isnot var2 - * - * "arg" must point to the first non-white of the expression. - * "arg" is advanced to the next non-white after the recognized expression. - * - * Return OK or FAIL. - */ +/// Handle third level expression: +/// var1 == var2 +/// var1 =~ var2 +/// var1 != var2 +/// var1 !~ var2 +/// var1 > var2 +/// var1 >= var2 +/// var1 < var2 +/// var1 <= var2 +/// var1 is var2 +/// var1 isnot var2 +/// +/// "arg" must point to the first non-white of the expression. +/// "arg" is advanced to the next non-white after the recognized expression. +/// +/// @return OK or FAIL. static int eval4(char_u **arg, typval_T *rettv, int evaluate) { typval_T var2; @@ -3701,18 +3712,16 @@ static int eval4(char_u **arg, typval_T *rettv, int evaluate) // TODO(ZyX-I): move to eval/expressions -/* - * Handle fourth level expression: - * + number addition - * - number subtraction - * . string concatenation - * .. string concatenation - * - * "arg" must point to the first non-white of the expression. - * "arg" is advanced to the next non-white after the recognized expression. - * - * Return OK or FAIL. - */ +/// Handle fourth level expression: +/// + number addition +/// - number subtraction +/// . string concatenation +/// .. string concatenation +/// +/// @param arg must point to the first non-white of the expression. +/// `arg` is advanced to the next non-white after the recognized expression. +/// +/// @return OK or FAIL. static int eval5(char_u **arg, typval_T *rettv, int evaluate) { typval_T var2; @@ -3965,18 +3974,11 @@ static int eval6(char_u **arg, typval_T *rettv, int evaluate, int want_string) f1 = f1 * f2; } else if (op == '/') { // Division by zero triggers error from AddressSanitizer - f1 = (f2 == 0 - ? ( + f1 = (f2 == 0 ? ( #ifdef NAN - f1 == 0 - ? NAN - : + f1 == 0 ? (float_T)NAN : #endif - (f1 > 0 - ? INFINITY - : -INFINITY) - ) - : f1 / f2); + (f1 > 0 ? (float_T)INFINITY : (float_T)-INFINITY)) : f1 / f2); } else { emsg(_("E804: Cannot use '%' with Float")); return FAIL; @@ -4258,7 +4260,8 @@ static int eval7(char_u **arg, typval_T *rettv, int evaluate, int want_string) /// Apply the leading "!" and "-" before an eval7 expression to "rettv". /// Adjusts "end_leaderp" until it is at "start_leader". -/// @return OK on success, FAIL on failure. +/// +/// @return OK on success, FAIL on failure. static int eval7_leader(typval_T *const rettv, const char_u *const start_leader, const char_u **const end_leaderp) FUNC_ATTR_NONNULL_ALL @@ -4312,7 +4315,7 @@ static int eval7_leader(typval_T *const rettv, const char_u *const start_leader, /// @param lua_funcname If `rettv` refers to a v:lua function, this must point /// to the name of the Lua function to call (after the /// "v:lua." prefix). -/// @return OK on success, FAIL on failure. +/// @return OK on success, FAIL on failure. static int call_func_rettv(char_u **const arg, typval_T *const rettv, const bool evaluate, dict_T *const selfdict, typval_T *const basetv, const char_u *const lua_funcname) @@ -4360,9 +4363,13 @@ static int call_func_rettv(char_u **const arg, typval_T *const rettv, const bool } /// Evaluate "->method()". +/// /// @param verbose if true, give error messages. -/// @note "*arg" points to the '-'. -/// @return FAIL or OK. @note "*arg" is advanced to after the ')'. +/// @param *arg points to the '-'. +/// +/// @return FAIL or OK. +/// +/// @note "*arg" is advanced to after the ')'. static int eval_lambda(char_u **const arg, typval_T *const rettv, const bool evaluate, const bool verbose) FUNC_ATTR_NONNULL_ALL @@ -4373,7 +4380,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) { @@ -4399,8 +4406,10 @@ static int eval_lambda(char_u **const arg, typval_T *const rettv, const bool eva } /// Evaluate "->method()" or "->v:lua.method()". -/// @note "*arg" points to the '-'. -/// @return FAIL or OK. "*arg" is advanced to after the ')'. +/// +/// @param *arg points to the '-'. +/// +/// @return FAIL or OK. "*arg" is advanced to after the ')'. static int eval_method(char_u **const arg, typval_T *const rettv, const bool evaluate, const bool verbose) FUNC_ATTR_NONNULL_ALL @@ -4473,9 +4482,10 @@ static int eval_method(char_u **const arg, typval_T *const rettv, const bool eva /// Evaluate an "[expr]" or "[expr:expr]" index. Also "dict.key". /// "*arg" points to the '[' or '.'. -/// Returns FAIL or OK. "*arg" is advanced to after the ']'. /// /// @param verbose give error messages +/// +/// @returns FAIL or OK. "*arg" is advanced to after the ']'. static int eval_index(char_u **arg, typval_T *rettv, int evaluate, int verbose) { bool empty1 = false; @@ -4523,7 +4533,7 @@ static int eval_index(char_u **arg, typval_T *rettv, int evaluate, int verbose) * dict.name */ key = *arg + 1; - for (len = 0; ASCII_ISALNUM(key[len]) || key[len] == '_'; ++len) { + for (len = 0; ASCII_ISALNUM(key[len]) || key[len] == '_'; len++) { } if (len == 0) { return FAIL; @@ -4780,19 +4790,18 @@ static int eval_index(char_u **arg, typval_T *rettv, int evaluate, int verbose) /// Get an option value /// -/// @param[in,out] arg Points to the '&' or '+' before the option name. Is +/// @param[in,out] arg Points to the '&' or '+' before the option name. Is /// advanced to the character after the option name. -/// @param[out] rettv Location where result is saved. -/// @param[in] evaluate If not true, rettv is not populated. +/// @param[out] rettv Location where result is saved. +/// @param[in] evaluate If not true, rettv is not populated. /// -/// @return OK or FAIL. +/// @return OK or FAIL. int get_option_tv(const char **const arg, typval_T *const rettv, const bool evaluate) FUNC_ATTR_NONNULL_ARG(1) { long numval; char_u *stringval; int opt_type; - int c; bool working = (**arg == '+'); // has("+option") int ret = OK; int opt_flags; @@ -4811,7 +4820,7 @@ int get_option_tv(const char **const arg, typval_T *const rettv, const bool eval return OK; } - c = *option_end; + char c = *option_end; *option_end = NUL; opt_type = get_option_value(*arg, &numval, rettv == NULL ? NULL : &stringval, opt_flags); @@ -4828,7 +4837,7 @@ int get_option_tv(const char **const arg, typval_T *const rettv, const bool eval } else if (opt_type == -1) { // hidden number option rettv->v_type = VAR_NUMBER; rettv->vval.v_number = 0; - } else if (opt_type == 1) { // number option + } else if (opt_type == 1 || opt_type == 2) { // number or boolean option rettv->v_type = VAR_NUMBER; rettv->vval.v_number = numval; } else { // string option @@ -4845,10 +4854,9 @@ int get_option_tv(const char **const arg, typval_T *const rettv, const bool eval return ret; } -/* - * Allocate a variable for a string constant. - * Return OK or FAIL. - */ +/// Allocate a variable for a string constant. +/// +/// @return OK or FAIL. static int get_string_tv(char_u **arg, typval_T *rettv, int evaluate) { char_u *p; @@ -4985,10 +4993,9 @@ static int get_string_tv(char_u **arg, typval_T *rettv, int evaluate) return OK; } -/* - * Allocate a variable for a 'str''ing' constant. - * Return OK or FAIL. - */ +/// Allocate a variable for a 'str''ing' constant. +/// +/// @return OK or FAIL. static int get_lit_string_tv(char_u **arg, typval_T *rettv, int evaluate) { char_u *p; @@ -5041,7 +5048,7 @@ static int get_lit_string_tv(char_u **arg, typval_T *rettv, int evaluate) return OK; } -/// @return the function name of the partial. +/// @return the function name of the partial. char_u *partial_name(partial_T *pt) { if (pt->pt_name != NULL) { @@ -5080,7 +5087,8 @@ void partial_unref(partial_T *pt) } /// Allocate a variable for a List and fill it from "*arg". -/// Return OK or FAIL. +/// +/// @return OK or FAIL. static int get_list_tv(char_u **arg, typval_T *rettv, int evaluate) { list_T *l = NULL; @@ -5218,7 +5226,8 @@ int get_copyID(void) /// Do garbage collection for lists and dicts. /// /// @param testing true if called from test_garbagecollect_now(). -/// @returns true if some memory was freed. +/// +/// @return true if some memory was freed. bool garbage_collect(bool testing) { bool abort = false; @@ -5396,10 +5405,11 @@ bool garbage_collect(bool testing) /// Free lists and dictionaries that are no longer referenced. /// -/// @note This function may only be called from garbage_collect(). +/// @note This function may only be called from garbage_collect(). /// -/// @param copyID Free lists/dictionaries that don't have this ID. -/// @return true, if something was freed. +/// @param copyID Free lists/dictionaries that don't have this ID. +/// +/// @return true, if something was freed. static int free_unref_items(int copyID) { bool did_free = false; @@ -5634,7 +5644,7 @@ bool set_ref_in_item(typval_T *tv, int copyID, ht_stack_T **ht_stack, list_stack /// Mark all lists and dicts referenced in given mark /// -/// @returns true if setting references failed somehow. +/// @return true if setting references failed somehow. static inline bool set_ref_in_fmark(fmark_T fm, int copyID) FUNC_ATTR_WARN_UNUSED_RESULT { @@ -5648,7 +5658,7 @@ static inline bool set_ref_in_fmark(fmark_T fm, int copyID) /// Mark all lists and dicts referenced in given list and the list itself /// -/// @returns true if setting references failed somehow. +/// @return true if setting references failed somehow. static inline bool set_ref_list(list_T *list, int copyID) FUNC_ATTR_WARN_UNUSED_RESULT { @@ -5664,7 +5674,7 @@ static inline bool set_ref_list(list_T *list, int copyID) /// Mark all lists and dicts referenced in given dict and the dict itself /// -/// @returns true if setting references failed somehow. +/// @return true if setting references failed somehow. static inline bool set_ref_dict(dict_T *dict, int copyID) FUNC_ATTR_WARN_UNUSED_RESULT { @@ -5679,8 +5689,9 @@ static inline bool set_ref_dict(dict_T *dict, int copyID) } -// Get the key for *{key: val} into "tv" and advance "arg". -// Return FAIL when there is no valid key. +/// Get the key for *{key: val} into "tv" and advance "arg". +/// +/// @return FAIL when there is no valid key. static int get_literal_key(char_u **arg, typval_T *tv) FUNC_ATTR_NONNULL_ALL { @@ -5698,9 +5709,10 @@ static int get_literal_key(char_u **arg, typval_T *tv) return OK; } -// Allocate a variable for a Dictionary and fill it from "*arg". -// "literal" is true for *{key: val} -// Return OK or FAIL. Returns NOTDONE for {expr}. +/// Allocate a variable for a Dictionary and fill it from "*arg". +/// "literal" is true for *{key: val} +/// +/// @return OK or FAIL. Returns NOTDONE for {expr}. static int dict_get_tv(char_u **arg, typval_T *rettv, int evaluate, bool literal) { dict_T *d = NULL; @@ -5810,10 +5822,10 @@ failret: /// This uses strtod(). setlocale(LC_NUMERIC, "C") has been used earlier to /// make sure this always uses a decimal point. /// -/// @param[in] text String to convert. -/// @param[out] ret_value Location where conversion result is saved. +/// @param[in] text String to convert. +/// @param[out] ret_value Location where conversion result is saved. /// -/// @return Length of the text that was consumed. +/// @return Length of the text that was consumed. size_t string2float(const char *const text, float_T *const ret_value) FUNC_ATTR_NONNULL_ALL { @@ -5821,15 +5833,15 @@ size_t string2float(const char *const text, float_T *const ret_value) // MS-Windows does not deal with "inf" and "nan" properly if (STRNICMP(text, "inf", 3) == 0) { - *ret_value = INFINITY; + *ret_value = (float_T)INFINITY; return 3; } if (STRNICMP(text, "-inf", 3) == 0) { - *ret_value = -INFINITY; + *ret_value = (float_T)-INFINITY; return 4; } if (STRNICMP(text, "nan", 3) == 0) { - *ret_value = NAN; + *ret_value = (float_T)NAN; return 3; } *ret_value = strtod(text, &s); @@ -5840,9 +5852,9 @@ size_t string2float(const char *const text, float_T *const ret_value) /// /// If the environment variable was not set, silently assume it is empty. /// -/// @param arg Points to the '$'. It is advanced to after the name. -/// @return FAIL if the name is invalid. +/// @param arg Points to the '$'. It is advanced to after the name. /// +/// @return FAIL if the name is invalid. static int get_env_tv(char_u **arg, typval_T *rettv, int evaluate) { char_u *name; @@ -5891,145 +5903,7 @@ void get_arglist_as_rettv(aentry_T *arglist, int argcount, typval_T *rettv) } } -// Prepare "gap" for an assert error and add the sourcing position. -void prepare_assert_error(garray_T *gap) -{ - char buf[NUMBUFLEN]; - - ga_init(gap, 1, 100); - if (sourcing_name != NULL) { - ga_concat(gap, (char *)sourcing_name); - if (sourcing_lnum > 0) { - ga_concat(gap, " "); - } - } - if (sourcing_lnum > 0) { - vim_snprintf(buf, ARRAY_SIZE(buf), "line %" PRId64, (int64_t)sourcing_lnum); - ga_concat(gap, buf); - } - if (sourcing_name != NULL || sourcing_lnum > 0) { - ga_concat(gap, ": "); - } -} - -// Append "p[clen]" to "gap", escaping unprintable characters. -// Changes NL to \n, CR to \r, etc. -static void ga_concat_esc(garray_T *gap, const char_u *p, int clen) - FUNC_ATTR_NONNULL_ALL -{ - char_u buf[NUMBUFLEN]; - - if (clen > 1) { - memmove(buf, p, clen); - buf[clen] = NUL; - ga_concat(gap, (char *)buf); - } else { - switch (*p) { - case BS: - ga_concat(gap, "\\b"); break; - case ESC: - ga_concat(gap, "\\e"); break; - case FF: - ga_concat(gap, "\\f"); break; - case NL: - ga_concat(gap, "\\n"); break; - case TAB: - ga_concat(gap, "\\t"); break; - case CAR: - ga_concat(gap, "\\r"); break; - case '\\': - ga_concat(gap, "\\\\"); break; - default: - if (*p < ' ') { - vim_snprintf((char *)buf, NUMBUFLEN, "\\x%02x", *p); - ga_concat(gap, (char *)buf); - } else { - ga_append(gap, *p); - } - break; - } - } -} - -// Append "str" to "gap", escaping unprintable characters. -// Changes NL to \n, CR to \r, etc. -static void ga_concat_shorten_esc(garray_T *gap, const char_u *str) - FUNC_ATTR_NONNULL_ARG(1) -{ - char_u buf[NUMBUFLEN]; - - if (str == NULL) { - ga_concat(gap, "NULL"); - return; - } - - for (const char_u *p = str; *p != NUL; p++) { - int same_len = 1; - const char_u *s = p; - const int c = mb_ptr2char_adv(&s); - const int clen = s - p; - while (*s != NUL && c == utf_ptr2char(s)) { - same_len++; - s += clen; - } - if (same_len > 20) { - ga_concat(gap, "\\["); - ga_concat_esc(gap, p, clen); - ga_concat(gap, " occurs "); - vim_snprintf((char *)buf, NUMBUFLEN, "%d", same_len); - ga_concat(gap, (char *)buf); - ga_concat(gap, " times]"); - p = s - 1; - } else { - ga_concat_esc(gap, p, clen); - } - } -} - -// Fill "gap" with information about an assert error. -void fill_assert_error(garray_T *gap, typval_T *opt_msg_tv, char_u *exp_str, typval_T *exp_tv, - typval_T *got_tv, assert_type_T atype) -{ - char_u *tofree; - - if (opt_msg_tv->v_type != VAR_UNKNOWN) { - tofree = (char_u *)encode_tv2echo(opt_msg_tv, NULL); - ga_concat(gap, (char *)tofree); - xfree(tofree); - ga_concat(gap, ": "); - } - - if (atype == ASSERT_MATCH || atype == ASSERT_NOTMATCH) { - ga_concat(gap, "Pattern "); - } else if (atype == ASSERT_NOTEQUAL) { - ga_concat(gap, "Expected not equal to "); - } else { - ga_concat(gap, "Expected "); - } - - if (exp_str == NULL) { - tofree = (char_u *)encode_tv2string(exp_tv, NULL); - ga_concat_shorten_esc(gap, tofree); - xfree(tofree); - } else { - ga_concat_shorten_esc(gap, exp_str); - } - - if (atype != ASSERT_NOTEQUAL) { - if (atype == ASSERT_MATCH) { - ga_concat(gap, " does not match "); - } else if (atype == ASSERT_NOTMATCH) { - ga_concat(gap, " does match "); - } else { - ga_concat(gap, " but got "); - } - tofree = (char_u *)encode_tv2string(got_tv, NULL); - ga_concat_shorten_esc(gap, tofree); - xfree(tofree); - } -} - -// Add an assert error to v:errors. +/// Add an assert error to v:errors. void assert_error(garray_T *gap) { struct vimvar *vp = &vimvars[VV_ERRORS]; @@ -6042,328 +5916,6 @@ void assert_error(garray_T *gap) (const char *)gap->ga_data, (ptrdiff_t)gap->ga_len); } -int assert_equal_common(typval_T *argvars, assert_type_T atype) - FUNC_ATTR_NONNULL_ALL -{ - garray_T ga; - - if (tv_equal(&argvars[0], &argvars[1], false, false) - != (atype == ASSERT_EQUAL)) { - prepare_assert_error(&ga); - fill_assert_error(&ga, &argvars[2], NULL, - &argvars[0], &argvars[1], atype); - assert_error(&ga); - ga_clear(&ga); - return 1; - } - return 0; -} - -int assert_equalfile(typval_T *argvars) - FUNC_ATTR_NONNULL_ALL -{ - char buf1[NUMBUFLEN]; - char buf2[NUMBUFLEN]; - const char *const fname1 = tv_get_string_buf_chk(&argvars[0], buf1); - const char *const fname2 = tv_get_string_buf_chk(&argvars[1], buf2); - garray_T ga; - - if (fname1 == NULL || fname2 == NULL) { - return 0; - } - - IObuff[0] = NUL; - FILE *const fd1 = os_fopen(fname1, READBIN); - char line1[200]; - char line2[200]; - ptrdiff_t lineidx = 0; - if (fd1 == NULL) { - snprintf((char *)IObuff, IOSIZE, (char *)e_notread, fname1); - } else { - FILE *const fd2 = os_fopen(fname2, READBIN); - if (fd2 == NULL) { - fclose(fd1); - snprintf((char *)IObuff, IOSIZE, (char *)e_notread, fname2); - } else { - int64_t linecount = 1; - for (int64_t count = 0;; count++) { - const int c1 = fgetc(fd1); - const int c2 = fgetc(fd2); - if (c1 == EOF) { - if (c2 != EOF) { - STRCPY(IObuff, "first file is shorter"); - } - break; - } else if (c2 == EOF) { - STRCPY(IObuff, "second file is shorter"); - break; - } else { - line1[lineidx] = c1; - line2[lineidx] = c2; - lineidx++; - if (c1 != c2) { - snprintf((char *)IObuff, IOSIZE, - "difference at byte %" PRId64 ", line %" PRId64, - count, linecount); - break; - } - } - if (c1 == NL) { - linecount++; - lineidx = 0; - } else if (lineidx + 2 == (ptrdiff_t)sizeof(line1)) { - memmove(line1, line1 + 100, lineidx - 100); - memmove(line2, line2 + 100, lineidx - 100); - lineidx -= 100; - } - } - fclose(fd1); - fclose(fd2); - } - } - if (IObuff[0] != NUL) { - prepare_assert_error(&ga); - if (argvars[2].v_type != VAR_UNKNOWN) { - char *const tofree = encode_tv2echo(&argvars[2], NULL); - ga_concat(&ga, tofree); - xfree(tofree); - ga_concat(&ga, ": "); - } - ga_concat(&ga, (char *)IObuff); - if (lineidx > 0) { - line1[lineidx] = NUL; - line2[lineidx] = NUL; - ga_concat(&ga, " after \""); - ga_concat(&ga, line1); - if (STRCMP(line1, line2) != 0) { - ga_concat(&ga, "\" vs \""); - ga_concat(&ga, line2); - } - ga_concat(&ga, "\""); - } - assert_error(&ga); - ga_clear(&ga); - return 1; - } - return 0; -} - -int assert_inrange(typval_T *argvars) - FUNC_ATTR_NONNULL_ALL -{ - bool error = false; - - if (argvars[0].v_type == VAR_FLOAT - || argvars[1].v_type == VAR_FLOAT - || argvars[2].v_type == VAR_FLOAT) { - const float_T flower = tv_get_float(&argvars[0]); - const float_T fupper = tv_get_float(&argvars[1]); - const float_T factual = tv_get_float(&argvars[2]); - - if (factual < flower || factual > fupper) { - garray_T ga; - prepare_assert_error(&ga); - if (argvars[3].v_type != VAR_UNKNOWN) { - char_u *const tofree = (char_u *)encode_tv2string(&argvars[3], NULL); - ga_concat(&ga, (char *)tofree); - xfree(tofree); - } else { - char msg[80]; - vim_snprintf(msg, sizeof(msg), "Expected range %g - %g, but got %g", - flower, fupper, factual); - ga_concat(&ga, msg); - } - assert_error(&ga); - ga_clear(&ga); - return 1; - } - } else { - const varnumber_T lower = tv_get_number_chk(&argvars[0], &error); - const varnumber_T upper = tv_get_number_chk(&argvars[1], &error); - const varnumber_T actual = tv_get_number_chk(&argvars[2], &error); - - if (error) { - return 0; - } - if (actual < lower || actual > upper) { - garray_T ga; - prepare_assert_error(&ga); - - char msg[55]; - vim_snprintf(msg, sizeof(msg), - "range %" PRIdVARNUMBER " - %" PRIdVARNUMBER ",", - lower, upper); // -V576 - fill_assert_error(&ga, &argvars[3], (char_u *)msg, NULL, &argvars[2], - ASSERT_INRANGE); - assert_error(&ga); - ga_clear(&ga); - return 1; - } - } - return 0; -} - -// Common for assert_true() and assert_false(). -int assert_bool(typval_T *argvars, bool is_true) - FUNC_ATTR_NONNULL_ALL -{ - bool error = false; - garray_T ga; - - if ((argvars[0].v_type != VAR_NUMBER - || (tv_get_number_chk(&argvars[0], &error) == 0) == is_true - || error) - && (argvars[0].v_type != VAR_BOOL - || (argvars[0].vval.v_bool - != (BoolVarValue)(is_true - ? kBoolVarTrue - : kBoolVarFalse)))) { - prepare_assert_error(&ga); - fill_assert_error(&ga, &argvars[1], - (char_u *)(is_true ? "True" : "False"), - NULL, &argvars[0], ASSERT_OTHER); - assert_error(&ga); - ga_clear(&ga); - return 1; - } - return 0; -} - -int assert_exception(typval_T *argvars) - FUNC_ATTR_NONNULL_ALL -{ - garray_T ga; - - const char *const error = tv_get_string_chk(&argvars[0]); - if (vimvars[VV_EXCEPTION].vv_str == NULL) { - prepare_assert_error(&ga); - ga_concat(&ga, "v:exception is not set"); - assert_error(&ga); - ga_clear(&ga); - return 1; - } else if (error != NULL - && strstr((char *)vimvars[VV_EXCEPTION].vv_str, error) == NULL) { - prepare_assert_error(&ga); - fill_assert_error(&ga, &argvars[1], NULL, &argvars[0], - &vimvars[VV_EXCEPTION].vv_tv, ASSERT_OTHER); - assert_error(&ga); - ga_clear(&ga); - return 1; - } - return 0; -} - -static void assert_append_cmd_or_arg(garray_T *gap, typval_T *argvars, const char *cmd) - FUNC_ATTR_NONNULL_ALL -{ - if (argvars[1].v_type != VAR_UNKNOWN && argvars[2].v_type != VAR_UNKNOWN) { - char *const tofree = encode_tv2echo(&argvars[2], NULL); - ga_concat(gap, tofree); - xfree(tofree); - } else { - ga_concat(gap, cmd); - } -} - -int assert_beeps(typval_T *argvars, bool no_beep) - FUNC_ATTR_NONNULL_ALL -{ - const char *const cmd = tv_get_string_chk(&argvars[0]); - int ret = 0; - - called_vim_beep = false; - suppress_errthrow = true; - emsg_silent = false; - do_cmdline_cmd(cmd); - if (no_beep ? called_vim_beep : !called_vim_beep) { - garray_T ga; - prepare_assert_error(&ga); - if (no_beep) { - ga_concat(&ga, "command did beep: "); - } else { - ga_concat(&ga, "command did not beep: "); - } - ga_concat(&ga, cmd); - assert_error(&ga); - ga_clear(&ga); - ret = 1; - } - - suppress_errthrow = false; - emsg_on_display = false; - return ret; -} - -int assert_fails(typval_T *argvars) - FUNC_ATTR_NONNULL_ALL -{ - const char *const cmd = tv_get_string_chk(&argvars[0]); - garray_T ga; - int ret = 0; - int save_trylevel = trylevel; - - // trylevel must be zero for a ":throw" command to be considered failed - trylevel = 0; - called_emsg = false; - suppress_errthrow = true; - emsg_silent = true; - - do_cmdline_cmd(cmd); - if (!called_emsg) { - prepare_assert_error(&ga); - ga_concat(&ga, "command did not fail: "); - assert_append_cmd_or_arg(&ga, argvars, cmd); - assert_error(&ga); - ga_clear(&ga); - ret = 1; - } else if (argvars[1].v_type != VAR_UNKNOWN) { - char buf[NUMBUFLEN]; - const char *const error = tv_get_string_buf_chk(&argvars[1], buf); - - if (error == NULL - || strstr((char *)vimvars[VV_ERRMSG].vv_str, error) == NULL) { - prepare_assert_error(&ga); - fill_assert_error(&ga, &argvars[2], NULL, &argvars[1], - &vimvars[VV_ERRMSG].vv_tv, ASSERT_OTHER); - ga_concat(&ga, ": "); - assert_append_cmd_or_arg(&ga, argvars, cmd); - assert_error(&ga); - ga_clear(&ga); - ret = 1; - } - } - - trylevel = save_trylevel; - called_emsg = false; - suppress_errthrow = false; - emsg_silent = false; - emsg_on_display = false; - set_vim_var_string(VV_ERRMSG, NULL, 0); - return ret; -} - -int assert_match_common(typval_T *argvars, assert_type_T atype) - FUNC_ATTR_NONNULL_ALL -{ - char buf1[NUMBUFLEN]; - char buf2[NUMBUFLEN]; - const char *const pat = tv_get_string_buf_chk(&argvars[0], buf1); - const char *const text = tv_get_string_buf_chk(&argvars[1], buf2); - - if (pat == NULL || text == NULL) { - emsg(_(e_invarg)); - } else if (pattern_match((char_u *)pat, (char_u *)text, false) - != (atype == ASSERT_MATCH)) { - garray_T ga; - prepare_assert_error(&ga); - fill_assert_error(&ga, &argvars[2], NULL, &argvars[0], &argvars[1], atype); - assert_error(&ga); - ga_clear(&ga); - return 1; - } - return 0; -} - /// Find a window: When using a Window ID in any tab page, when using a number /// in the current tab page. win_T *find_win_by_nr_or_id(typval_T *vp) @@ -6371,15 +5923,13 @@ win_T *find_win_by_nr_or_id(typval_T *vp) int nr = (int)tv_get_number_chk(vp, NULL); if (nr >= LOWEST_WIN_ID) { - return win_id2wp(vp); + return win_id2wp(tv_get_number(vp)); } return find_win_by_nr(vp, NULL); } -/* - * Implementation of map() and filter(). - */ +/// Implementation of map() and filter(). void filter_map(typval_T *argvars, typval_T *rettv, int map) { typval_T *expr; @@ -6439,6 +5989,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; @@ -6469,6 +6023,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; @@ -6501,6 +6056,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, @@ -6519,6 +6078,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); @@ -6603,8 +6163,9 @@ void common_function(typval_T *argvars, typval_T *rettv, bool is_funcref, FunPtr : (const char *)s)); // Don't check an autoload name for existence here. } else if (trans_name != NULL - && (is_funcref ? find_func(trans_name) == NULL - : !translated_function_exists((const char *)trans_name))) { + && (is_funcref + ? find_func(trans_name) == NULL + : !translated_function_exists((const char *)trans_name))) { semsg(_("E700: Unknown function: %s"), s); } else { int dict_idx = 0; @@ -6730,7 +6291,7 @@ theend: xfree(trans_name); } -/// Returns buffer options, variables and other attributes in a dictionary. +/// @return buffer options, variables and other attributes in a dictionary. dict_T *get_buffer_info(buf_T *buf) { dict_T *const dict = tv_dict_alloc(); @@ -6774,12 +6335,12 @@ dict_T *get_buffer_info(buf_T *buf) /// /// @note Unlike tv_get_lnum(), this one supports only "$" special string. /// -/// @param[in] tv Object to get value from. Is expected to be a number or +/// @param[in] tv Object to get value from. Is expected to be a number or /// a special string "$". -/// @param[in] buf Buffer to take last line number from in case tv is "$". May -/// be NULL, in this case "$" results in zero return. +/// @param[in] buf Buffer to take last line number from in case tv is "$". May +/// be NULL, in this case "$" results in zero return. /// -/// @return Line number or 0 in case of error. +/// @return Line number or 0 in case of error. linenr_T tv_get_lnum_buf(const typval_T *const tv, const buf_T *const buf) FUNC_ATTR_NONNULL_ARG(1) FUNC_ATTR_WARN_UNUSED_RESULT { @@ -6815,8 +6376,8 @@ void get_qf_loc_list(int is_qf, win_T *wp, typval_T *what_arg, typval_T *rettv) } } -/// Returns information (variables, options, etc.) about a tab page -/// as a dictionary. +/// @return information (variables, options, etc.) about a tab page +/// as a dictionary. dict_T *get_tabpage_info(tabpage_T *tp, int tp_idx) { dict_T *const dict = tv_dict_alloc(); @@ -6835,7 +6396,7 @@ dict_T *get_tabpage_info(tabpage_T *tp, int tp_idx) return dict; } -/// Returns information about a window as a dictionary. +/// @return information about a window as a dictionary. dict_T *get_win_info(win_T *wp, int16_t tpnr, int16_t winnr) { dict_T *const dict = tv_dict_alloc(); @@ -6851,7 +6412,7 @@ dict_T *get_win_info(win_T *wp, int16_t tpnr, int16_t winnr) tv_dict_add_nr(dict, S_LEN("width"), wp->w_width); tv_dict_add_nr(dict, S_LEN("bufnr"), wp->w_buffer->b_fnum); tv_dict_add_nr(dict, S_LEN("wincol"), wp->w_wincol + 1); - + tv_dict_add_nr(dict, S_LEN("textoff"), win_col_off(wp)); tv_dict_add_nr(dict, S_LEN("terminal"), bt_terminal(wp->w_buffer)); tv_dict_add_nr(dict, S_LEN("quickfix"), bt_quickfix(wp->w_buffer)); tv_dict_add_nr(dict, S_LEN("loclist"), @@ -6926,10 +6487,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) { @@ -6949,8 +6509,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 @@ -6978,7 +6538,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--; @@ -6989,12 +6549,10 @@ void getwinvar(typval_T *argvars, typval_T *rettv, int off) } } -/* - * This function is used by f_input() and f_inputdialog() functions. The third - * argument to f_input() specifies the type of completion to use at the - * prompt. The third argument to f_inputdialog() specifies the value to return - * when the user cancels the prompt. - */ +/// This function is used by f_input() and f_inputdialog() functions. The third +/// argument to f_input() specifies the type of completion to use at the +/// prompt. The third argument to f_inputdialog() specifies the value to return +/// when the user cancels the prompt. void get_user_input(const typval_T *const argvars, typval_T *const rettv, const bool inputdialog, const bool secret) FUNC_ATTR_NONNULL_ALL @@ -7126,10 +6684,10 @@ void get_user_input(const typval_T *const argvars, typval_T *const rettv, const /// Turn a dictionary into a list /// -/// @param[in] tv Dictionary to convert. Is checked for actually being -/// a dictionary, will give an error if not. -/// @param[out] rettv Location where result will be saved. -/// @param[in] what What to save in rettv. +/// @param[in] tv Dictionary to convert. Is checked for actually being +/// a dictionary, will give an error if not. +/// @param[out] rettv Location where result will be saved. +/// @param[in] what What to save in rettv. void dict_list(typval_T *const tv, typval_T *const rettv, const DictListType what) { if (tv->v_type != VAR_DICT) { @@ -7146,30 +6704,30 @@ void dict_list(typval_T *const tv, typval_T *const rettv, const DictListType wha typval_T tv_item = { .v_lock = VAR_UNLOCKED }; switch (what) { - case kDictListKeys: - tv_item.v_type = VAR_STRING; - tv_item.vval.v_string = vim_strsave(di->di_key); - break; - case kDictListValues: - tv_copy(&di->di_tv, &tv_item); - break; - case kDictListItems: { - // items() - list_T *const sub_l = tv_list_alloc(2); - tv_item.v_type = VAR_LIST; - tv_item.vval.v_list = sub_l; - tv_list_ref(sub_l); - - tv_list_append_owned_tv(sub_l, (typval_T) { + case kDictListKeys: + tv_item.v_type = VAR_STRING; + tv_item.vval.v_string = vim_strsave(di->di_key); + break; + case kDictListValues: + tv_copy(&di->di_tv, &tv_item); + break; + case kDictListItems: { + // items() + list_T *const sub_l = tv_list_alloc(2); + tv_item.v_type = VAR_LIST; + tv_item.vval.v_list = sub_l; + tv_list_ref(sub_l); + + tv_list_append_owned_tv(sub_l, (typval_T) { .v_type = VAR_STRING, .v_lock = VAR_UNLOCKED, .vval.v_string = (char_u *)xstrdup((const char *)di->di_key), }); - tv_list_append_tv(sub_l, &di->di_tv); + tv_list_append_tv(sub_l, &di->di_tv); - break; - } + break; + } } tv_list_append_owned_tv(rettv->vval.v_list, tv_item); @@ -7182,7 +6740,7 @@ void dict_list(typval_T *const tv, typval_T *const rettv, const DictListType wha /// @param[out] cmd Returns the command or executable name. /// @param[out] executable Returns `false` if argv[0] is not executable. /// -/// @returns Result of `shell_build_argv()` if `cmd_tv` is a String. +/// @return Result of `shell_build_argv()` if `cmd_tv` is a String. /// Else, string values of `cmd_tv` copied to a (char **) list with /// argv[0] resolved to full path ($PATHEXT-resolved on Windows). char **tv_to_argv(typval_T *cmd_tv, const char **cmd, bool *executable) @@ -7246,10 +6804,10 @@ char **tv_to_argv(typval_T *cmd_tv, const char **cmd, bool *executable) /// Fill a dictionary with all applicable maparg() like dictionaries /// -/// @param dict The dictionary to be filled -/// @param mp The maphash that contains the mapping information -/// @param buffer_value The "buffer" value -/// @param compatible True for compatible with old maparg() dict +/// @param dict The dictionary to be filled +/// @param mp The maphash that contains the mapping information +/// @param buffer_value The "buffer" value +/// @param compatible True for compatible with old maparg() dict void mapblock_fill_dict(dict_T *const dict, const mapblock_T *const mp, long buffer_value, bool compatible) FUNC_ATTR_NONNULL_ALL @@ -7269,12 +6827,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); @@ -7288,30 +6853,6 @@ void mapblock_fill_dict(dict_T *const dict, const mapblock_T *const mp, long buf tv_dict_add_allocated_str(dict, S_LEN("mode"), mapmode); } -int matchadd_dict_arg(typval_T *tv, const char **conceal_char, win_T **win) -{ - dictitem_T *di; - - if (tv->v_type != VAR_DICT) { - emsg(_(e_dictreq)); - return FAIL; - } - - if ((di = tv_dict_find(tv->vval.v_dict, S_LEN("conceal"))) != NULL) { - *conceal_char = tv_get_string(&di->di_tv); - } - - if ((di = tv_dict_find(tv->vval.v_dict, S_LEN("window"))) != NULL) { - *win = find_win_by_nr_or_id(&di->di_tv); - if (*win == NULL) { - emsg(_(e_invalwindow)); - return FAIL; - } - } - - return OK; -} - void return_register(int regname, typval_T *rettv) { char_u buf[2] = { regname, 0 }; @@ -7473,11 +7014,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; @@ -7499,7 +7038,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); } } } @@ -7541,7 +7080,7 @@ static list_T *string_to_list(const char *str, size_t len, const bool keepempty) return list; } -// os_system wrapper. Handles 'verbose', :profile, and v:shell_error. +/// os_system wrapper. Handles 'verbose', :profile, and v:shell_error. void get_system_output_as_rettv(typval_T *argvars, typval_T *rettv, bool retlist) { proftime_T wait_time; @@ -7666,6 +7205,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) { @@ -7695,6 +7235,8 @@ bool callback_call(Callback *const callback, const int argcount_in, typval_T *co { partial_T *partial; char_u *name; + Array args = ARRAY_DICT_INIT; + Object rv; switch (callback->type) { case kCallbackFuncref: name = callback->data.funcref; @@ -7706,6 +7248,15 @@ bool callback_call(Callback *const callback, const int argcount_in, typval_T *co name = partial_name(partial); break; + case kCallbackLua: + rv = nlua_call_ref(callback->data.luaref, NULL, args, true, NULL); + switch (rv.type) { + case kObjectTypeBoolean: + return rv.data.boolean; + default: + return false; + } + case kCallbackNone: return false; break; @@ -7798,7 +7349,7 @@ void add_timer_info_all(typval_T *rettv) }) } -// invoked on the main loop +/// invoked on the main loop void timer_due_cb(TimeWatcher *tw, void *data) { timer_T *timer = (timer_T *)data; @@ -7884,8 +7435,8 @@ void timer_stop(timer_T *timer) time_watcher_close(&timer->tw, timer_close_cb); } -// This will be run on the main loop after the last timer_due_cb, so at this -// point it is safe to free the callback. +/// This will be run on the main loop after the last timer_due_cb, so at this +/// point it is safe to free the callback. static void timer_close_cb(TimeWatcher *tw, void *data) { timer_T *timer = (timer_T *)data; @@ -8110,6 +7661,68 @@ 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. +/// The index of the first byte and the first character is zero. +/// +/// @return -1 on failure. +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 @@ -8118,13 +7731,14 @@ 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; - pos_T *pp; // Argument can be [lnum, col, coladd]. if (tv->v_type == VAR_LIST) { @@ -8150,7 +7764,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); @@ -8180,21 +7798,31 @@ pos_T *var2fpos(const typval_T *const tv, const bool dollar_lnum, int *const ret if (name == NULL) { return NULL; } - if (name[0] == '.') { // Cursor. - return &curwin->w_cursor; - } - if (name[0] == 'v' && name[1] == NUL) { // Visual start. + + pos.lnum = 0; + if (name[0] == '.') { + // cursor + pos = curwin->w_cursor; + } else if (name[0] == 'v' && name[1] == NUL) { + // Visual start if (VIsual_active) { - return &VIsual; + pos = VIsual; + } else { + pos = curwin->w_cursor; } - return &curwin->w_cursor; - } - if (name[0] == '\'') { // Mark. - pp = getmark_buf_fnum(curbuf, (uint8_t)name[1], false, ret_fnum); + } else if (name[0] == '\'') { + // mark + const pos_T *const pp = getmark_buf_fnum(curbuf, (uint8_t)name[1], false, ret_fnum); if (pp == NULL || pp == (pos_T *)-1 || pp->lnum <= 0) { return NULL; } - return pp; + pos = *pp; + } + if (pos.lnum != 0) { + if (charcol) { + pos.col = buf_byteidx_to_charidx(curbuf, pos.lnum, pos.col); + } + return &pos; } pos.coladd = 0; @@ -8219,22 +7847,25 @@ 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; @@ -8270,6 +7901,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 @@ -8286,11 +7926,10 @@ int list2fpos(typval_T *arg, pos_T *posp, int *fnump, colnr_T *curswantp) return OK; } -/* - * Get the length of an environment variable name. - * Advance "arg" to the first character after the name. - * Return 0 for error. - */ +/// Get the length of an environment variable name. +/// Advance "arg" to the first character after the name. +/// +/// @return 0 for error. static int get_env_len(const char_u **arg) { int len; @@ -8307,9 +7946,11 @@ static int get_env_len(const char_u **arg) return len; } -// Get the length of the name of a function or internal variable. -// "arg" is advanced to the first non-white character after the name. -// Return 0 if something is wrong. +/// Get the length of the name of a function or internal variable. +/// +/// @param arg is advanced to the first non-white character after the name. +/// +/// @return 0 if something is wrong. int get_id_len(const char **const arg) { int len; @@ -8337,15 +7978,15 @@ int get_id_len(const char **const arg) return len; } -/* - * Get the length of the name of a variable or function. - * Only the name is recognized, does not handle ".key" or "[idx]". - * "arg" is advanced to the first non-white character after the name. - * Return -1 if curly braces expansion failed. - * Return 0 if something else is wrong. - * If the name contains 'magic' {}'s, expand them and return the - * expanded name in an allocated string via 'alias' - caller must free. - */ +/// Get the length of the name of a variable or function. +/// Only the name is recognized, does not handle ".key" or "[idx]". +/// +/// @param arg is advanced to the first non-white character after the name. +/// If the name contains 'magic' {}'s, expand them and return the +/// expanded name in an allocated string via 'alias' - caller must free. +/// +/// @return -1 if curly braces expansion failed or +/// 0 if something else is wrong. int get_name_len(const char **const arg, char **alias, bool evaluate, bool verbose) { int len; @@ -8402,12 +8043,15 @@ int get_name_len(const char **const arg, char **alias, bool evaluate, bool verbo return len; } -// Find the end of a variable or function name, taking care of magic braces. -// If "expr_start" is not NULL then "expr_start" and "expr_end" are set to the -// start and end of the first magic braces item. -// "flags" can have FNE_INCL_BR and FNE_CHECK_START. -// Return a pointer to just after the name. Equal to "arg" if there is no -// valid name. +/// Find the end of a variable or function name, taking care of magic braces. +/// +/// @param expr_start if not NULL, then `expr_start` and `expr_end` are set to the +/// start and end of the first magic braces item. +/// +/// @param flags can have FNE_INCL_BR and FNE_CHECK_START. +/// +/// @return a pointer to just after the name. Equal to "arg" if there is no +/// valid name. const char_u *find_name_end(const char_u *arg, const char_u **expr_start, const char_u **expr_end, int flags) { @@ -8485,19 +8129,17 @@ const char_u *find_name_end(const char_u *arg, const char_u **expr_start, const return p; } -/* - * Expands out the 'magic' {}'s in a variable/function name. - * Note that this can call itself recursively, to deal with - * constructs like foo{bar}{baz}{bam} - * The four pointer arguments point to "foo{expre}ss{ion}bar" - * "in_start" ^ - * "expr_start" ^ - * "expr_end" ^ - * "in_end" ^ - * - * Returns a new allocated string, which the caller must free. - * Returns NULL for failure. - */ +/// Expands out the 'magic' {}'s in a variable/function name. +/// Note that this can call itself recursively, to deal with +/// constructs like foo{bar}{baz}{bam} +/// The four pointer arguments point to "foo{expre}ss{ion}bar" +/// "in_start" ^ +/// "expr_start" ^ +/// "expr_end" ^ +/// "in_end" ^ +/// +/// @return a new allocated string, which the caller must free or +/// NULL for failure. static char_u *make_expanded_name(const char_u *in_start, char_u *expr_start, char_u *expr_end, char_u *in_end) { @@ -8544,44 +8186,42 @@ static char_u *make_expanded_name(const char_u *in_start, char_u *expr_start, ch return retval; } -/* - * Return TRUE if character "c" can be used in a variable or function name. - * Does not include '{' or '}' for magic braces. - */ +/// @return TRUE if character "c" can be used in a variable or function name. +/// Does not include '{' or '}' for magic braces. int eval_isnamec(int c) { return ASCII_ISALNUM(c) || c == '_' || c == ':' || c == AUTOLOAD_CHAR; } -/* - * Return TRUE if character "c" can be used as the first character in a - * variable or function name (excluding '{' and '}'). - */ +/// @return TRUE if character "c" can be used as the first character in a +/// variable or function name (excluding '{' and '}'). int eval_isnamec1(int c) { return ASCII_ISALPHA(c) || c == '_'; } -/* - * Get number v: variable value. - */ +/// Get typval_T v: variable value. +typval_T *get_vim_var_tv(int idx) +{ + return &vimvars[idx].vv_tv; +} + +/// Get number v: variable value. varnumber_T get_vim_var_nr(int idx) FUNC_ATTR_PURE { return vimvars[idx].vv_nr; } -// Get string v: variable value. Uses a static buffer, can only be used once. -// If the String variable has never been set, return an empty string. -// Never returns NULL; +/// Get string v: variable value. Uses a static buffer, can only be used once. +/// If the String variable has never been set, return an empty string. +/// Never returns NULL; char_u *get_vim_var_str(int idx) FUNC_ATTR_PURE FUNC_ATTR_NONNULL_RET { return (char_u *)tv_get_string(&vimvars[idx].vv_tv); } -/* - * Get List v: variable value. Caller must take care of reference count when - * needed. - */ +/// Get List v: variable value. Caller must take care of reference count when +/// needed. list_T *get_vim_var_list(int idx) FUNC_ATTR_PURE { return vimvars[idx].vv_list; @@ -8594,9 +8234,7 @@ dict_T *get_vim_var_dict(int idx) FUNC_ATTR_PURE return vimvars[idx].vv_dict; } -/* - * Set v:char to character "c". - */ +/// Set v:char to character "c". void set_vim_var_char(int c) { char buf[MB_MAXBYTES + 1]; @@ -8605,10 +8243,9 @@ void set_vim_var_char(int c) set_vim_var_string(VV_CHAR, buf, -1); } -/* - * Set v:count to "count" and v:count1 to "count1". - * When "set_prevcount" is TRUE first set v:prevcount from v:count. - */ +/// Set v:count to "count" and v:count1 to "count1". +/// +/// @param set_prevcount if TRUE, first set v:prevcount from v:count. void set_vcount(long count, long count1, int set_prevcount) { if (set_prevcount) { @@ -8716,9 +8353,7 @@ void set_argv_var(char **argv, int argc) set_vim_var_list(VV_ARGV, l); } -/* - * Set v:register if needed. - */ +/// Set v:register if needed. void set_reg_var(int c) { char regname; @@ -8734,12 +8369,10 @@ void set_reg_var(int c) } } -/* - * Get or set v:exception. If "oldval" == NULL, return the current value. - * Otherwise, restore the value to "oldval" and return NULL. - * Must always be called in pairs to save and restore v:exception! Does not - * take care of memory allocations. - */ +/// Get or set v:exception. If "oldval" == NULL, return the current value. +/// Otherwise, restore the value to "oldval" and return NULL. +/// Must always be called in pairs to save and restore v:exception! Does not +/// take care of memory allocations. char_u *v_exception(char_u *oldval) { if (oldval == NULL) { @@ -8750,12 +8383,10 @@ char_u *v_exception(char_u *oldval) return NULL; } -/* - * Get or set v:throwpoint. If "oldval" == NULL, return the current value. - * Otherwise, restore the value to "oldval" and return NULL. - * Must always be called in pairs to save and restore v:throwpoint! Does not - * take care of memory allocations. - */ +/// Get or set v:throwpoint. If "oldval" == NULL, return the current value. +/// Otherwise, restore the value to "oldval" and return NULL. +/// Must always be called in pairs to save and restore v:throwpoint! Does not +/// take care of memory allocations. char_u *v_throwpoint(char_u *oldval) { if (oldval == NULL) { @@ -8766,12 +8397,10 @@ char_u *v_throwpoint(char_u *oldval) return NULL; } -/* - * Set v:cmdarg. - * If "eap" != NULL, use "eap" to generate the value and return the old value. - * If "oldarg" != NULL, restore the value to "oldarg" and return NULL. - * Must always be called in pairs! - */ +/// Set v:cmdarg. +/// If "eap" != NULL, use "eap" to generate the value and return the old value. +/// If "oldarg" != NULL, restore the value to "oldarg" and return NULL. +/// Must always be called in pairs! char_u *set_cmdarg(exarg_T *eap, char_u *oldarg) { char_u *oldval = vimvars[VV_CMDARG].vv_str; @@ -8909,7 +8538,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; @@ -9036,11 +8665,12 @@ void set_selfdict(typval_T *const rettv, dict_T *const selfdict) make_partial(selfdict, rettv); } -// Find variable "name" in the list of variables. -// Return a pointer to it if found, NULL if not found. -// Careful: "a:0" variables don't have a name. -// When "htp" is not NULL we are writing to the variable, set "htp" to the -// hashtab_T used. +/// Find variable "name" in the list of variables. +/// Careful: "a:0" variables don't have a name. +/// When "htp" is not NULL we are writing to the variable, set "htp" to the +/// hashtab_T used. +/// +/// @return a pointer to it if found, NULL if not found. dictitem_T *find_var(const char *const name, const size_t name_len, hashtab_T **htp, int no_autoload) { @@ -9128,6 +8758,8 @@ dictitem_T *find_var_in_ht(hashtab_T *const ht, int htname, const char *const va /// Finds the dict (g:, l:, s:, …) and hashtable used for a variable. /// +/// Assigns SID if s: scope is accessed from Lua or anonymous Vimscript. #15994 +/// /// @param[in] name Variable name, possibly with scope prefix. /// @param[in] name_len Variable name length. /// @param[out] varname Will be set to the start of the name without scope @@ -9190,10 +8822,32 @@ 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) { + // Create SID if s: scope is accessed from Lua or anon Vimscript. #15994 new_script_item(NULL, ¤t_sctx.sc_sid); } *d = &SCRIPT_SV(current_sctx.sc_sid)->sv_dict; @@ -9217,11 +8871,10 @@ hashtab_T *find_var_ht(const char *name, const size_t name_len, const char **var return find_var_ht_dict(name, name_len, varname, &d); } -/* - * Get the string value of a (global/local) variable. - * Note: see tv_get_string() for how long the pointer remains valid. - * Returns NULL when it doesn't exist. - */ +/// @return the string value of a (global/local) variable or +/// NULL when it doesn't exist. +/// +/// @see tv_get_string() for how long the pointer remains valid. char_u *get_var_value(const char *const name) { dictitem_T *v; @@ -9233,16 +8886,14 @@ char_u *get_var_value(const char *const name) return (char_u *)tv_get_string(&v->di_tv); } -/* - * Allocate a new hashtab for a sourced script. It will be used while - * sourcing this script and when executing functions defined in the script. - */ +/// Allocate a new hashtab for a sourced script. It will be used while +/// sourcing this script and when executing functions defined in the script. void new_script_vars(scid_T id) { hashtab_T *ht; scriptvar_T *sv; - ga_grow(&ga_scripts, (int)(id - ga_scripts.ga_len)); + ga_grow(&ga_scripts, id - ga_scripts.ga_len); { /* Re-allocating ga_data means that an ht_array pointing to * ht_smallarray becomes invalid. We can recognize this: ht_mask is @@ -9264,10 +8915,8 @@ void new_script_vars(scid_T id) } } -/* - * Initialize dictionary "dict" as a scope and set variable "dict_var" to - * point to it. - */ +/// Initialize dictionary "dict" as a scope and set variable "dict_var" to +/// point to it. void init_var_dict(dict_T *dict, ScopeDictDictItem *dict_var, int scope) { hash_init(&dict->dv_hashtab); @@ -9283,9 +8932,7 @@ void init_var_dict(dict_T *dict, ScopeDictDictItem *dict_var, int scope) QUEUE_INIT(&dict->watchers); } -/* - * Unreference a dictionary initialized by init_var_dict(). - */ +/// Unreference a dictionary initialized by init_var_dict(). void unref_var_dict(dict_T *dict) { /* Now the dict needs to be freed if no one else is using it, go back to @@ -9294,19 +8941,15 @@ void unref_var_dict(dict_T *dict) tv_dict_unref(dict); } -/* - * Clean up a list of internal variables. - * Frees all allocated variables and the value they contain. - * Clears hashtab "ht", does not free it. - */ +/// Clean up a list of internal variables. +/// Frees all allocated variables and the value they contain. +/// Clears hashtab "ht", does not free it. void vars_clear(hashtab_T *ht) { vars_clear_ext(ht, TRUE); } -/* - * Like vars_clear(), but only free the value if "free_val" is TRUE. - */ +/// Like vars_clear(), but only free the value if "free_val" is TRUE. void vars_clear_ext(hashtab_T *ht, int free_val) { int todo; @@ -9335,10 +8978,8 @@ void vars_clear_ext(hashtab_T *ht, int free_val) ht->ht_used = 0; } -/* - * Delete a variable from hashtab "ht" at item "hi". - * Clear the variable value and free the dictitem. - */ +/// Delete a variable from hashtab "ht" at item "hi". +/// Clear the variable value and free the dictitem. static void delete_var(hashtab_T *ht, hashitem_T *hi) { dictitem_T *di = TV_DICT_HI2DI(hi); @@ -9348,9 +8989,7 @@ static void delete_var(hashtab_T *ht, hashitem_T *hi) xfree(di); } -/* - * List the value of one internal variable. - */ +/// List the value of one internal variable. static void list_one_var(dictitem_T *v, const char *prefix, int *first) { char *const s = encode_tv2echo(&v->di_tv, NULL); @@ -9649,8 +9288,8 @@ bool var_check_func_name(const char *const name, const bool new_var) { // Allow for w: b: s: and t:. if (!(vim_strchr((char_u *)"wbst", name[0]) != NULL && name[1] == ':') - && !ASCII_ISUPPER((name[0] != NUL && name[1] == ':') ? name[2] - : name[0])) { + && !ASCII_ISUPPER((name[0] != NUL && name[1] == ':') + ? name[2] : name[0])) { semsg(_("E704: Funcref variable name must start with a capital: %s"), name); return false; } @@ -9783,11 +9422,9 @@ int var_item_copy(const vimconv_T *const conv, typval_T *const from, typval_T *c return ret; } -/* - * ":echo expr1 ..." print each argument separated with a space, add a - * newline at the end. - * ":echon expr1 ..." print each argument plain. - */ +/// ":echo expr1 ..." print each argument separated with a space, add a +/// newline at the end. +/// ":echon expr1 ..." print each argument plain. void ex_echo(exarg_T *eap) { char_u *arg = eap->arg; @@ -9861,21 +9498,17 @@ void ex_echo(exarg_T *eap) } } -/* - * ":echohl {name}". - */ +/// ":echohl {name}". void ex_echohl(exarg_T *eap) { echo_attr = syn_name2attr(eap->arg); } -/* - * ":execute expr1 ..." execute the result of an expression. - * ":echomsg expr1 ..." Print a message - * ":echoerr expr1 ..." Print an error - * Each gets spaces around each argument and a newline at the end for - * echo commands - */ +/// ":execute expr1 ..." execute the result of an expression. +/// ":echomsg expr1 ..." Print a message +/// ":echoerr expr1 ..." Print an error +/// Each gets spaces around each argument and a newline at the end for +/// echo commands void ex_execute(exarg_T *eap) { char_u *arg = eap->arg; @@ -9952,12 +9585,12 @@ void ex_execute(exarg_T *eap) eap->nextcmd = check_nextcmd(arg); } -/* - * Skip over the name of an option: "&option", "&g:option" or "&l:option". - * "arg" points to the "&" or '+' when called, to "option" when returning. - * Returns NULL when no option name found. Otherwise pointer to the char - * after the option name. - */ +/// Skip over the name of an option: "&option", "&g:option" or "&l:option". +/// +/// @param arg points to the "&" or '+' when called, to "option" when returning. +/// +/// @return NULL when no option name found. Otherwise pointer to the char +/// after the option name. static const char *find_option_end(const char **const arg, int *const opt_flags) { const char *p = *arg; @@ -10020,9 +9653,7 @@ void func_do_profile(ufunc_T *fp) fp->uf_profiling = TRUE; } -/* - * Dump the profiling results for all functions in file "fd". - */ +/// Dump the profiling results for all functions in file "fd". void func_dump_profile(FILE *fd) { hashitem_T *hi; @@ -10142,9 +9773,7 @@ static void prof_func_line(FILE *fd, int count, proftime_T *total, proftime_T *s } } -/* - * Compare function for total time sorting. - */ +/// Compare function for total time sorting. static int prof_total_cmp(const void *s1, const void *s2) { ufunc_T *p1 = *(ufunc_T **)s1; @@ -10152,9 +9781,7 @@ static int prof_total_cmp(const void *s1, const void *s2) return profile_cmp(p1->uf_tm_total, p2->uf_tm_total); } -/* - * Compare function for self time sorting. - */ +/// Compare function for self time sorting. static int prof_self_cmp(const void *s1, const void *s2) { ufunc_T *p1 = *(ufunc_T **)s1; @@ -10236,12 +9863,10 @@ bool script_autoload(const char *const name, const size_t name_len, const bool r return ret; } -/* - * Called when starting to read a function line. - * "sourcing_lnum" must be correct! - * When skipping lines it may not actually be executed, but we won't find out - * until later and we need to store the time now. - */ +/// Called when starting to read a function line. +/// "sourcing_lnum" must be correct! +/// When skipping lines it may not actually be executed, but we won't find out +/// until later and we need to store the time now. void func_line_start(void *cookie) { funccall_T *fcp = (funccall_T *)cookie; @@ -10261,9 +9886,7 @@ void func_line_start(void *cookie) } } -/* - * Called when actually executing a function line. - */ +/// Called when actually executing a function line. void func_line_exec(void *cookie) { funccall_T *fcp = (funccall_T *)cookie; @@ -10274,9 +9897,7 @@ void func_line_exec(void *cookie) } } -/* - * Called when done with a function line. - */ +/// Called when done with a function line. void func_line_end(void *cookie) { funccall_T *fcp = (funccall_T *)cookie; @@ -10411,10 +10032,8 @@ int store_session_globals(FILE *fd) return OK; } -/* - * Display script name where an item was last set. - * Should only be invoked when 'verbose' is non-zero. - */ +/// Display script name where an item was last set. +/// Should only be invoked when 'verbose' is non-zero. void last_set_msg(sctx_T script_ctx) { const LastSet last_set = (LastSet){ @@ -10446,11 +10065,15 @@ void option_last_set_msg(LastSet last_set) } } -// reset v:option_new, v:option_old and v:option_type +// reset v:option_new, v:option_old, v:option_oldlocal, v:option_oldglobal, +// v:option_type, and v:option_command. void reset_v_option_vars(void) { - set_vim_var_string(VV_OPTION_NEW, NULL, -1); - set_vim_var_string(VV_OPTION_OLD, NULL, -1); + set_vim_var_string(VV_OPTION_NEW, NULL, -1); + set_vim_var_string(VV_OPTION_OLD, NULL, -1); + set_vim_var_string(VV_OPTION_OLDLOCAL, NULL, -1); + set_vim_var_string(VV_OPTION_OLDGLOBAL, NULL, -1); + set_vim_var_string(VV_OPTION_COMMAND, NULL, -1); set_vim_var_string(VV_OPTION_TYPE, NULL, -1); } @@ -10474,12 +10097,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; @@ -10548,8 +10172,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); @@ -10558,18 +10182,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 { @@ -10580,6 +10219,7 @@ repeat: *fnamep = s; xfree(*bufp); *bufp = s; + has_homerelative = true; } } xfree(pbuf); @@ -10746,7 +10386,8 @@ repeat: /// Perform a substitution on "str" with pattern "pat" and substitute "sub". /// When "sub" is NULL "expr" is used, must be a VAR_FUNC or VAR_PARTIAL. /// "flags" can be "g" to do a global substitute. -/// Returns an allocated string, NULL for error. +/// +/// @return an allocated string, NULL for error. char_u *do_string_sub(char_u *str, char_u *pat, char_u *sub, typval_T *expr, char_u *flags) { int sublen; @@ -10956,10 +10597,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") @@ -11085,7 +10723,7 @@ void invoke_prompt_callback(void) tv_clear(&rettv); } -// Return true When the interrupt callback was invoked. +/// @return true when the interrupt callback was invoked. bool invoke_prompt_interrupt(void) { typval_T rettv; diff --git a/src/nvim/eval.h b/src/nvim/eval.h index d34348a274..a9ec5d47a6 100644 --- a/src/nvim/eval.h +++ b/src/nvim/eval.h @@ -138,6 +138,9 @@ typedef enum { VV_COMPLETED_ITEM, VV_OPTION_NEW, VV_OPTION_OLD, + VV_OPTION_OLDLOCAL, + VV_OPTION_OLDGLOBAL, + VV_OPTION_COMMAND, VV_OPTION_TYPE, VV_ERRORS, VV_FALSE, @@ -190,6 +193,13 @@ extern const list_T *eval_msgpack_type_lists[LAST_MSGPACK_TYPE + 1]; #undef LAST_MSGPACK_TYPE +// Struct passed to get_v_event() and restore_v_event(). +typedef struct { + bool sve_did_save; + hashtab_T sve_hashtab; +} save_v_event_T; + + /// trans_function_name() flags typedef enum { TFN_INT = 1, ///< May use internal function name @@ -209,8 +219,8 @@ typedef enum { /// flags for find_name_end() #define FNE_INCL_BR 1 // find_name_end(): include [] in name -#define FNE_CHECK_START 2 /* find_name_end(): check name starts with - valid character */ +#define FNE_CHECK_START 2 // find_name_end(): check name starts with + // valid character typedef struct { TimeWatcher tw; diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua index dfc51d80af..698cffe2fa 100644 --- a/src/nvim/eval.lua +++ b/src/nvim/eval.lua @@ -17,7 +17,7 @@ end -- Usable with the base key: use the last function argument as the method base. -- Value is from funcs.h file. "BASE_" prefix is omitted. -local LAST = "BASE_LAST" +-- local LAST = "BASE_LAST" (currently unused after port of v8.2.1168) return { funcs={ @@ -26,14 +26,14 @@ return { add={args=2, base=1}, ['and']={args=2, base=1}, api_info={}, - append={args=2, base=LAST}, - appendbufline={args=3, base=LAST}, + append={args=2, base=2}, + appendbufline={args=3, base=3}, argc={args={0, 1}}, argidx={}, arglistid={args={0, 2}}, argv={args={0, 2}}, asin={args=1, base=1, func="float_op_wrapper", data="&asin"}, -- WJMc - assert_beeps={args={1}, base=1}, + assert_beeps={args=1, base=1}, assert_equal={args={2, 3}, base=2}, assert_equalfile={args={2, 3}, base=1}, assert_exception={args={1, 2}}, @@ -41,7 +41,7 @@ return { assert_false={args={1, 2}, base=1}, assert_inrange={args={3, 4}, base=3}, assert_match={args={2, 3}, base=2}, - assert_nobeep={args={1}}, + assert_nobeep={args=1, base=1}, assert_notequal={args={2, 3}, base=2}, assert_notmatch={args={2, 3}, base=2}, assert_report={args=1, base=1}, @@ -53,8 +53,8 @@ return { bufadd={args=1, base=1}, bufexists={args=1, base=1}, buffer_exists={args=1, base=1, func='f_bufexists'}, -- obsolete - buffer_name={args={0, 1}, func='f_bufname'}, -- obsolete - buffer_number={args={0, 1}, func='f_bufnr'}, -- obsolete + buffer_name={args={0, 1}, base=1, func='f_bufname'}, -- obsolete + buffer_number={args={0, 1}, base=1, func='f_bufnr'}, -- obsolete buflisted={args=1, base=1}, bufload={args=1, base=1}, bufloaded={args=1, base=1}, @@ -71,7 +71,8 @@ return { chanclose={args={1, 2}}, chansend={args=2}, char2nr={args={1, 2}, base=1}, - charidx={args={2, 3}}, + charcol={args=1, base=1}, + charidx={args={2, 3}, base=1}, chdir={args=1, base=1}, cindent={args=1, base=1}, clearmatches={args={0, 1}, base=1}, @@ -101,6 +102,10 @@ return { did_filetype={}, diff_filler={args=1, base=1}, diff_hlID={args=2, base=1}, + digraph_get={args=1, base=1}, + digraph_getlist={args={0, 1}, base=1}, + digraph_set={args=2, base=1}, + digraph_setlist={args=1, base=1}, empty={args=1, base=1}, environ={}, escape={args=2, base=1}, @@ -121,7 +126,7 @@ return { filter={args=2, base=1}, finddir={args={1, 3}, base=1}, findfile={args={1, 3}, base=1}, - flatten={args={1, 2}}, + flatten={args={1, 2}, base=1}, float2nr={args=1, base=1}, floor={args=1, base=1, func="float_op_wrapper", data="&floor"}, fmod={args=2, base=1}, @@ -133,16 +138,18 @@ return { foldtext={}, foldtextresult={args=1, base=1}, foreground={}, + fullcommand={args=1, base=1}, funcref={args={1, 3}, base=1}, ['function']={args={1, 3}, base=1}, garbagecollect={args={0, 1}}, get={args={2, 3}, base=1}, - getbufinfo={args={0, 1}}, + getbufinfo={args={0, 1}, base=1}, getbufline={args={2, 3}, base=1}, getbufvar={args={2, 3}, base=1}, getchangelist={args={0, 1}, base=1}, getchar={args={0, 1}}, getcharmod={}, + getcharpos={args=1, base=1}, getcharsearch={}, getcharstr={args={0, 1}}, getcmdline={}, @@ -150,9 +157,10 @@ 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}, + getenv={args=1, base=1}, getfontname={args={0, 1}}, getfperm={args=1, base=1}, getfsize={args=1, base=1}, @@ -161,7 +169,7 @@ return { getjumplist={args={0, 2}, base=1}, getline={args={1, 2}, base=1}, getloclist={args={1, 2}}, - getmarklist={args={0, 1}}, + getmarklist={args={0, 1}, base=1}, getmatches={args={0, 1}}, getmousepos={}, getpid={}, @@ -245,6 +253,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}, @@ -258,25 +268,28 @@ 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}, - prompt_getprompt={args=1}, + prompt_getprompt={args=1, base=1}, prompt_setcallback={args={2, 2}, base=1}, prompt_setinterrupt={args={2, 2}, base=1}, prompt_setprompt={args={2, 2}, base=1}, 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={}, reltime={args={0, 2}, base=1}, reltimefloat={args=1, base=1}, reltimestr={args=1, base=1}, @@ -291,82 +304,85 @@ return { rpcstart={args={1, 2}}, rpcstop={args=1}, rubyeval={args=1, base=1}, - screenattr={args=2}, - screenchar={args=2}, - screenchars={args=2}, + screenattr={args=2, base=1}, + screenchar={args=2, base=1}, + screenchars={args=2, base=1}, screencol={}, - screenpos={args=3}, + screenpos={args=3, base=1}, screenrow={}, - screenstring={args=2}, - search={args={1, 4}}, - searchcount={args={0,1}}, - searchdecl={args={1, 3}}, + screenstring={args=2, 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}}, + searchpos={args={1, 5}, base=1}, serverlist={}, serverstart={args={0, 1}}, serverstop={args=1}, - setbufline={args=3}, - setbufvar={args=3}, - setcharsearch={args=1}, - setcmdpos={args=1}, - setenv={args=2}, + 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}, - setloclist={args={2, 4}}, - setmatches={args={1, 2}}, - setpos={args=2}, - setqflist={args={1, 3}}, - setreg={args={2, 3}}, - settabvar={args=3}, - settabwinvar={args=4}, - settagstack={args={2, 3}}, - setwinvar={args=3}, - sha256={args=1}, - shellescape={args={1, 2}}, - shiftwidth={args={0, 1}}, - sign_define={args={1, 2}}, - sign_getdefined={args={0, 1}}, - sign_getplaced={args={0, 2}}, - sign_jump={args={3, 3}}, - sign_place={args={4, 5}}, - sign_placelist={args={1}}, - sign_undefine={args={0, 1}}, - sign_unplace={args={1, 2}}, - sign_unplacelist={args={1}}, - simplify={args=1}, + setline={args=2, base=2}, + setloclist={args={2, 4}, base=2}, + setmatches={args={1, 2}, base=1}, + setpos={args=2, base=2}, + setqflist={args={1, 3}, base=1}, + setreg={args={2, 3}, base=2}, + settabvar={args=3, base=3}, + settabwinvar={args=4, base=4}, + settagstack={args={2, 3}, base=2}, + setwinvar={args=3, base=3}, + sha256={args=1, base=1}, + shellescape={args={1, 2}, base=1}, + shiftwidth={args={0, 1}, base=1}, + sign_define={args={1, 2}, base=1}, + sign_getdefined={args={0, 1}, base=1}, + sign_getplaced={args={0, 2}, base=1}, + sign_jump={args=3, base=1}, + sign_place={args={4, 5}, base=1}, + sign_placelist={args=1, base=1}, + sign_undefine={args={0, 1}, base=1}, + sign_unplace={args={1, 2}, base=1}, + sign_unplacelist={args=1, base=1}, + simplify={args=1, base=1}, sin={args=1, base=1, func="float_op_wrapper", data="&sin"}, sinh={args=1, base=1, func="float_op_wrapper", data="&sinh"}, sockconnect={args={2,3}}, sort={args={1, 3}, base=1}, - soundfold={args=1}, + soundfold={args=1, base=1}, stdioopen={args=1}, - spellbadword={args={0, 1}}, - spellsuggest={args={1, 3}}, + spellbadword={args={0, 1}, base=1}, + 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}, - str2nr={args={1, 3}}, - strcharpart={args={2, 3}}, - strchars={args={1,2}}, - strdisplaywidth={args={1, 2}}, - strftime={args={1, 2}}, - strgetchar={args={2, 2}}, - stridx={args={2, 3}}, + str2nr={args={1, 3}, base=1}, + strcharpart={args={2, 3}, base=1}, + strchars={args={1, 2}, base=1}, + strdisplaywidth={args={1, 2}, base=1}, + strftime={args={1, 2}, base=1}, + strgetchar={args=2, base=1}, + stridx={args={2, 3}, base=1}, string={args=1, base=1}, strlen={args=1, base=1}, - strpart={args={2, 4}}, - strptime={args=2}, - strridx={args={2, 3}}, + strpart={args={2, 4}, base=1}, + strptime={args=2, base=1}, + strridx={args={2, 3}, base=1}, strtrans={args=1, base=1}, strwidth={args=1, base=1}, - submatch={args={1, 2}}, + submatch={args={1, 2}, base=1}, substitute={args=4, base=1}, - swapinfo={args={1}}, - swapname={args={1}}, + swapinfo={args=1, base=1}, + swapname={args=1, base=1}, synID={args=3}, synIDattr={args={2, 3}, base=1}, synIDtrans={args=1, base=1}, @@ -374,58 +390,60 @@ return { synstack={args=2}, system={args={1, 2}, base=1}, systemlist={args={1, 3}, base=1}, - tabpagebuflist={args={0, 1}}, + tabpagebuflist={args={0, 1}, base=1}, tabpagenr={args={0, 1}}, - tabpagewinnr={args={1, 2}}, + tabpagewinnr={args={1, 2}, base=1}, tagfiles={}, - taglist={args={1, 2}}, + taglist={args={1, 2}, base=1}, tan={args=1, base=1, func="float_op_wrapper", data="&tan"}, tanh={args=1, base=1, func="float_op_wrapper", data="&tanh"}, tempname={}, termopen={args={1, 2}}, test_garbagecollect_now={}, test_write_list_log={args=1}, - timer_info={args={0,1}}, - timer_pause={args=2}, - timer_start={args={2,3}}, - timer_stop={args=1}, + timer_info={args={0, 1}, base=1}, + timer_pause={args=2, base=1}, + timer_start={args={2, 3}, base=1}, + timer_stop={args=1, base=1}, timer_stopall={args=0}, - tolower={args=1}, - toupper={args=1}, - tr={args=3}, - trim={args={1,3}}, + tolower={args=1, base=1}, + toupper={args=1, base=1}, + tr={args=3, base=1}, + trim={args={1, 3}, base=1}, trunc={args=1, base=1, func="float_op_wrapper", data="&trunc"}, type={args=1, base=1}, - undofile={args=1}, + undofile={args=1, base=1}, undotree={}, uniq={args={1, 3}, base=1}, values={args=1, base=1}, - virtcol={args=1}, + virtcol={args=1, base=1}, visualmode={args={0, 1}}, wait={args={2,3}}, wildmenumode={}, - win_execute={args={2, 3}}, - win_findbuf={args=1}, - win_getid={args={0,2}}, - win_gettype={args={0,1}}, - win_gotoid={args=1}, - win_id2tabwin={args=1}, - win_id2win={args=1}, - win_screenpos={args=1}, - win_splitmove={args={2, 3}}, + win_execute={args={2, 3}, base=2}, + win_findbuf={args=1, base=1}, + win_getid={args={0, 2}, base=1}, + win_gettype={args={0, 1}, base=1}, + 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}, wincol={}, windowsversion={}, - winheight={args=1}, - winlayout={args={0, 1}}, + winheight={args=1, base=1}, + winlayout={args={0, 1}, base=1}, winline={}, - winnr={args={0, 1}}, + winnr={args={0, 1}, base=1}, winrestcmd={}, - winrestview={args=1}, + winrestview={args=1, base=1}, winsaveview={}, - winwidth={args=1}, + winwidth={args=1, base=1}, wordcount={}, - writefile={args={2, 3}}, + writefile={args={2, 3}, base=1}, xor={args=2, base=1}, }, } diff --git a/src/nvim/eval/decode.c b/src/nvim/eval/decode.c index 5008945f09..797420c150 100644 --- a/src/nvim/eval/decode.c +++ b/src/nvim/eval/decode.c @@ -726,11 +726,11 @@ json_decode_string_cycle_start: semsg(_("E474: Using comma in place of colon: %.*s"), LENP(p, e)); goto json_decode_string_fail; } else if (last_container.special_val == NULL - ? (last_container.container.v_type == VAR_DICT - ? (DICT_LEN(last_container.container.vval.v_dict) == 0) - : (tv_list_len(last_container.container.vval.v_list) - == 0)) - : (tv_list_len(last_container.special_val) == 0)) { + ? (last_container.container.v_type == VAR_DICT + ? (DICT_LEN(last_container.container.vval.v_dict) == 0) + : (tv_list_len(last_container.container.vval.v_list) + == 0)) + : (tv_list_len(last_container.special_val) == 0)) { semsg(_("E474: Leading comma: %.*s"), LENP(p, e)); goto json_decode_string_fail; } diff --git a/src/nvim/eval/encode.c b/src/nvim/eval/encode.c index d694b8a374..6f4357421b 100644 --- a/src/nvim/eval/encode.c +++ b/src/nvim/eval/encode.c @@ -352,20 +352,20 @@ int encode_read_from_list(ListReaderState *const state, char *const buf, const s const float_T flt_ = (flt); \ switch (xfpclassify(flt_)) { \ case FP_NAN: { \ - ga_concat(gap, "str2float('nan')"); \ - break; \ + ga_concat(gap, "str2float('nan')"); \ + break; \ } \ case FP_INFINITE: { \ - if (flt_ < 0) { \ - ga_append(gap, '-'); \ - } \ - ga_concat(gap, "str2float('inf')"); \ - break; \ + if (flt_ < 0) { \ + ga_append(gap, '-'); \ + } \ + ga_concat(gap, "str2float('inf')"); \ + break; \ } \ default: { \ - char numbuf[NUMBUFLEN]; \ - vim_snprintf(numbuf, ARRAY_SIZE(numbuf), "%g", flt_); \ - ga_concat(gap, numbuf); \ + char numbuf[NUMBUFLEN]; \ + vim_snprintf(numbuf, ARRAY_SIZE(numbuf), "%g", flt_); \ + ga_concat(gap, numbuf); \ } \ } \ } while (0) @@ -556,18 +556,18 @@ int encode_read_from_list(ListReaderState *const state, char *const buf, const s const float_T flt_ = (flt); \ switch (xfpclassify(flt_)) { \ case FP_NAN: { \ - emsg(_("E474: Unable to represent NaN value in JSON")); \ - return FAIL; \ + emsg(_("E474: Unable to represent NaN value in JSON")); \ + return FAIL; \ } \ case FP_INFINITE: { \ - emsg(_("E474: Unable to represent infinity in JSON")); \ - return FAIL; \ + emsg(_("E474: Unable to represent infinity in JSON")); \ + return FAIL; \ } \ default: { \ - char numbuf[NUMBUFLEN]; \ - vim_snprintf(numbuf, ARRAY_SIZE(numbuf), "%g", flt_); \ - ga_concat(gap, numbuf); \ - break; \ + char numbuf[NUMBUFLEN]; \ + vim_snprintf(numbuf, ARRAY_SIZE(numbuf), "%g", flt_); \ + ga_concat(gap, numbuf); \ + break; \ } \ } \ } while (0) diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index 714c60c27e..609db3990b 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -15,6 +15,7 @@ #include "nvim/charset.h" #include "nvim/context.h" #include "nvim/cursor.h" +#include "nvim/digraph.h" #include "nvim/diff.h" #include "nvim/edit.h" #include "nvim/eval.h" @@ -22,6 +23,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" @@ -30,15 +32,17 @@ #include "nvim/fileio.h" #include "nvim/fold.h" #include "nvim/globals.h" +#include "nvim/highlight_group.h" #include "nvim/if_cscope.h" #include "nvim/indent.h" #include "nvim/indent_c.h" +#include "nvim/input.h" #include "nvim/lua/executor.h" #include "nvim/macros.h" #include "nvim/mark.h" +#include "nvim/match.h" #include "nvim/math.h" #include "nvim/memline.h" -#include "nvim/misc1.h" #include "nvim/mouse.h" #include "nvim/move.h" #include "nvim/msgpack_rpc/channel.h" @@ -61,6 +65,7 @@ #include "nvim/state.h" #include "nvim/syntax.h" #include "nvim/tag.h" +#include "nvim/testing.h" #include "nvim/ui.h" #include "nvim/undo.h" #include "nvim/version.h" @@ -97,9 +102,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 /// @@ -174,7 +179,7 @@ char_u *get_expr_name(expand_T *xp, int idx) /// /// @param[in] name Name of the function. /// -/// Returns pointer to the function definition or NULL if not found. +/// @return pointer to the function definition or NULL if not found. const VimLFuncDef *find_internal_func(const char *const name) FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE FUNC_ATTR_NONNULL_ALL { @@ -228,9 +233,7 @@ int call_internal_method(const char_u *const fname, const int argcount, typval_T return ERROR_NONE; } -/* - * Return TRUE for a non-zero Number and a non-empty String. - */ +/// @return TRUE for a non-zero Number and a non-empty String. static int non_zero_arg(typval_T *argvars) { return ((argvars[0].v_type == VAR_NUMBER @@ -242,11 +245,11 @@ static int non_zero_arg(typval_T *argvars) && *argvars[0].vval.v_string != NUL)); } -// Apply a floating point C function on a typval with one float_T. -// -// Some versions of glibc on i386 have an optimization that makes it harder to -// call math functions indirectly from inside an inlined function, causing -// compile-time errors. Avoid `inline` in that case. #3072 +/// Apply a floating point C function on a typval with one float_T. +/// +/// Some versions of glibc on i386 have an optimization that makes it harder to +/// call math functions indirectly from inside an inlined function, causing +/// compile-time errors. Avoid `inline` in that case. #3072 static void float_op_wrapper(typval_T *argvars, typval_T *rettv, FunPtr fptr) { float_T f; @@ -292,9 +295,7 @@ end: api_clear_error(&err); } -/* - * "abs(expr)" function - */ +/// "abs(expr)" function static void f_abs(typval_T *argvars, typval_T *rettv, FunPtr fptr) { if (argvars[0].v_type == VAR_FLOAT) { @@ -314,9 +315,7 @@ static void f_abs(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/* - * "add(list, item)" function - */ +/// "add(list, item)" function static void f_add(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->vval.v_number = 1; // Default: failed. @@ -344,9 +343,7 @@ static void f_add(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/* - * "and(expr, expr)" function - */ +/// "and(expr, expr)" function static void f_and(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->vval.v_number = tv_get_number_chk(&argvars[0], NULL) @@ -362,7 +359,7 @@ static void f_api_info(typval_T *argvars, typval_T *rettv, FunPtr fptr) api_free_dictionary(metadata); } -// "append(lnum, string/list)" function +/// "append(lnum, string/list)" function static void f_append(typval_T *argvars, typval_T *rettv, FunPtr fptr) { const linenr_T lnum = tv_get_lnum(&argvars[0]); @@ -370,7 +367,7 @@ static void f_append(typval_T *argvars, typval_T *rettv, FunPtr fptr) set_buffer_lines(curbuf, lnum, true, &argvars[1], rettv); } -// "appendbufline(buf, lnum, string/list)" function +/// "appendbufline(buf, lnum, string/list)" function static void f_appendbufline(typval_T *argvars, typval_T *rettv, FunPtr fptr) { buf_T *const buf = tv_get_buf(&argvars[0], false); @@ -402,9 +399,7 @@ static void f_argc(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/* - * "argidx()" function - */ +/// "argidx()" function static void f_argidx(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->vval.v_number = curwin->w_arg_idx; @@ -420,9 +415,7 @@ static void f_arglistid(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/* - * "argv(nr)" function - */ +/// "argv(nr)" function static void f_argv(typval_T *argvars, typval_T *rettv, FunPtr fptr) { aentry_T *arglist = NULL; @@ -457,93 +450,7 @@ static void f_argv(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -// "assert_beeps(cmd [, error])" function -static void f_assert_beeps(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = assert_beeps(argvars, false); -} - -// "assert_nobeep(cmd [, error])" function -static void f_assert_nobeep(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = assert_beeps(argvars, true); -} - -// "assert_equal(expected, actual[, msg])" function -static void f_assert_equal(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = assert_equal_common(argvars, ASSERT_EQUAL); -} - -// "assert_equalfile(fname-one, fname-two[, msg])" function -static void f_assert_equalfile(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = assert_equalfile(argvars); -} - -// "assert_notequal(expected, actual[, msg])" function -static void f_assert_notequal(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = assert_equal_common(argvars, ASSERT_NOTEQUAL); -} - -/// "assert_report(msg) -static void f_assert_report(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - garray_T ga; - - prepare_assert_error(&ga); - ga_concat(&ga, tv_get_string(&argvars[0])); - assert_error(&ga); - ga_clear(&ga); - rettv->vval.v_number = 1; -} - -/// "assert_exception(string[, msg])" function -static void f_assert_exception(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = assert_exception(argvars); -} - -/// "assert_fails(cmd [, error [, msg]])" function -static void f_assert_fails(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = assert_fails(argvars); -} - -// "assert_false(actual[, msg])" function -static void f_assert_false(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = assert_bool(argvars, false); -} - -/// "assert_inrange(lower, upper[, msg])" function -static void f_assert_inrange(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = assert_inrange(argvars); -} - -/// "assert_match(pattern, actual[, msg])" function -static void f_assert_match(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = assert_match_common(argvars, ASSERT_MATCH); -} - -/// "assert_notmatch(pattern, actual[, msg])" function -static void f_assert_notmatch(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = assert_match_common(argvars, ASSERT_NOTMATCH); -} - -// "assert_true(actual[, msg])" function -static void f_assert_true(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = assert_bool(argvars, true); -} - -/* - * "atan2()" function - */ +/// "atan2()" function static void f_atan2(typval_T *argvars, typval_T *rettv, FunPtr fptr) { float_T fx; @@ -557,27 +464,21 @@ static void f_atan2(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/* - * "browse(save, title, initdir, default)" function - */ +/// "browse(save, title, initdir, default)" function static void f_browse(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->vval.v_string = NULL; rettv->v_type = VAR_STRING; } -/* - * "browsedir(title, initdir)" function - */ +/// "browsedir(title, initdir)" function static void f_browsedir(typval_T *argvars, typval_T *rettv, FunPtr fptr) { f_browse(argvars, rettv, NULL); } -/* - * Find a buffer by number or exact name. - */ +/// Find a buffer by number or exact name. static buf_T *find_buffer(typval_T *avar) { buf_T *buf = NULL; @@ -604,7 +505,7 @@ static buf_T *find_buffer(typval_T *avar) return buf; } -// "bufadd(expr)" function +/// "bufadd(expr)" function static void f_bufadd(typval_T *argvars, typval_T *rettv, FunPtr fptr) { char_u *name = (char_u *)tv_get_string(&argvars[0]); @@ -612,17 +513,13 @@ static void f_bufadd(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_number = buflist_add(*name == NUL ? NULL : name, 0); } -/* - * "bufexists(expr)" function - */ +/// "bufexists(expr)" function static void f_bufexists(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->vval.v_number = (find_buffer(&argvars[0]) != NULL); } -/* - * "buflisted(expr)" function - */ +/// "buflisted(expr)" function static void f_buflisted(typval_T *argvars, typval_T *rettv, FunPtr fptr) { buf_T *buf; @@ -631,7 +528,7 @@ static void f_buflisted(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_number = (buf != NULL && buf->b_p_bl); } -// "bufload(expr)" function +/// "bufload(expr)" function static void f_bufload(typval_T *argvars, typval_T *unused, FunPtr fptr) { buf_T *buf = get_buf_arg(&argvars[0]); @@ -646,9 +543,7 @@ static void f_bufload(typval_T *argvars, typval_T *unused, FunPtr fptr) } } -/* - * "bufloaded(expr)" function - */ +/// "bufloaded(expr)" function static void f_bufloaded(typval_T *argvars, typval_T *rettv, FunPtr fptr) { buf_T *buf; @@ -657,9 +552,7 @@ static void f_bufloaded(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_number = (buf != NULL && buf->b_ml.ml_mfp != NULL); } -/* - * "bufname(expr)" function - */ +/// "bufname(expr)" function static void f_bufname(typval_T *argvars, typval_T *rettv, FunPtr fptr) { const buf_T *buf; @@ -675,9 +568,7 @@ static void f_bufname(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/* - * "bufnr(expr)" function - */ +/// "bufnr(expr)" function static void f_bufnr(typval_T *argvars, typval_T *rettv, FunPtr fptr) { const buf_T *buf; @@ -749,9 +640,7 @@ static void f_bufwinnr(typval_T *argvars, typval_T *rettv, FunPtr fptr) buf_win_common(argvars, rettv, true); } -/* - * Get buffer by number or pattern. - */ +/// Get buffer by number or pattern. buf_T *tv_get_buf(typval_T *tv, int curtab_only) { char_u *name = tv->vval.v_string; @@ -819,9 +708,7 @@ buf_T *get_buf_arg(typval_T *arg) return buf; } -/* - * "byte2line(byte)" function - */ +/// "byte2line(byte)" function static void f_byte2line(typval_T *argvars, typval_T *rettv, FunPtr fptr) { long boff = tv_get_number(&argvars[0]) - 1; @@ -856,17 +743,13 @@ static void byteidx(typval_T *argvars, typval_T *rettv, int comp) rettv->vval.v_number = (varnumber_T)(t - str); } -/* - * "byteidx()" function - */ +/// "byteidx()" function static void f_byteidx(typval_T *argvars, typval_T *rettv, FunPtr fptr) { byteidx(argvars, rettv, FALSE); } -/* - * "byteidxcomp()" function - */ +/// "byteidxcomp()" function static void f_byteidxcomp(typval_T *argvars, typval_T *rettv, FunPtr fptr) { byteidx(argvars, rettv, TRUE); @@ -893,6 +776,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 { @@ -917,15 +801,13 @@ static void f_call(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/* - * "changenr()" function - */ +/// "changenr()" function static void f_changenr(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->vval.v_number = curbuf->b_u_seq_cur; } -// "chanclose(id[, stream])" function +/// "chanclose(id[, stream])" function static void f_chanclose(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->v_type = VAR_NUMBER; @@ -964,7 +846,7 @@ static void f_chanclose(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -// "chansend(id, data)" function +/// "chansend(id, data)" function static void f_chansend(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->v_type = VAR_NUMBER; @@ -1005,9 +887,7 @@ static void f_chansend(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/* - * "char2nr(string)" function - */ +/// "char2nr(string)" function static void f_char2nr(typval_T *argvars, typval_T *rettv, FunPtr fptr) { if (argvars[1].v_type != VAR_UNKNOWN) { @@ -1019,7 +899,51 @@ 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])); } -// "charidx()" function +/// Get the current cursor column and store it in 'rettv'. +/// +/// @return the character index of the column if 'charcol' is true, +/// otherwise 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) { rettv->vval.v_number = -1; @@ -1065,7 +989,7 @@ static void f_charidx(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_number = len > 0 ? len - 1 : 0; } -// "chdir(dir)" function +/// "chdir(dir)" function static void f_chdir(typval_T *argvars, typval_T *rettv, FunPtr fptr) { char_u *cwd; @@ -1082,15 +1006,13 @@ static void f_chdir(typval_T *argvars, typval_T *rettv, FunPtr fptr) // Return the current directory cwd = xmalloc(MAXPATHL); - if (cwd != NULL) { - if (os_dirname(cwd, MAXPATHL) != FAIL) { + if (os_dirname(cwd, MAXPATHL) != FAIL) { #ifdef BACKSLASH_IN_FILENAME - slash_adjust(cwd); + slash_adjust(cwd); #endif - rettv->vval.v_string = vim_strsave(cwd); - } - xfree(cwd); + rettv->vval.v_string = vim_strsave(cwd); } + xfree(cwd); if (curwin->w_localdir != NULL) { scope = kCdScopeWindow; @@ -1104,9 +1026,7 @@ static void f_chdir(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/* - * "cindent(lnum)" function - */ +/// "cindent(lnum)" function static void f_cindent(typval_T *argvars, typval_T *rettv, FunPtr fptr) { pos_T pos; @@ -1123,7 +1043,7 @@ static void f_cindent(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -static win_T *get_optional_window(typval_T *argvars, int idx) +win_T *get_optional_window(typval_T *argvars, int idx) { win_T *win = curwin; @@ -1137,62 +1057,13 @@ static win_T *get_optional_window(typval_T *argvars, int idx) return win; } -/* - * "clearmatches()" function - */ -static void f_clearmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - win_T *win = get_optional_window(argvars, 0); - - if (win != NULL) { - clear_matches(win); - } -} - -/* - * "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); } -/* - * "complete()" function - */ +/// "complete()" function static void f_complete(typval_T *argvars, typval_T *rettv, FunPtr fptr) { if ((State & INSERT) == 0) { @@ -1219,17 +1090,13 @@ static void f_complete(typval_T *argvars, typval_T *rettv, FunPtr fptr) set_completion(startcol - 1, argvars[1].vval.v_list); } -/* - * "complete_add()" function - */ +/// "complete_add()" function static void f_complete_add(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->vval.v_number = ins_compl_add_tv(&argvars[0], 0, false); } -/* - * "complete_check()" function - */ +/// "complete_check()" function static void f_complete_check(typval_T *argvars, typval_T *rettv, FunPtr fptr) { int saved = RedrawingDisabled; @@ -1240,7 +1107,7 @@ static void f_complete_check(typval_T *argvars, typval_T *rettv, FunPtr fptr) RedrawingDisabled = saved; } -// "complete_info()" function +/// "complete_info()" function static void f_complete_info(typval_T *argvars, typval_T *rettv, FunPtr fptr) { tv_dict_alloc_ret(rettv); @@ -1257,9 +1124,7 @@ static void f_complete_info(typval_T *argvars, typval_T *rettv, FunPtr fptr) get_complete_info(what_list, rettv->vval.v_dict); } -/* - * "confirm(message, buttons[, default [, type]])" function - */ +/// "confirm(message, buttons[, default [, type]])" function static void f_confirm(typval_T *argvars, typval_T *rettv, FunPtr fptr) { char buf[NUMBUFLEN]; @@ -1314,17 +1179,13 @@ static void f_confirm(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/* - * "copy()" function - */ +/// "copy()" function static void f_copy(typval_T *argvars, typval_T *rettv, FunPtr fptr) { var_item_copy(NULL, &argvars[0], rettv, false, 0); } -/* - * "count()" function - */ +/// "count()" function static void f_count(typval_T *argvars, typval_T *rettv, FunPtr fptr) { long n = 0; @@ -1415,11 +1276,9 @@ static void f_count(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_number = n; } -/* - * "cscope_connection([{num} , {dbpath} [, {prepend}]])" function - * - * Checks the existence of a cscope connection. - */ +/// "cscope_connection([{num} , {dbpath} [, {prepend}]])" function +/// +/// Checks the existence of a cscope connection. static void f_cscope_connection(typval_T *argvars, typval_T *rettv, FunPtr fptr) { int num = 0; @@ -1550,24 +1409,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 offset. +/// 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; } @@ -1579,16 +1435,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; @@ -1607,7 +1469,18 @@ static void f_cursor(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_number = 0; } -// "debugbreak()" function +/// "cursor(lnum, col)" function, or +/// "cursor(list)" +/// +/// Moves the cursor to the specified line and column. +/// +/// @return 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) { int pid; @@ -1631,7 +1504,7 @@ static void f_debugbreak(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -// "deepcopy()" function +/// "deepcopy()" function static void f_deepcopy(typval_T *argvars, typval_T *rettv, FunPtr fptr) { int noref = 0; @@ -1648,7 +1521,7 @@ static void f_deepcopy(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -// "delete()" function +/// "delete()" function static void f_delete(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->vval.v_number = -1; @@ -1684,7 +1557,7 @@ static void f_delete(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -// dictwatcheradd(dict, key, funcref) function +/// dictwatcheradd(dict, key, funcref) function static void f_dictwatcheradd(typval_T *argvars, typval_T *rettv, FunPtr fptr) { if (check_secure()) { @@ -1722,7 +1595,7 @@ static void f_dictwatcheradd(typval_T *argvars, typval_T *rettv, FunPtr fptr) callback); } -// dictwatcherdel(dict, key, funcref) function +/// dictwatcherdel(dict, key, funcref) function static void f_dictwatcherdel(typval_T *argvars, typval_T *rettv, FunPtr fptr) { if (check_secure()) { @@ -1831,25 +1704,19 @@ static void f_deletebufline(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/* - * "did_filetype()" function - */ +/// "did_filetype()" function static void f_did_filetype(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->vval.v_number = did_filetype; } -/* - * "diff_filler()" function - */ +/// "diff_filler()" function static void f_diff_filler(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->vval.v_number = MAX(0, diff_check(curwin, tv_get_lnum(argvars))); } -/* - * "diff_hlID()" function - */ +/// "diff_hlID()" function static void f_diff_hlID(typval_T *argvars, typval_T *rettv, FunPtr fptr) { linenr_T lnum = tv_get_lnum(argvars); @@ -1901,9 +1768,7 @@ static void f_diff_hlID(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_number = hlID == (hlf_T)0 ? 0 : (int)(hlID + 1); } -/* - * "empty({expr})" function - */ +/// "empty({expr})" function static void f_empty(typval_T *argvars, typval_T *rettv, FunPtr fptr) { bool n = true; @@ -2000,9 +1865,7 @@ static void f_environ(typval_T *argvars, typval_T *rettv, FunPtr fptr) os_free_fullenv(env); } -/* - * "escape({string}, {chars})" function - */ +/// "escape({string}, {chars})" function static void f_escape(typval_T *argvars, typval_T *rettv, FunPtr fptr) { char buf[NUMBUFLEN]; @@ -2026,9 +1889,7 @@ static void f_getenv(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->v_type = VAR_STRING; } -/* - * "eval()" function - */ +/// "eval()" function static void f_eval(typval_T *argvars, typval_T *rettv, FunPtr fptr) { const char *s = tv_get_string_chk(&argvars[0]); @@ -2049,17 +1910,13 @@ static void f_eval(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/* - * "eventhandler()" function - */ +/// "eventhandler()" function static void f_eventhandler(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->vval.v_number = vgetc_busy; } -/* - * "executable()" function - */ +/// "executable()" function static void f_executable(typval_T *argvars, typval_T *rettv, FunPtr fptr) { if (tv_check_for_string(&argvars[0]) == FAIL) { @@ -2167,36 +2024,24 @@ static void execute_common(typval_T *argvars, typval_T *rettv, FunPtr fptr, int capture_ga = save_capture_ga; } -// "execute(command)" function +/// "execute(command)" function static void f_execute(typval_T *argvars, typval_T *rettv, FunPtr fptr) { execute_common(argvars, rettv, fptr, 0); } -// "win_execute(win_id, command)" function +/// "win_execute(win_id, command)" function 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; + int id = tv_get_number(argvars); + tabpage_T *tp; + win_T *wp = win_id2wp_tp(id, &tp); 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)); } } @@ -2215,9 +2060,7 @@ static void f_exepath(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_string = (char_u *)path; } -/* - * "exists()" function - */ +/// "exists()" function static void f_exists(typval_T *argvars, typval_T *rettv, FunPtr fptr) { int n = false; @@ -2257,9 +2100,7 @@ static void f_exists(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_number = n; } -/* - * "expand()" function - */ +/// "expand()" function static void f_expand(typval_T *argvars, typval_T *rettv, FunPtr fptr) { size_t len; @@ -2344,8 +2185,8 @@ static void f_menu_get(typval_T *argvars, typval_T *rettv, FunPtr fptr) menu_get((char_u *)tv_get_string(&argvars[0]), modes, rettv->vval.v_list); } -// "expandcmd()" function -// Expand all the special characters in a command string. +/// "expandcmd()" function +/// Expand all the special characters in a command string. static void f_expandcmd(typval_T *argvars, typval_T *rettv, FunPtr fptr) { char *errormsg = NULL; @@ -2405,10 +2246,8 @@ static void f_flatten(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/* - * "extend(list, list [, idx])" function - * "extend(dict, dict [, action])" function - */ +/// "extend(list, list [, idx])" function +/// "extend(dict, dict [, action])" function static void f_extend(typval_T *argvars, typval_T *rettv, FunPtr fptr) { const char *const arg_errmsg = N_("extend() argument"); @@ -2485,9 +2324,7 @@ static void f_extend(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/* - * "feedkeys()" function - */ +/// "feedkeys()" function static void f_feedkeys(typval_T *argvars, typval_T *rettv, FunPtr fptr) { // This is not allowed in the sandbox. If the commands would still be @@ -2516,10 +2353,9 @@ static void f_filereadable(typval_T *argvars, typval_T *rettv, FunPtr fptr) (*p && !os_isdir((const char_u *)p) && os_file_is_readable(p)); } -/* - * Return 0 for not writable, 1 for writable file, 2 for a dir which we have - * rights to write into. - */ +/// @return 0 for not writable +/// 1 for writable file +/// 2 for a dir which we have rights to write into. static void f_filewritable(typval_T *argvars, typval_T *rettv, FunPtr fptr) { const char *filename = tv_get_string(&argvars[0]); @@ -2586,33 +2422,25 @@ static void findfilendir(typval_T *argvars, typval_T *rettv, int find_what) } -/* - * "filter()" function - */ +/// "filter()" function static void f_filter(typval_T *argvars, typval_T *rettv, FunPtr fptr) { filter_map(argvars, rettv, FALSE); } -/* - * "finddir({fname}[, {path}[, {count}]])" function - */ +/// "finddir({fname}[, {path}[, {count}]])" function static void f_finddir(typval_T *argvars, typval_T *rettv, FunPtr fptr) { findfilendir(argvars, rettv, FINDFILE_DIR); } -/* - * "findfile({fname}[, {path}[, {count}]])" function - */ +/// "findfile({fname}[, {path}[, {count}]])" function static void f_findfile(typval_T *argvars, typval_T *rettv, FunPtr fptr) { findfilendir(argvars, rettv, FINDFILE_FILE); } -/* - * "float2nr({float})" function - */ +/// "float2nr({float})" function static void f_float2nr(typval_T *argvars, typval_T *rettv, FunPtr fptr) { float_T f; @@ -2628,9 +2456,7 @@ static void f_float2nr(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/* - * "fmod()" function - */ +/// "fmod()" function static void f_fmod(typval_T *argvars, typval_T *rettv, FunPtr fptr) { float_T fx; @@ -2644,18 +2470,14 @@ static void f_fmod(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/* - * "fnameescape({string})" function - */ +/// "fnameescape({string})" function static void f_fnameescape(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->vval.v_string = (char_u *)vim_strsave_fnameescape(tv_get_string(&argvars[0]), false); rettv->v_type = VAR_STRING; } -/* - * "fnamemodify({fname}, {mods})" function - */ +/// "fnamemodify({fname}, {mods})" function static void f_fnamemodify(typval_T *argvars, typval_T *rettv, FunPtr fptr) { char_u *fbuf = NULL; @@ -2684,9 +2506,7 @@ static void f_fnamemodify(typval_T *argvars, typval_T *rettv, FunPtr fptr) } -/* - * "foldclosed()" function - */ +/// "foldclosed()" function static void foldclosed_both(typval_T *argvars, typval_T *rettv, int end) { const linenr_T lnum = tv_get_lnum(argvars); @@ -2705,25 +2525,19 @@ static void foldclosed_both(typval_T *argvars, typval_T *rettv, int end) rettv->vval.v_number = -1; } -/* - * "foldclosed()" function - */ +/// "foldclosed()" function static void f_foldclosed(typval_T *argvars, typval_T *rettv, FunPtr fptr) { foldclosed_both(argvars, rettv, FALSE); } -/* - * "foldclosedend()" function - */ +/// "foldclosedend()" function static void f_foldclosedend(typval_T *argvars, typval_T *rettv, FunPtr fptr) { foldclosed_both(argvars, rettv, TRUE); } -/* - * "foldlevel()" function - */ +/// "foldlevel()" function static void f_foldlevel(typval_T *argvars, typval_T *rettv, FunPtr fptr) { const linenr_T lnum = tv_get_lnum(argvars); @@ -2732,9 +2546,7 @@ static void f_foldlevel(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/* - * "foldtext()" function - */ +/// "foldtext()" function static void f_foldtext(typval_T *argvars, typval_T *rettv, FunPtr fptr) { linenr_T foldstart; @@ -2787,9 +2599,7 @@ static void f_foldtext(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/* - * "foldtextresult(lnum)" function - */ +/// "foldtextresult(lnum)" function static void f_foldtextresult(typval_T *argvars, typval_T *rettv, FunPtr fptr) { char_u *text; @@ -2820,9 +2630,7 @@ static void f_foldtextresult(typval_T *argvars, typval_T *rettv, FunPtr fptr) entered = false; } -/* - * "foreground()" function - */ +/// "foreground()" function static void f_foreground(typval_T *argvars, typval_T *rettv, FunPtr fptr) { } @@ -2849,9 +2657,7 @@ static void f_garbagecollect(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/* - * "get()" function - */ +/// "get()" function static void f_get(typval_T *argvars, typval_T *rettv, FunPtr fptr) { listitem_T *li; @@ -3011,12 +2817,12 @@ static void f_getbufinfo(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/* - * Get line or list of lines from buffer "buf" into "rettv". - * Return a range (from start to end) of lines in rettv from the specified - * buffer. - * If 'retlist' is TRUE, then the lines are returned as a Vim List. - */ +/// Get line or list of lines from buffer "buf" into "rettv". +/// +/// @param retlist if TRUE, then the lines are returned as a Vim List. +/// +/// @return range (from start to end) of lines in rettv from the specified +/// buffer. static void get_buffer_lines(buf_T *buf, linenr_T start, linenr_T end, int retlist, typval_T *rettv) { rettv->v_type = (retlist ? VAR_LIST : VAR_STRING); @@ -3049,9 +2855,7 @@ static void get_buffer_lines(buf_T *buf, linenr_T start, linenr_T end, int retli } } -/* - * "getbufline()" function - */ +/// "getbufline()" function static void f_getbufline(typval_T *argvars, typval_T *rettv, FunPtr fptr) { buf_T *const buf = tv_get_buf_from_arg(&argvars[0]); @@ -3064,9 +2868,7 @@ static void f_getbufline(typval_T *argvars, typval_T *rettv, FunPtr fptr) get_buffer_lines(buf, lnum, end, true, rettv); } -/* - * "getbufvar()" function - */ +/// "getbufvar()" function static void f_getbufvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) { bool done = false; @@ -3126,7 +2928,7 @@ f_getbufvar_end: } } -// "getchangelist()" function +/// "getchangelist()" function static void f_getchangelist(typval_T *argvars, typval_T *rettv, FunPtr fptr) { tv_list_alloc_ret(rettv, 2); @@ -3166,7 +2968,7 @@ static void f_getchangelist(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -// "getchar()" and "getcharstr()" functions +/// "getchar()" and "getcharstr()" functions static void getchar_common(typval_T *argvars, typval_T *rettv) FUNC_ATTR_NONNULL_ALL { @@ -3182,7 +2984,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(); @@ -3218,7 +3020,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; @@ -3268,13 +3070,13 @@ static void getchar_common(typval_T *argvars, typval_T *rettv) } } -// "getchar()" function +/// "getchar()" function static void f_getchar(typval_T *argvars, typval_T *rettv, FunPtr fptr) { getchar_common(argvars, rettv); } -// "getcharstr()" function +/// "getcharstr()" function static void f_getcharstr(typval_T *argvars, typval_T *rettv, FunPtr fptr) { getchar_common(argvars, rettv); @@ -3294,17 +3096,74 @@ static void f_getcharstr(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/* - * "getcharmod()" function - */ +/// "getcharmod()" function static void f_getcharmod(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->vval.v_number = mod_mask; } -/* - * "getcharsearch()" function - */ +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 static void f_getcharsearch(typval_T *argvars, typval_T *rettv, FunPtr fptr) { tv_dict_alloc_ret(rettv); @@ -3316,26 +3175,20 @@ static void f_getcharsearch(typval_T *argvars, typval_T *rettv, FunPtr fptr) tv_dict_add_nr(dict, S_LEN("until"), last_csearch_until()); } -/* - * "getcmdline()" function - */ +/// "getcmdline()" function static void f_getcmdline(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->v_type = VAR_STRING; rettv->vval.v_string = get_cmdline_str(); } -/* - * "getcmdpos()" function - */ +/// "getcmdpos()" function static void f_getcmdpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->vval.v_number = get_cmdline_pos() + 1; } -/* - * "getcmdtype()" function - */ +/// "getcmdtype()" function static void f_getcmdtype(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->v_type = VAR_STRING; @@ -3343,9 +3196,7 @@ static void f_getcmdtype(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_string[0] = get_cmdline_type(); } -/* - * "getcmdwintype()" function - */ +/// "getcmdwintype()" function static void f_getcmdwintype(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->v_type = VAR_STRING; @@ -3354,7 +3205,7 @@ static void f_getcmdwintype(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_string[0] = cmdwin_type; } -// "getcompletion()" function +/// "getcompletion()" function static void f_getcompletion(typval_T *argvars, typval_T *rettv, FunPtr fptr) { char_u *pat; @@ -3386,15 +3237,17 @@ static void f_getcompletion(typval_T *argvars, typval_T *rettv, FunPtr fptr) emsg(_(e_invarg)); return; } + const char *pattern = tv_get_string(&argvars[0]); if (strcmp(type, "cmdline") == 0) { - set_one_cmd_context(&xpc, tv_get_string(&argvars[0])); + set_one_cmd_context(&xpc, pattern); xpc.xp_pattern_len = STRLEN(xpc.xp_pattern); + xpc.xp_col = STRLEN(pattern); goto theend; } ExpandInit(&xpc); - xpc.xp_pattern = (char_u *)tv_get_string(&argvars[0]); + xpc.xp_pattern = (char_u *)pattern; xpc.xp_pattern_len = STRLEN(xpc.xp_pattern); xpc.xp_context = cmdcomplete_str_to_type(type); if (xpc.xp_context == EXPAND_NOTHING) { @@ -3485,11 +3338,6 @@ static void f_getcwd(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } - // If the user didn't specify anything, default to window scope - if (scope == kCdScopeInvalid) { - scope = MIN_CD_SCOPE; - } - // Find the tabpage by number if (scope_number[kCdScopeTabpage] > 0) { tp = find_tabpage(scope_number[kCdScopeTabpage]); @@ -3535,12 +3383,13 @@ static void f_getcwd(typval_T *argvars, typval_T *rettv, FunPtr fptr) case kCdScopeGlobal: if (globaldir) { // `globaldir` is not always set. from = globaldir; - } else if (os_dirname(cwd, MAXPATHL) == FAIL) { // Get the OS CWD. + break; + } + FALLTHROUGH; // In global directory, just need to get OS CWD. + case kCdScopeInvalid: // If called without any arguments, get OS CWD. + if (os_dirname(cwd, MAXPATHL) == FAIL) { from = (char_u *)""; // Return empty string on failure. } - break; - case kCdScopeInvalid: // We should never get here - abort(); } if (from) { @@ -3555,18 +3404,14 @@ static void f_getcwd(typval_T *argvars, typval_T *rettv, FunPtr fptr) xfree(cwd); } -/* - * "getfontname()" function - */ +/// "getfontname()" function static void f_getfontname(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->v_type = VAR_STRING; rettv->vval.v_string = NULL; } -/* - * "getfperm({fname})" function - */ +/// "getfperm({fname})" function static void f_getfperm(typval_T *argvars, typval_T *rettv, FunPtr fptr) { char *perm = NULL; @@ -3586,9 +3431,7 @@ static void f_getfperm(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_string = (char_u *)perm; } -/* - * "getfsize({fname})" function - */ +/// "getfsize({fname})" function static void f_getfsize(typval_T *argvars, typval_T *rettv, FunPtr fptr) { const char *fname = tv_get_string(&argvars[0]); @@ -3613,9 +3456,7 @@ static void f_getfsize(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/* - * "getftime({fname})" function - */ +/// "getftime({fname})" function static void f_getftime(typval_T *argvars, typval_T *rettv, FunPtr fptr) { const char *fname = tv_get_string(&argvars[0]); @@ -3628,9 +3469,7 @@ static void f_getftime(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/* - * "getftype({fname})" function - */ +/// "getftype({fname})" function static void f_getftype(typval_T *argvars, typval_T *rettv, FunPtr fptr) { char_u *type = NULL; @@ -3664,7 +3503,7 @@ static void f_getftype(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_string = type; } -// "getjumplist()" function +/// "getjumplist()" function static void f_getjumplist(typval_T *argvars, typval_T *rettv, FunPtr fptr) { tv_list_alloc_ret(rettv, kListLenMayKnow); @@ -3695,9 +3534,7 @@ static void f_getjumplist(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/* - * "getline(lnum, [end])" function - */ +/// "getline(lnum, [end])" function static void f_getline(typval_T *argvars, typval_T *rettv, FunPtr fptr) { linenr_T end; @@ -3741,65 +3578,8 @@ static void f_getmarklist(typval_T *argvars, typval_T *rettv, FunPtr fptr) get_buf_local_marks(buf, rettv->vval.v_list); } -/* - * "getmatches()" function - */ -static void f_getmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - matchitem_T *cur; - int i; - win_T *win = get_optional_window(argvars, 0); - - if (win == NULL) { - return; - } - - tv_list_alloc_ret(rettv, kListLenMayKnow); - cur = win->w_match_head; - while (cur != NULL) { - dict_T *dict = tv_dict_alloc(); - if (cur->match.regprog == NULL) { - // match added with matchaddpos() - for (i = 0; i < MAXPOSMATCH; i++) { - llpos_T *llpos; - char buf[30]; // use 30 to avoid compiler warning - - llpos = &cur->pos.pos[i]; - if (llpos->lnum == 0) { - break; - } - list_T *const l = tv_list_alloc(1 + (llpos->col > 0 ? 2 : 0)); - tv_list_append_number(l, (varnumber_T)llpos->lnum); - if (llpos->col > 0) { - tv_list_append_number(l, (varnumber_T)llpos->col); - tv_list_append_number(l, (varnumber_T)llpos->len); - } - int len = snprintf(buf, sizeof(buf), "pos%d", i + 1); - assert((size_t)len < sizeof(buf)); - tv_dict_add_list(dict, buf, (size_t)len, l); - } - } else { - tv_dict_add_str(dict, S_LEN("pattern"), (const char *)cur->pattern); - } - tv_dict_add_str(dict, S_LEN("group"), - (const char *)syn_id2name(cur->hlg_id)); - tv_dict_add_nr(dict, S_LEN("priority"), (varnumber_T)cur->priority); - tv_dict_add_nr(dict, S_LEN("id"), (varnumber_T)cur->id); - - if (cur->conceal_char) { - char buf[MB_MAXBYTES + 1]; - - buf[utf_char2bytes(cur->conceal_char, (char_u *)buf)] = NUL; - tv_dict_add_str(dict, S_LEN("conceal"), buf); - } - - tv_list_append_dict(rettv->vval.v_list, dict); - cur = cur->next; - } -} - -// "getmousepos()" function -void f_getmousepos(typval_T *argvars, typval_T *rettv, FunPtr fptr) +/// "getmousepos()" function +static void f_getmousepos(typval_T *argvars, typval_T *rettv, FunPtr fptr) { dict_T *d; win_T *wp; @@ -3809,7 +3589,7 @@ void f_getmousepos(typval_T *argvars, typval_T *rettv, FunPtr fptr) varnumber_T winid = 0; varnumber_T winrow = 0; varnumber_T wincol = 0; - linenr_T line = 0; + linenr_T lnum = 0; varnumber_T column = 0; tv_dict_alloc_ret(rettv); @@ -3820,7 +3600,7 @@ void f_getmousepos(typval_T *argvars, typval_T *rettv, FunPtr fptr) wp = mouse_find_win(&grid, &row, &col); if (wp != NULL) { - int height = wp->w_height + wp->w_status_height; + int height = wp->w_height + wp->w_hsep_height + wp->w_status_height; // The height is adjusted by 1 when there is a bottom border. This is not // necessary for a top border since `row` starts at -1 in that case. if (row < height + wp->w_border_adj[2]) { @@ -3828,18 +3608,8 @@ void f_getmousepos(typval_T *argvars, typval_T *rettv, FunPtr fptr) winrow = row + 1 + wp->w_border_adj[0]; // Adjust by 1 for top border wincol = col + 1 + wp->w_border_adj[3]; // Adjust by 1 for left border if (row >= 0 && row < wp->w_height && col >= 0 && col < wp->w_width) { - char_u *p; - int count; - - mouse_comp_pos(wp, &row, &col, &line); - - // limit to text length plus one - p = ml_get_buf(wp->w_buffer, line, false); - count = (int)STRLEN(p); - if (col > count) { - col = count; - } - + (void)mouse_comp_pos(wp, &row, &col, &lnum); + col = vcol2col(wp, lnum, col); column = col + 1; } } @@ -3847,73 +3617,31 @@ void f_getmousepos(typval_T *argvars, typval_T *rettv, FunPtr fptr) tv_dict_add_nr(d, S_LEN("winid"), winid); tv_dict_add_nr(d, S_LEN("winrow"), winrow); tv_dict_add_nr(d, S_LEN("wincol"), wincol); - tv_dict_add_nr(d, S_LEN("line"), (varnumber_T)line); + tv_dict_add_nr(d, S_LEN("line"), (varnumber_T)lnum); tv_dict_add_nr(d, S_LEN("column"), column); } -/* - * "getpid()" function - */ +/// "getpid()" function 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 @@ -3922,34 +3650,46 @@ static void f_getqflist(typval_T *argvars, typval_T *rettv, FunPtr fptr) get_qf_loc_list(true, NULL, &argvars[0], rettv); } -/// "getreg()" function -static void f_getreg(typval_T *argvars, typval_T *rettv, FunPtr fptr) +/// Common between getreg(), getreginfo() and getregtype(): get the register +/// name from the first argument. +/// Returns zero on error. +static int getreg_get_regname(typval_T *argvars) { - const char *strregname; - int arg2 = false; - bool return_list = false; - bool error = false; + const char_u *strregname; if (argvars[0].v_type != VAR_UNKNOWN) { - strregname = tv_get_string_chk(&argvars[0]); - error = strregname == NULL; - if (argvars[1].v_type != VAR_UNKNOWN) { - arg2 = tv_get_number_chk(&argvars[1], &error); - if (!error && argvars[2].v_type != VAR_UNKNOWN) { - return_list = tv_get_number_chk(&argvars[2], &error); - } + strregname = (const char_u *)tv_get_string_chk(&argvars[0]); + if (strregname == NULL) { // type error; errmsg already given + return 0; } } else { - strregname = _(get_vim_var_str(VV_REG)); + // Default to v:register + strregname = get_vim_var_str(VV_REG); } - if (error) { + return *strregname == 0 ? '"' : *strregname; +} + +/// "getreg()" function +static void f_getreg(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + int arg2 = false; + bool return_list = false; + + int regname = getreg_get_regname(argvars); + if (regname == 0) { return; } - int regname = (uint8_t)(strregname == NULL ? '"' : *strregname); - if (regname == 0) { - regname = '"'; + if (argvars[0].v_type != VAR_UNKNOWN && argvars[1].v_type != VAR_UNKNOWN) { + bool error = false; + arg2 = (int)tv_get_number_chk(&argvars[1], &error); + if (!error && argvars[2].v_type != VAR_UNKNOWN) { + return_list = (bool)tv_get_number_chk(&argvars[2], &error); + } + if (error) { + return; + } } if (return_list) { @@ -3966,28 +3706,16 @@ static void f_getreg(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/* - * "getregtype()" function - */ +/// "getregtype()" function static void f_getregtype(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - const char *strregname; - - if (argvars[0].v_type != VAR_UNKNOWN) { - strregname = tv_get_string_chk(&argvars[0]); - if (strregname == NULL) { // Type error; errmsg already given. - rettv->v_type = VAR_STRING; - rettv->vval.v_string = NULL; - return; - } - } else { - // Default to v:register. - strregname = _(get_vim_var_str(VV_REG)); - } + // on error return an empty string + rettv->v_type = VAR_STRING; + rettv->vval.v_string = NULL; - int regname = (uint8_t)(strregname == NULL ? '"' : *strregname); + int regname = getreg_get_regname(argvars); if (regname == 0) { - regname = '"'; + return; } colnr_T reglen = 0; @@ -3995,7 +3723,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); } @@ -4031,13 +3758,9 @@ static void f_gettabinfo(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/* - * "gettabvar()" function - */ +/// "gettabvar()" function 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; @@ -4051,7 +3774,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', @@ -4064,7 +3788,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) { @@ -4072,15 +3796,13 @@ static void f_gettabvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/* - * "gettabwinvar()" function - */ +/// "gettabwinvar()" function static void f_gettabwinvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) { getwinvar(argvars, rettv, 1); } -// "gettagstack()" function +/// "gettagstack()" function static void f_gettagstack(typval_T *argvars, typval_T *rettv, FunPtr fptr) { win_T *wp = curwin; // default is current window @@ -4105,7 +3827,7 @@ static void f_getwininfo(typval_T *argvars, typval_T *rettv, FunPtr fptr) tv_list_alloc_ret(rettv, kListLenMayKnow); if (argvars[0].v_type != VAR_UNKNOWN) { - wparg = win_id2wp(argvars); + wparg = win_id2wp(tv_get_number(&argvars[0])); if (wparg == NULL) { return; } @@ -4132,12 +3854,12 @@ static void f_getwininfo(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -// Dummy timer callback. Used by f_wait(). +/// Dummy timer callback. Used by f_wait(). static void dummy_timer_due_cb(TimeWatcher *tw, void *data) { } -// Dummy timer close callback. Used by f_wait(). +/// Dummy timer close callback. Used by f_wait(). static void dummy_timer_close_cb(TimeWatcher *tw, void *data) { xfree(tw); @@ -4200,7 +3922,7 @@ static void f_wait(typval_T *argvars, typval_T *rettv, FunPtr fptr) time_watcher_close(tw, dummy_timer_close_cb); } -// "win_screenpos()" function +/// "win_screenpos()" function static void f_win_screenpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) { tv_list_alloc_ret(rettv, 2); @@ -4209,16 +3931,14 @@ static void f_win_screenpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) tv_list_append_number(rettv->vval.v_list, wp == NULL ? 0 : wp->w_wincol + 1); } -// -// Move the window wp into a new split of targetwin in a given direction -// +/// Move the window wp into a new split of targetwin in a given direction static void win_move_into_split(win_T *wp, win_T *targetwin, int size, int flags) { int dir; int height = wp->w_height; win_T *oldwin = curwin; - if (wp == targetwin) { + if (wp == targetwin || wp == aucmd_win) { return; } @@ -4249,7 +3969,7 @@ static void win_move_into_split(win_T *wp, win_T *targetwin, int size, int flags } } -// "win_splitmove()" function +/// "win_splitmove()" function static void f_win_splitmove(typval_T *argvars, typval_T *rettv, FunPtr fptr) { win_T *wp; @@ -4289,7 +4009,7 @@ static void f_win_splitmove(typval_T *argvars, typval_T *rettv, FunPtr fptr) win_move_into_split(wp, targetwin, size, flags); } -// "getwinpos({timeout})" function +/// "getwinpos({timeout})" function static void f_getwinpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) { tv_list_alloc_ret(rettv, 2); @@ -4297,17 +4017,13 @@ static void f_getwinpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) tv_list_append_number(rettv->vval.v_list, -1); } -/* - * "getwinposx()" function - */ +/// "getwinposx()" function static void f_getwinposx(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->vval.v_number = -1; } -/* - * "getwinposy()" function - */ +/// "getwinposy()" function static void f_getwinposy(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->vval.v_number = -1; @@ -4319,9 +4035,7 @@ static void f_getwinvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) getwinvar(argvars, rettv, 0); } -/* - * "glob()" function - */ +/// "glob()" function static void f_glob(typval_T *argvars, typval_T *rettv, FunPtr fptr) { int options = WILD_SILENT|WILD_USE_NL; @@ -4419,7 +4133,7 @@ static void f_globpath(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -// "glob2regpat()" function +/// "glob2regpat()" function static void f_glob2regpat(typval_T *argvars, typval_T *rettv, FunPtr fptr) { const char *const pat = tv_get_string_chk(&argvars[0]); // NULL on type error @@ -4438,6 +4152,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 @@ -4509,6 +4229,7 @@ static void f_has(typval_T *argvars, typval_T *rettv, FunPtr fptr) "mouse", "multi_byte", "multi_lang", + "nanotime", "num64", "packages", "path_extra", @@ -4748,9 +4469,7 @@ static void f_haslocaldir(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/* - * "hasmapto()" function - */ +/// "hasmapto()" function static void f_hasmapto(typval_T *argvars, typval_T *rettv, FunPtr fptr) { const char *mode; @@ -4773,9 +4492,7 @@ static void f_hasmapto(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/* - * "histadd()" function - */ +/// "histadd()" function static void f_histadd(typval_T *argvars, typval_T *rettv, FunPtr fptr) { HistoryType histype; @@ -4798,9 +4515,7 @@ static void f_histadd(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/* - * "histdel()" function - */ +/// "histdel()" function static void f_histdel(typval_T *argvars, typval_T *rettv, FunPtr fptr) { int n; @@ -4823,9 +4538,7 @@ static void f_histdel(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_number = n; } -/* - * "histget()" function - */ +/// "histget()" function static void f_histget(typval_T *argvars, typval_T *rettv, FunPtr fptr) { HistoryType type; @@ -4847,9 +4560,7 @@ static void f_histget(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->v_type = VAR_STRING; } -/* - * "histnr()" function - */ +/// "histnr()" function static void f_histnr(typval_T *argvars, typval_T *rettv, FunPtr fptr) { const char *const history = tv_get_string_chk(&argvars[0]); @@ -4862,25 +4573,19 @@ static void f_histnr(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_number = i; } -/* - * "highlightID(name)" function - */ +/// "highlightID(name)" function static void f_hlID(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->vval.v_number = syn_name2id(tv_get_string(&argvars[0])); } -/* - * "highlight_exists()" function - */ +/// "highlight_exists()" function static void f_hlexists(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->vval.v_number = highlight_exists(tv_get_string(&argvars[0])); } -/* - * "hostname()" function - */ +/// "hostname()" function static void f_hostname(typval_T *argvars, typval_T *rettv, FunPtr fptr) { char hostname[256]; @@ -4890,9 +4595,7 @@ static void f_hostname(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_string = vim_strsave((char_u *)hostname); } -/* - * iconv() function - */ +/// iconv() function static void f_iconv(typval_T *argvars, typval_T *rettv, FunPtr fptr) { vimconv_T vimconv; @@ -4920,9 +4623,7 @@ static void f_iconv(typval_T *argvars, typval_T *rettv, FunPtr fptr) xfree(to); } -/* - * "indent()" function - */ +/// "indent()" function static void f_indent(typval_T *argvars, typval_T *rettv, FunPtr fptr) { const linenr_T lnum = tv_get_lnum(argvars); @@ -4933,9 +4634,7 @@ static void f_indent(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/* - * "index()" function - */ +/// "index()" function static void f_index(typval_T *argvars, typval_T *rettv, FunPtr fptr) { long idx = 0; @@ -5009,26 +4708,20 @@ static void f_index(typval_T *argvars, typval_T *rettv, FunPtr fptr) static bool inputsecret_flag = false; -/* - * "input()" function - * Also handles inputsecret() when inputsecret is set. - */ +/// "input()" function +/// Also handles inputsecret() when inputsecret is set. static void f_input(typval_T *argvars, typval_T *rettv, FunPtr fptr) { get_user_input(argvars, rettv, FALSE, inputsecret_flag); } -/* - * "inputdialog()" function - */ +/// "inputdialog()" function static void f_inputdialog(typval_T *argvars, typval_T *rettv, FunPtr fptr) { get_user_input(argvars, rettv, TRUE, inputsecret_flag); } -/* - * "inputlist()" function - */ +/// "inputlist()" function static void f_inputlist(typval_T *argvars, typval_T *rettv, FunPtr fptr) { int selected; @@ -5094,9 +4787,7 @@ static void f_inputsecret(typval_T *argvars, typval_T *rettv, FunPtr fptr) inputsecret_flag = false; } -/* - * "insert()" function - */ +/// "insert()" function static void f_insert(typval_T *argvars, typval_T *rettv, FunPtr fptr) { list_T *l; @@ -5168,32 +4859,26 @@ static void f_insert(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -// "interrupt()" function +/// "interrupt()" function static void f_interrupt(typval_T *argvars FUNC_ATTR_UNUSED, typval_T *rettv FUNC_ATTR_UNUSED, FunPtr fptr FUNC_ATTR_UNUSED) { got_int = true; } -/* - * "invert(expr)" function - */ +/// "invert(expr)" function static void f_invert(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->vval.v_number = ~tv_get_number_chk(&argvars[0], NULL); } -/* - * "isdirectory()" function - */ +/// "isdirectory()" function static void f_isdirectory(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->vval.v_number = os_isdir((const char_u *)tv_get_string(&argvars[0])); } -/* - * "islocked()" function - */ +/// "islocked()" function static void f_islocked(typval_T *argvars, typval_T *rettv, FunPtr fptr) { lval_T lv; @@ -5236,7 +4921,7 @@ static void f_islocked(typval_T *argvars, typval_T *rettv, FunPtr fptr) clear_lval(&lv); } -// "isinf()" function +/// "isinf()" function static void f_isinf(typval_T *argvars, typval_T *rettv, FunPtr fptr) { if (argvars[0].v_type == VAR_FLOAT @@ -5245,7 +4930,7 @@ static void f_isinf(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -// "isnan()" function +/// "isnan()" function static void f_isnan(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->vval.v_number = argvars[0].v_type == VAR_FLOAT @@ -5263,15 +4948,13 @@ static void f_id(typval_T *argvars, typval_T *rettv, FunPtr fptr) dummy_ap, argvars); } -/* - * "items(dict)" function - */ +/// "items(dict)" function static void f_items(typval_T *argvars, typval_T *rettv, FunPtr fptr) { dict_list(argvars, rettv, 2); } -// "jobpid(id)" function +/// "jobpid(id)" function static void f_jobpid(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->v_type = VAR_NUMBER; @@ -5295,7 +4978,7 @@ static void f_jobpid(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_number = proc->pid; } -// "jobresize(job, width, height)" function +/// "jobresize(job, width, height)" function static void f_jobresize(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->v_type = VAR_NUMBER; @@ -5442,7 +5125,7 @@ static dict_T *create_environment(const dictitem_T *job_env, const bool clear_en return env; } -// "jobstart()" function +/// "jobstart()" function static void f_jobstart(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->v_type = VAR_NUMBER; @@ -5563,7 +5246,7 @@ static void f_jobstart(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -// "jobstop()" function +/// "jobstop()" function static void f_jobstop(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->v_type = VAR_NUMBER; @@ -5596,7 +5279,7 @@ static void f_jobstop(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -// "jobwait(ids[, timeout])" function +/// "jobwait(ids[, timeout])" function static void f_jobwait(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->v_type = VAR_NUMBER; @@ -5695,9 +5378,7 @@ static void f_jobwait(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_list = rv; } -/* - * "join()" function - */ +/// "join()" function static void f_join(typval_T *argvars, typval_T *rettv, FunPtr fptr) { if (argvars[0].v_type != VAR_LIST) { @@ -5762,17 +5443,13 @@ static void f_json_encode(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_string = (char_u *)encode_tv2json(&argvars[0], NULL); } -/* - * "keys()" function - */ +/// "keys()" function static void f_keys(typval_T *argvars, typval_T *rettv, FunPtr fptr) { dict_list(argvars, rettv, 0); } -/* - * "last_buffer_nr()" function. - */ +/// "last_buffer_nr()" function. static void f_last_buffer_nr(typval_T *argvars, typval_T *rettv, FunPtr fptr) { int n = 0; @@ -5786,9 +5463,7 @@ static void f_last_buffer_nr(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_number = n; } -/* - * "len()" function - */ +/// "len()" function static void f_len(typval_T *argvars, typval_T *rettv, FunPtr fptr) { switch (argvars[0].v_type) { @@ -5861,23 +5536,19 @@ static void libcall_common(typval_T *argvars, typval_T *rettv, int out_type) } } -/* - * "libcall()" function - */ +/// "libcall()" function static void f_libcall(typval_T *argvars, typval_T *rettv, FunPtr fptr) { libcall_common(argvars, rettv, VAR_STRING); } -/* - * "libcallnr()" function - */ +/// "libcallnr()" function static void f_libcallnr(typval_T *argvars, typval_T *rettv, FunPtr fptr) { libcall_common(argvars, rettv, VAR_NUMBER); } -// "line(string, [winid])" function +/// "line(string, [winid])" function static void f_line(typval_T *argvars, typval_T *rettv, FunPtr fptr) { linenr_T lnum = 0; @@ -5885,23 +5556,21 @@ static void f_line(typval_T *argvars, typval_T *rettv, FunPtr fptr) int fnum; 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); + int id = (int)tv_get_number(&argvars[1]); + tabpage_T *tp; + win_T *wp = win_id2wp_tp(id, &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) { @@ -5910,9 +5579,7 @@ static void f_line(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_number = lnum; } -/* - * "line2byte(lnum)" function - */ +/// "line2byte(lnum)" function static void f_line2byte(typval_T *argvars, typval_T *rettv, FunPtr fptr) { const linenr_T lnum = tv_get_lnum(argvars); @@ -5926,9 +5593,7 @@ static void f_line2byte(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/* - * "lispindent(lnum)" function - */ +/// "lispindent(lnum)" function static void f_lispindent(typval_T *argvars, typval_T *rettv, FunPtr fptr) { const pos_T pos = curwin->w_cursor; @@ -5942,7 +5607,7 @@ static void f_lispindent(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -// "list2str()" function +/// "list2str()" function static void f_list2str(typval_T *argvars, typval_T *rettv, FunPtr fptr) { garray_T ga; @@ -5971,9 +5636,7 @@ static void f_list2str(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_string = ga.ga_data; } -/* - * "localtime()" function - */ +/// "localtime()" function static void f_localtime(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->vval.v_number = (varnumber_T)time(NULL); @@ -5984,6 +5647,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; @@ -6020,7 +5684,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) { @@ -6031,10 +5695,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); } @@ -6053,25 +5722,19 @@ static void f_luaeval(typval_T *argvars, typval_T *rettv, FunPtr fptr) nlua_typval_eval(cstr_as_string((char *)str), &argvars[1], rettv); } -/* - * "map()" function - */ +/// "map()" function static void f_map(typval_T *argvars, typval_T *rettv, FunPtr fptr) { filter_map(argvars, rettv, TRUE); } -/* - * "maparg()" function - */ +/// "maparg()" function static void f_maparg(typval_T *argvars, typval_T *rettv, FunPtr fptr) { get_maparg(argvars, rettv, TRUE); } -/* - * "mapcheck()" function - */ +/// "mapcheck()" function static void f_mapcheck(typval_T *argvars, typval_T *rettv, FunPtr fptr) { get_maparg(argvars, rettv, FALSE); @@ -6293,166 +5956,25 @@ theend: p_cpo = save_cpo; } -/* - * "match()" function - */ +/// "match()" function static void f_match(typval_T *argvars, typval_T *rettv, FunPtr fptr) { find_some_match(argvars, rettv, kSomeMatch); } -/* - * "matchadd()" function - */ -static void f_matchadd(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - char grpbuf[NUMBUFLEN]; - char patbuf[NUMBUFLEN]; - // group - const char *const grp = tv_get_string_buf_chk(&argvars[0], grpbuf); - // pattern - const char *const pat = tv_get_string_buf_chk(&argvars[1], patbuf); - // default priority - int prio = 10; - int id = -1; - bool error = false; - const char *conceal_char = NULL; - win_T *win = curwin; - - rettv->vval.v_number = -1; - - if (grp == NULL || pat == NULL) { - return; - } - if (argvars[2].v_type != VAR_UNKNOWN) { - prio = tv_get_number_chk(&argvars[2], &error); - if (argvars[3].v_type != VAR_UNKNOWN) { - id = tv_get_number_chk(&argvars[3], &error); - if (argvars[4].v_type != VAR_UNKNOWN - && matchadd_dict_arg(&argvars[4], &conceal_char, &win) == FAIL) { - return; - } - } - } - if (error) { - return; - } - if (id >= 1 && id <= 3) { - semsg(_("E798: ID is reserved for \":match\": %" PRId64), (int64_t)id); - return; - } - - rettv->vval.v_number = match_add(win, grp, pat, prio, id, NULL, conceal_char); -} - -static void f_matchaddpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - rettv->vval.v_number = -1; - - char buf[NUMBUFLEN]; - const char *const group = tv_get_string_buf_chk(&argvars[0], buf); - if (group == NULL) { - return; - } - - if (argvars[1].v_type != VAR_LIST) { - semsg(_(e_listarg), "matchaddpos()"); - return; - } - - list_T *l; - l = argvars[1].vval.v_list; - if (l == NULL) { - return; - } - - bool error = false; - int prio = 10; - int id = -1; - const char *conceal_char = NULL; - win_T *win = curwin; - - if (argvars[2].v_type != VAR_UNKNOWN) { - prio = tv_get_number_chk(&argvars[2], &error); - if (argvars[3].v_type != VAR_UNKNOWN) { - id = tv_get_number_chk(&argvars[3], &error); - if (argvars[4].v_type != VAR_UNKNOWN - && matchadd_dict_arg(&argvars[4], &conceal_char, &win) == FAIL) { - return; - } - } - } - if (error == true) { - return; - } - - // id == 3 is ok because matchaddpos() is supposed to substitute :3match - if (id == 1 || id == 2) { - semsg(_("E798: ID is reserved for \"match\": %" PRId64), (int64_t)id); - return; - } - - rettv->vval.v_number = match_add(win, group, NULL, prio, id, l, conceal_char); -} - -/* - * "matcharg()" function - */ -static void f_matcharg(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - const int id = tv_get_number(&argvars[0]); - - tv_list_alloc_ret(rettv, (id >= 1 && id <= 3 - ? 2 - : 0)); - - if (id >= 1 && id <= 3) { - matchitem_T *const m = get_match(curwin, id); - - if (m != NULL) { - tv_list_append_string(rettv->vval.v_list, - (const char *)syn_id2name(m->hlg_id), -1); - tv_list_append_string(rettv->vval.v_list, (const char *)m->pattern, -1); - } else { - tv_list_append_string(rettv->vval.v_list, NULL, 0); - tv_list_append_string(rettv->vval.v_list, NULL, 0); - } - } -} - -/* - * "matchdelete()" function - */ -static void f_matchdelete(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - win_T *win = get_optional_window(argvars, 1); - if (win == NULL) { - rettv->vval.v_number = -1; - } else { - rettv->vval.v_number = match_delete(win, - (int)tv_get_number(&argvars[0]), true); - } -} - -/* - * "matchend()" function - */ +/// "matchend()" function static void f_matchend(typval_T *argvars, typval_T *rettv, FunPtr fptr) { find_some_match(argvars, rettv, kSomeMatchEnd); } -/* - * "matchlist()" function - */ +/// "matchlist()" function static void f_matchlist(typval_T *argvars, typval_T *rettv, FunPtr fptr) { find_some_match(argvars, rettv, kSomeMatchList); } -/* - * "matchstr()" function - */ +/// "matchstr()" function static void f_matchstr(typval_T *argvars, typval_T *rettv, FunPtr fptr) { find_some_match(argvars, rettv, kSomeMatchStr); @@ -6513,25 +6035,19 @@ static void max_min(const typval_T *const tv, typval_T *const rettv, const bool rettv->vval.v_number = n; } -/* - * "max()" function - */ +/// "max()" function static void f_max(typval_T *argvars, typval_T *rettv, FunPtr fptr) { max_min(argvars, rettv, TRUE); } -/* - * "min()" function - */ +/// "min()" function static void f_min(typval_T *argvars, typval_T *rettv, FunPtr fptr) { max_min(argvars, rettv, FALSE); } -/* - * "mkdir()" function - */ +/// "mkdir()" function static void f_mkdir(typval_T *argvars, typval_T *rettv, FunPtr fptr) { int prot = 0755; // -V536 @@ -6579,15 +6095,17 @@ static void f_mkdir(typval_T *argvars, typval_T *rettv, FunPtr fptr) /// "mode()" function static void f_mode(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - char *mode = get_mode(); + char buf[MODE_MAX_LENGTH]; + + get_mode(buf); // Clear out the minor mode when the argument is not a non-zero number or // non-empty string. if (!non_zero_arg(&argvars[0])) { - mode[1] = NUL; + buf[1] = NUL; } - rettv->vval.v_string = (char_u *)mode; + rettv->vval.v_string = vim_strsave((char_u *)buf); rettv->v_type = VAR_STRING; } @@ -6746,9 +6264,7 @@ static void f_msgpackparse(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/* - * "nextnonblank()" function - */ +/// "nextnonblank()" function static void f_nextnonblank(typval_T *argvars, typval_T *rettv, FunPtr fptr) { linenr_T lnum; @@ -6765,9 +6281,7 @@ static void f_nextnonblank(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_number = lnum; } -/* - * "nr2char()" function - */ +/// "nr2char()" function static void f_nr2char(typval_T *argvars, typval_T *rettv, FunPtr fptr) { if (argvars[1].v_type != VAR_UNKNOWN) { @@ -6798,31 +6312,36 @@ static void f_nr2char(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_string = xmemdupz(buf, (size_t)len); } -/* - * "or(expr, expr)" function - */ +/// "or(expr, expr)" function static void f_or(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->vval.v_number = tv_get_number_chk(&argvars[0], NULL) | tv_get_number_chk(&argvars[1], NULL); } -/* - * "pathshorten()" function - */ +/// "pathshorten()" function 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)); } -/* - * "pow()" function - */ +/// "pow()" function static void f_pow(typval_T *argvars, typval_T *rettv, FunPtr fptr) { float_T fx; @@ -6836,9 +6355,7 @@ static void f_pow(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/* - * "prevnonblank()" function - */ +/// "prevnonblank()" function static void f_prevnonblank(typval_T *argvars, typval_T *rettv, FunPtr fptr) { linenr_T lnum = tv_get_lnum(argvars); @@ -6852,9 +6369,7 @@ static void f_prevnonblank(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_number = lnum; } -/* - * "printf()" function - */ +/// "printf()" function static void f_printf(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->v_type = VAR_STRING; @@ -6877,7 +6392,7 @@ static void f_printf(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -// "prompt_setcallback({buffer}, {callback})" function +/// "prompt_setcallback({buffer}, {callback})" function static void f_prompt_setcallback(typval_T *argvars, typval_T *rettv, FunPtr fptr) { buf_T *buf; @@ -6901,7 +6416,7 @@ static void f_prompt_setcallback(typval_T *argvars, typval_T *rettv, FunPtr fptr buf->b_prompt_callback = prompt_callback; } -// "prompt_setinterrupt({buffer}, {callback})" function +/// "prompt_setinterrupt({buffer}, {callback})" function static void f_prompt_setinterrupt(typval_T *argvars, typval_T *rettv, FunPtr fptr) { buf_T *buf; @@ -6926,7 +6441,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 @@ -6945,7 +6460,7 @@ void f_prompt_getprompt(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_string = vim_strsave(buf_prompt_text(buf)); } -// "prompt_setprompt({buffer}, {text})" function +/// "prompt_setprompt({buffer}, {text})" function static void f_prompt_setprompt(typval_T *argvars, typval_T *rettv, FunPtr fptr) { buf_T *buf; @@ -6964,16 +6479,14 @@ static void f_prompt_setprompt(typval_T *argvars, typval_T *rettv, FunPtr fptr) buf->b_prompt_text = vim_strsave(text); } -// "pum_getpos()" function +/// "pum_getpos()" function static void f_pum_getpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) { tv_dict_alloc_ret(rettv); pum_set_event_info(rettv->vval.v_dict); } -/* - * "pumvisible()" function - */ +/// "pumvisible()" function static void f_pumvisible(typval_T *argvars, typval_T *rettv, FunPtr fptr) { if (pum_visible()) { @@ -6981,50 +6494,183 @@ 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("python3", argvars, rettv); +} + +static void init_srand(uint32_t *const x) + FUNC_ATTR_NONNULL_ALL +{ +#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 + // uncrustify:off + *x = time(NULL); +#ifndef MSWIN + } +#endif + // uncrustify:on +} + +static inline uint32_t splitmix32(uint32_t *const x) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_ALWAYS_INLINE { - script_host_eval("python", argvars, rettv); + uint32_t z = (*x += 0x9e3779b9); + z = (z ^ (z >> 16)) * 0x85ebca6b; + z = (z ^ (z >> 13)) * 0xc2b2ae35; + return z ^ (z >> 16); } -/* - * "py3eval()" function - */ -static void f_py3eval(typval_T *argvars, typval_T *rettv, FunPtr fptr) +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 { - script_host_eval("python3", argvars, rettv); +#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; } -// "pyxeval()" function -static void f_pyxeval(typval_T *argvars, typval_T *rettv, FunPtr fptr) +/// "rand()" function +static void f_rand(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - init_pyxversion(); - if (p_pyx == 2) { - f_pyeval(argvars, rettv, NULL); + 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 { - f_py3eval(argvars, rettv, NULL); + 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; +} + +/// "srand()" function +static void f_srand(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + uint32_t x = 0; + + tv_list_alloc_ret(rettv, 4); + if (argvars[0].v_type == VAR_UNKNOWN) { + init_srand(&x); + } else { + 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); } -// "rubyeval()" function +/// "rubyeval()" function static void f_rubyeval(typval_T *argvars, typval_T *rettv, FunPtr fptr) { script_host_eval("ruby", argvars, rettv); } -/* - * "range()" function - */ +/// "range()" function static void f_range(typval_T *argvars, typval_T *rettv, FunPtr fptr) { varnumber_T start; @@ -7059,15 +6705,21 @@ static void f_range(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -// Evaluate "expr" for readdir(). -static varnumber_T readdir_checkitem(typval_T *expr, const char *name) +/// Evaluate "expr" (= "context") for readdir(). +static varnumber_T readdir_checkitem(void *context, const char *name) + FUNC_ATTR_NONNULL_ALL { + typval_T *expr = (typval_T *)context; typval_T save_val; typval_T rettv; typval_T argv[2]; varnumber_T retval = 0; bool error = false; + if (expr->v_type == VAR_UNKNOWN) { + return 1; + } + prepare_vimvar(VV_VAL, &save_val); set_vim_var_string(VV_VAL, name, -1); argv[0].v_type = VAR_STRING; @@ -7090,65 +6742,25 @@ theend: return retval; } -// "readdir()" function +/// "readdir()" function static void f_readdir(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - typval_T *expr; - const char *path; - garray_T ga; - Directory dir; - tv_list_alloc_ret(rettv, kListLenUnknown); - path = tv_get_string(&argvars[0]); - expr = &argvars[1]; - ga_init(&ga, (int)sizeof(char *), 20); - - if (!os_scandir(&dir, path)) { - smsg(_(e_notopen), path); - } else { - for (;;) { - bool ignore; - - path = os_scandir_next(&dir); - if (path == NULL) { - break; - } - - ignore = (path[0] == '.' - && (path[1] == NUL || (path[1] == '.' && path[2] == NUL))); - if (!ignore && expr->v_type != VAR_UNKNOWN) { - varnumber_T r = readdir_checkitem(expr, path); - - if (r < 0) { - break; - } - if (r == 0) { - ignore = true; - } - } - - if (!ignore) { - ga_grow(&ga, 1); - ((char **)ga.ga_data)[ga.ga_len++] = xstrdup(path); - } - } - - os_closedir(&dir); - } - if (rettv->vval.v_list != NULL && ga.ga_len > 0) { - sort_strings((char_u **)ga.ga_data, ga.ga_len); + const char *path = tv_get_string(&argvars[0]); + typval_T *expr = &argvars[1]; + garray_T ga; + int ret = readdir_core(&ga, path, (void *)expr, readdir_checkitem); + if (ret == OK && ga.ga_len > 0) { for (int i = 0; i < ga.ga_len; i++) { - path = ((const char **)ga.ga_data)[i]; - tv_list_append_string(rettv->vval.v_list, path, -1); + const char *p = ((const char **)ga.ga_data)[i]; + tv_list_append_string(rettv->vval.v_list, p, -1); } } ga_clear_strings(&ga); } -/* - * "readfile()" function - */ +/// "readfile()" function static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr fptr) { bool binary = false; @@ -7335,18 +6947,12 @@ static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr fptr) /// "getreginfo()" function static void f_getreginfo(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - const char *strregname; - if (argvars[0].v_type != VAR_UNKNOWN) { - strregname = tv_get_string_chk(&argvars[0]); - if (strregname == NULL) { - return; - } - } else { - strregname = (const char *)get_vim_var_str(VV_REG); + int regname = getreg_get_regname(argvars); + if (regname == 0) { + return; } - int regname = (strregname == NULL ? '"' : *strregname); - if (regname == 0 || regname == '@') { + if (regname == '@') { regname = '"'; } @@ -7388,18 +6994,23 @@ static void f_getreginfo(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -// "reg_executing()" function +/// "reg_executing()" function static void f_reg_executing(typval_T *argvars, typval_T *rettv, FunPtr fptr) { return_register(reg_executing, rettv); } -// "reg_recording()" function +/// "reg_recording()" function static void f_reg_recording(typval_T *argvars, typval_T *rettv, FunPtr fptr) { return_register(reg_recording, rettv); } +static void f_reg_recorded(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + return_register(reg_recorded, rettv); +} + /// list2proftime - convert a List to proftime_T /// /// @param arg The input list, must be of type VAR_LIST and have @@ -7491,9 +7102,7 @@ static void f_reltimestr(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/* - * "remove()" function - */ +/// "remove()" function static void f_remove(typval_T *argvars, typval_T *rettv, FunPtr fptr) { list_T *l; @@ -7627,9 +7236,7 @@ static void f_remove(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/* - * "rename({from}, {to})" function - */ +/// "rename({from}, {to})" function static void f_rename(typval_T *argvars, typval_T *rettv, FunPtr fptr) { if (check_secure()) { @@ -7641,9 +7248,7 @@ static void f_rename(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/* - * "repeat()" function - */ +/// "repeat()" function static void f_repeat(typval_T *argvars, typval_T *rettv, FunPtr fptr) { varnumber_T n = tv_get_number(&argvars[1]); @@ -7680,9 +7285,7 @@ static void f_repeat(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/* - * "resolve()" function - */ +/// "resolve()" function static void f_resolve(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->v_type = VAR_STRING; @@ -7851,9 +7454,7 @@ static void f_resolve(typval_T *argvars, typval_T *rettv, FunPtr fptr) simplify_filename(rettv->vval.v_string); } -/* - * "reverse({list})" function - */ +/// "reverse({list})" function static void f_reverse(typval_T *argvars, typval_T *rettv, FunPtr fptr) { if (argvars[0].v_type == VAR_BLOB) { @@ -7878,6 +7479,102 @@ static void f_reverse(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } +/// "reduce(list, { accumulator, 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 @@ -7887,11 +7584,10 @@ static void f_reverse(typval_T *argvars, typval_T *rettv, FunPtr fptr) #define SP_END 0x40 ///< leave cursor at end of match #define SP_COLUMN 0x80 ///< start at cursor column -/* - * Get flags for a search function. - * Possibly sets "p_ws". - * Returns BACKWARD, FORWARD or zero (for an error). - */ +/// Get flags for a search function. +/// Possibly sets "p_ws". +/// +/// @return BACKWARD, FORWARD or zero (for an error). static int get_search_arg(typval_T *varp, int *flagsp) { int dir = FORWARD; @@ -7949,7 +7645,7 @@ static int get_search_arg(typval_T *varp, int *flagsp) return dir; } -// Shared by search() and searchpos() functions. +/// Shared by search() and searchpos() functions. static int search_cmn(typval_T *argvars, pos_T *match_pos, int *flagsp) { int flags; @@ -7964,6 +7660,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. @@ -7981,7 +7678,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) { @@ -7992,6 +7689,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]); } } @@ -8011,11 +7709,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; @@ -8048,7 +7781,7 @@ theend: return retval; } -// "rpcnotify()" function +/// "rpcnotify()" function static void f_rpcnotify(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->v_type = VAR_NUMBER; @@ -8083,7 +7816,7 @@ static void f_rpcnotify(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_number = 1; } -// "rpcrequest()" function +/// "rpcrequest()" function static void f_rpcrequest(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->v_type = VAR_NUMBER; @@ -8181,7 +7914,7 @@ end: api_clear_error(&err); } -// "rpcstart()" function (DEPRECATED) +/// "rpcstart()" function (DEPRECATED) static void f_rpcstart(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->v_type = VAR_NUMBER; @@ -8248,7 +7981,7 @@ static void f_rpcstart(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -// "rpcstop()" function +/// "rpcstop()" function static void f_rpcstop(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->v_type = VAR_NUMBER; @@ -8278,7 +8011,7 @@ static void f_rpcstop(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -// "screenattr()" function +/// "screenattr()" function static void f_screenattr(typval_T *argvars, typval_T *rettv, FunPtr fptr) { int c; @@ -8296,7 +8029,7 @@ static void f_screenattr(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_number = c; } -// "screenchar()" function +/// "screenchar()" function static void f_screenchar(typval_T *argvars, typval_T *rettv, FunPtr fptr) { int c; @@ -8314,7 +8047,7 @@ static void f_screenchar(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_number = c; } -// "screenchars()" function +/// "screenchars()" function static void f_screenchars(typval_T *argvars, typval_T *rettv, FunPtr fptr) { int row = tv_get_number_chk(&argvars[0], NULL) - 1; @@ -8339,9 +8072,9 @@ static void f_screenchars(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -// "screencol()" function -// -// First column is 1 to be consistent with virtcol(). +/// "screencol()" function +/// +/// First column is 1 to be consistent with virtcol(). static void f_screencol(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->vval.v_number = ui_current_col() + 1; @@ -8373,13 +8106,13 @@ static void f_screenpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) tv_dict_add_nr(dict, S_LEN("endcol"), ecol); } -// "screenrow()" function +/// "screenrow()" function static void f_screenrow(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->vval.v_number = ui_current_row() + 1; } -// "screenstring()" function +/// "screenstring()" function static void f_screenstring(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->vval.v_string = NULL; @@ -8395,7 +8128,7 @@ static void f_screenstring(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_string = vim_strsave(grid->chars[grid->line_offset[row] + col]); } -// "search()" function +/// "search()" function static void f_search(typval_T *argvars, typval_T *rettv, FunPtr fptr) { int flags = 0; @@ -8403,9 +8136,7 @@ static void f_search(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_number = search_cmn(argvars, NULL, &flags); } -/* - * "searchdecl()" function - */ +/// "searchdecl()" function static void f_searchdecl(typval_T *argvars, typval_T *rettv, FunPtr fptr) { int locally = 1; @@ -8427,9 +8158,7 @@ static void f_searchdecl(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/* - * Used by searchpair() and searchpairpos() - */ +/// Used by searchpair() and searchpairpos() static int searchpair_cmn(typval_T *argvars, pos_T *match_pos) { bool save_p_ws = p_ws; @@ -8475,13 +8204,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) { @@ -8507,17 +8232,13 @@ theend: return retval; } -/* - * "searchpair()" function - */ +/// "searchpair()" function static void f_searchpair(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->vval.v_number = searchpair_cmn(argvars, NULL); } -/* - * "searchpairpos()" function - */ +/// "searchpairpos()" function static void f_searchpairpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) { pos_T match_pos; @@ -8592,10 +8313,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; @@ -8706,9 +8424,7 @@ long do_searchpair(const char *spat, const char *mpat, const char *epat, int dir return retval; } -/* - * "searchpos()" function - */ +/// "searchpos()" function static void f_searchpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) { pos_T match_pos; @@ -8822,9 +8538,7 @@ static void f_setbufline(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/* - * "setbufvar()" function - */ +/// "setbufvar()" function static void f_setbufvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) { if (check_secure() @@ -8868,6 +8582,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 offset. +/// 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; @@ -8898,9 +8655,7 @@ static void f_setcharsearch(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/* - * "setcmdpos()" function - */ +/// "setcmdpos()" function static void f_setcmdpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) { const int pos = (int)tv_get_number(&argvars[0]) - 1; @@ -8910,6 +8665,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) { @@ -8956,9 +8717,7 @@ static void f_setfperm(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_number = os_setperm(fname, mode) == OK; } -/* - * "setline()" function - */ +/// "setline()" function static void f_setline(typval_T *argvars, typval_T *rettv, FunPtr fptr) { linenr_T lnum = tv_get_lnum(&argvars[0]); @@ -8985,7 +8744,7 @@ static void set_qf_ll_list(win_T *wp, typval_T *args, typval_T *rettv) { static char *e_invact = N_("E927: Invalid action: '%s'"); const char *title = NULL; - int action = ' '; + char action = ' '; static int recursive = 0; rettv->vval.v_number = -1; dict_T *what = NULL; @@ -9046,9 +8805,7 @@ skip_args: recursive--; } -/* - * "setloclist()" function - */ +/// "setloclist()" function static void f_setloclist(typval_T *argvars, typval_T *rettv, FunPtr fptr) { win_T *win; @@ -9061,151 +8818,13 @@ static void f_setloclist(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/* - * "setmatches()" function - */ -static void f_setmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - dict_T *d; - list_T *s = NULL; - win_T *win = get_optional_window(argvars, 1); - - rettv->vval.v_number = -1; - if (argvars[0].v_type != VAR_LIST) { - emsg(_(e_listreq)); - return; - } - if (win == NULL) { - return; - } - - list_T *const l = argvars[0].vval.v_list; - // To some extent make sure that we are dealing with a list from - // "getmatches()". - int li_idx = 0; - TV_LIST_ITER_CONST(l, li, { - if (TV_LIST_ITEM_TV(li)->v_type != VAR_DICT - || (d = TV_LIST_ITEM_TV(li)->vval.v_dict) == NULL) { - semsg(_("E474: List item %d is either not a dictionary " - "or an empty one"), li_idx); - return; - } - if (!(tv_dict_find(d, S_LEN("group")) != NULL - && (tv_dict_find(d, S_LEN("pattern")) != NULL - || tv_dict_find(d, S_LEN("pos1")) != NULL) - && tv_dict_find(d, S_LEN("priority")) != NULL - && tv_dict_find(d, S_LEN("id")) != NULL)) { - semsg(_("E474: List item %d is missing one of the required keys"), - li_idx); - return; - } - li_idx++; - }); - - clear_matches(win); - bool match_add_failed = false; - TV_LIST_ITER_CONST(l, li, { - int i = 0; - - d = TV_LIST_ITEM_TV(li)->vval.v_dict; - dictitem_T *const di = tv_dict_find(d, S_LEN("pattern")); - if (di == NULL) { - if (s == NULL) { - s = tv_list_alloc(9); - } - - // match from matchaddpos() - for (i = 1; i < 9; i++) { - char buf[30]; // use 30 to avoid compiler warning - snprintf(buf, sizeof(buf), "pos%d", i); - dictitem_T *const pos_di = tv_dict_find(d, buf, -1); - if (pos_di != NULL) { - if (pos_di->di_tv.v_type != VAR_LIST) { - return; - } - - tv_list_append_tv(s, &pos_di->di_tv); - tv_list_ref(s); - } else { - break; - } - } - } - - // Note: there are three number buffers involved: - // - group_buf below. - // - numbuf in tv_dict_get_string(). - // - mybuf in tv_get_string(). - // - // If you change this code make sure that buffers will not get - // accidentally reused. - char group_buf[NUMBUFLEN]; - const char *const group = tv_dict_get_string_buf(d, "group", group_buf); - const int priority = (int)tv_dict_get_number(d, "priority"); - const int id = (int)tv_dict_get_number(d, "id"); - dictitem_T *const conceal_di = tv_dict_find(d, S_LEN("conceal")); - const char *const conceal = (conceal_di != NULL - ? tv_get_string(&conceal_di->di_tv) - : NULL); - if (i == 0) { - if (match_add(win, group, - tv_dict_get_string(d, "pattern", false), - priority, id, NULL, conceal) != id) { - match_add_failed = true; - } - } else { - if (match_add(win, group, NULL, priority, id, s, conceal) != id) { - match_add_failed = true; - } - tv_list_unref(s); - s = NULL; - } - }); - if (!match_add_failed) { - rettv->vval.v_number = 0; - } -} - -/* - * "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); } -/* - * "setqflist()" function - */ +/// "setqflist()" function static void f_setqflist(typval_T *argvars, typval_T *rettv, FunPtr fptr) { set_qf_ll_list(NULL, argvars, rettv); @@ -9241,12 +8860,9 @@ static int get_yank_type(char_u **const pp, MotionType *const yank_type, long *c return OK; } -/* - * "setreg()" function - */ +/// "setreg()" function static void f_setreg(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - int regname; bool append = false; MotionType yank_type; long block_len; @@ -9260,13 +8876,13 @@ static void f_setreg(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (strregname == NULL) { return; // Type error; errmsg already given. } - regname = (uint8_t)(*strregname); + char regname = (uint8_t)(*strregname); if (regname == 0 || regname == '@') { regname = '"'; } const typval_T *regcontents = NULL; - int pointreg = 0; + char pointreg = 0; if (argvars[1].v_type == VAR_DICT) { dict_T *const d = argvars[1].vval.v_dict; @@ -9384,14 +9000,11 @@ free_lstval: if (set_unnamed) { // Discard the result. We already handle the error case. - if (op_reg_set_previous(regname)) { - } + op_reg_set_previous(regname); } } -/* - * "settabvar()" function - */ +/// "settabvar()" function static void f_settabvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->vval.v_number = 0; @@ -9422,21 +9035,19 @@ static void f_settabvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/* - * "settabwinvar()" function - */ +/// "settabwinvar()" function static void f_settabwinvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) { setwinvar(argvars, rettv, 1); } -// "settagstack()" function +/// "settagstack()" function static void f_settagstack(typval_T *argvars, typval_T *rettv, FunPtr fptr) { static char *e_invact2 = N_("E962: Invalid action: '%s'"); win_T *wp; dict_T *d; - int action = 'r'; + char action = 'r'; rettv->vval.v_number = -1; @@ -9483,9 +9094,7 @@ static void f_settagstack(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/* - * "setwinvar()" function - */ +/// "setwinvar()" function static void f_setwinvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) { setwinvar(argvars, rettv, 0); @@ -9502,9 +9111,7 @@ static void f_sha256(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->v_type = VAR_STRING; } -/* - * "shellescape({string})" function - */ +/// "shellescape({string})" function static void f_shellescape(typval_T *argvars, typval_T *rettv, FunPtr fptr) { const bool do_special = non_zero_arg(&argvars[1]); @@ -9515,9 +9122,7 @@ static void f_shellescape(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->v_type = VAR_STRING; } -/* - * shiftwidth() function - */ +/// shiftwidth() function static void f_shiftwidth(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->vval.v_number = 0; @@ -9535,275 +9140,7 @@ static void f_shiftwidth(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_number = get_sw_value(curbuf); } -/// "sign_define()" function -static void f_sign_define(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - const char *name; - - if (argvars[0].v_type == VAR_LIST && argvars[1].v_type == VAR_UNKNOWN) { - // Define multiple signs - tv_list_alloc_ret(rettv, kListLenMayKnow); - - sign_define_multiple(argvars[0].vval.v_list, rettv->vval.v_list); - return; - } - - // Define a single sign - rettv->vval.v_number = -1; - - name = tv_get_string_chk(&argvars[0]); - if (name == NULL) { - return; - } - - if (argvars[1].v_type != VAR_UNKNOWN && argvars[1].v_type != VAR_DICT) { - emsg(_(e_dictreq)); - return; - } - - rettv->vval.v_number = sign_define_from_dict(name, - argvars[1].v_type == - VAR_DICT ? argvars[1].vval.v_dict : NULL); -} - -/// "sign_getdefined()" function -static void f_sign_getdefined(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - const char *name = NULL; - - tv_list_alloc_ret(rettv, 0); - - if (argvars[0].v_type != VAR_UNKNOWN) { - name = tv_get_string(&argvars[0]); - } - - sign_getlist((const char_u *)name, rettv->vval.v_list); -} - -/// "sign_getplaced()" function -static void f_sign_getplaced(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - buf_T *buf = NULL; - dict_T *dict; - dictitem_T *di; - linenr_T lnum = 0; - int sign_id = 0; - const char *group = NULL; - bool notanum = false; - - tv_list_alloc_ret(rettv, 0); - - if (argvars[0].v_type != VAR_UNKNOWN) { - // get signs placed in the specified buffer - buf = get_buf_arg(&argvars[0]); - if (buf == NULL) { - return; - } - - if (argvars[1].v_type != VAR_UNKNOWN) { - if (argvars[1].v_type != VAR_DICT - || ((dict = argvars[1].vval.v_dict) == NULL)) { - emsg(_(e_dictreq)); - return; - } - if ((di = tv_dict_find(dict, "lnum", -1)) != NULL) { - // get signs placed at this line - lnum = (linenr_T)tv_get_number_chk(&di->di_tv, ¬anum); - if (notanum) { - return; - } - (void)lnum; - lnum = tv_get_lnum(&di->di_tv); - } - if ((di = tv_dict_find(dict, "id", -1)) != NULL) { - // get sign placed with this identifier - sign_id = (int)tv_get_number_chk(&di->di_tv, ¬anum); - if (notanum) { - return; - } - } - if ((di = tv_dict_find(dict, "group", -1)) != NULL) { - group = tv_get_string_chk(&di->di_tv); - if (group == NULL) { - return; - } - if (*group == '\0') { // empty string means global group - group = NULL; - } - } - } - } - - sign_get_placed(buf, lnum, sign_id, (const char_u *)group, - rettv->vval.v_list); -} - -/// "sign_jump()" function -static void f_sign_jump(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - int sign_id; - char *sign_group = NULL; - buf_T *buf; - bool notanum = false; - - rettv->vval.v_number = -1; - - // Sign identifier - sign_id = (int)tv_get_number_chk(&argvars[0], ¬anum); - if (notanum) { - return; - } - if (sign_id <= 0) { - emsg(_(e_invarg)); - return; - } - - // Sign group - const char *sign_group_chk = tv_get_string_chk(&argvars[1]); - if (sign_group_chk == NULL) { - return; - } - if (sign_group_chk[0] == '\0') { - sign_group = NULL; // global sign group - } else { - sign_group = xstrdup(sign_group_chk); - } - - // Buffer to place the sign - buf = get_buf_arg(&argvars[2]); - if (buf == NULL) { - goto cleanup; - } - - rettv->vval.v_number = sign_jump(sign_id, (char_u *)sign_group, buf); - -cleanup: - xfree(sign_group); -} - -/// "sign_place()" function -static void f_sign_place(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - dict_T *dict = NULL; - - rettv->vval.v_number = -1; - - if (argvars[4].v_type != VAR_UNKNOWN - && (argvars[4].v_type != VAR_DICT - || ((dict = argvars[4].vval.v_dict) == NULL))) { - emsg(_(e_dictreq)); - return; - } - - rettv->vval.v_number = sign_place_from_dict(&argvars[0], &argvars[1], &argvars[2], &argvars[3], - dict); -} - -/// "sign_placelist()" function. Place multiple signs. -static void f_sign_placelist(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - int sign_id; - - tv_list_alloc_ret(rettv, kListLenMayKnow); - - if (argvars[0].v_type != VAR_LIST) { - emsg(_(e_listreq)); - return; - } - - // Process the List of sign attributes - TV_LIST_ITER_CONST(argvars[0].vval.v_list, li, { - sign_id = -1; - if (TV_LIST_ITEM_TV(li)->v_type == VAR_DICT) { - sign_id = sign_place_from_dict(NULL, NULL, NULL, NULL, TV_LIST_ITEM_TV(li)->vval.v_dict); - } else { - emsg(_(e_dictreq)); - } - tv_list_append_number(rettv->vval.v_list, sign_id); - }); -} - -/// "sign_undefine()" function -static void f_sign_undefine(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - const char *name; - - if (argvars[0].v_type == VAR_LIST && argvars[1].v_type == VAR_UNKNOWN) { - // Undefine multiple signs - tv_list_alloc_ret(rettv, kListLenMayKnow); - - sign_undefine_multiple(argvars[0].vval.v_list, rettv->vval.v_list); - return; - } - - rettv->vval.v_number = -1; - - if (argvars[0].v_type == VAR_UNKNOWN) { - // Free all the signs - free_signs(); - rettv->vval.v_number = 0; - } else { - // Free only the specified sign - name = tv_get_string_chk(&argvars[0]); - if (name == NULL) { - return; - } - - if (sign_undefine_by_name((const char_u *)name) == OK) { - rettv->vval.v_number = 0; - } - } -} - -/// "sign_unplace()" function -static void f_sign_unplace(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - dict_T *dict = NULL; - - rettv->vval.v_number = -1; - - if (argvars[0].v_type != VAR_STRING) { - emsg(_(e_invarg)); - return; - } - - if (argvars[1].v_type != VAR_UNKNOWN) { - if (argvars[1].v_type != VAR_DICT) { - emsg(_(e_dictreq)); - return; - } - dict = argvars[1].vval.v_dict; - } - - rettv->vval.v_number = sign_unplace_from_dict(&argvars[0], dict); -} - -/// "sign_unplacelist()" function -static void f_sign_unplacelist(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - int retval; - - tv_list_alloc_ret(rettv, kListLenMayKnow); - - if (argvars[0].v_type != VAR_LIST) { - emsg(_(e_listreq)); - return; - } - - TV_LIST_ITER_CONST(argvars[0].vval.v_list, li, { - retval = -1; - if (TV_LIST_ITEM_TV(li)->v_type == VAR_DICT) { - retval = sign_unplace_from_dict(NULL, TV_LIST_ITEM_TV(li)->vval.v_dict); - } else { - emsg(_(e_dictreq)); - } - tv_list_append_number(rettv->vval.v_list, retval); - }); -} - -/* - * "simplify()" function - */ +/// "simplify()" function static void f_simplify(typval_T *argvars, typval_T *rettv, FunPtr fptr) { const char *const p = tv_get_string(&argvars[0]); @@ -9880,9 +9217,7 @@ static sortinfo_T *sortinfo = NULL; #define ITEM_COMPARE_FAIL 999 -/* - * Compare functions for f_sort() and f_uniq() below. - */ +/// Compare functions for f_sort() and f_uniq() below. static int item_compare(const void *s1, const void *s2, bool keep_zero) { ListSortItem *const si1 = (ListSortItem *)s1; @@ -10019,6 +9354,11 @@ static int item_compare2(const void *s1, const void *s2, bool keep_zero) res = ITEM_COMPARE_FAIL; } else { res = tv_get_number_chk(&rettv, &sortinfo->item_compare_func_err); + if (res > 0) { + res = 1; + } else if (res < 0) { + res = -1; + } } if (sortinfo->item_compare_func_err) { res = ITEM_COMPARE_FAIL; // return value has wrong type @@ -10046,9 +9386,7 @@ static int item_compare2_not_keeping_zero(const void *s1, const void *s2) return item_compare2(s1, s2, false); } -/* - * "sort({list})" function - */ +/// "sort({list})" function static void do_sort_uniq(typval_T *argvars, typval_T *rettv, bool sort) { ListSortItem *ptrs; @@ -10215,6 +9553,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; @@ -10237,7 +9579,7 @@ static void f_uniq(typval_T *argvars, typval_T *rettv, FunPtr fptr) do_sort_uniq(argvars, rettv, false); } -// "reltimefloat()" function +/// "reltimefloat()" function static void f_reltimefloat(typval_T *argvars, typval_T *rettv, FunPtr fptr) FUNC_ATTR_NONNULL_ALL { @@ -10250,9 +9592,7 @@ static void f_reltimefloat(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/* - * "soundfold({word})" function - */ +/// "soundfold({word})" function static void f_soundfold(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->v_type = VAR_STRING; @@ -10260,9 +9600,7 @@ static void f_soundfold(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_string = (char_u *)eval_soundfold(s); } -/* - * "spellbadword()" function - */ +/// "spellbadword()" function static void f_spellbadword(typval_T *argvars, typval_T *rettv, FunPtr fptr) { const char *word = ""; @@ -10321,9 +9659,7 @@ static void f_spellbadword(typval_T *argvars, typval_T *rettv, FunPtr fptr) NULL), -1); } -/* - * "spellsuggest()" function - */ +/// "spellsuggest()" function static void f_spellsuggest(typval_T *argvars, typval_T *rettv, FunPtr fptr) { bool typeerr = false; @@ -10478,9 +9814,7 @@ static void f_stdpath(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/* - * "str2float()" function - */ +/// "str2float()" function static void f_str2float(typval_T *argvars, typval_T *rettv, FunPtr fptr) { char_u *p = skipwhite((const char_u *)tv_get_string(&argvars[0])); @@ -10496,7 +9830,7 @@ static void f_str2float(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->v_type = VAR_FLOAT; } -// "str2list()" function +/// "str2list()" function static void f_str2list(typval_T *argvars, typval_T *rettv, FunPtr fptr) { tv_list_alloc_ret(rettv, kListLenUnknown); @@ -10507,7 +9841,7 @@ static void f_str2list(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -// "str2nr()" function +/// "str2nr()" function static void f_str2nr(typval_T *argvars, typval_T *rettv, FunPtr fptr) { int base = 10; @@ -10550,9 +9884,7 @@ static void f_str2nr(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/* - * "strftime({format}[, {time}])" function - */ +/// "strftime({format}[, {time}])" function static void f_strftime(typval_T *argvars, typval_T *rettv, FunPtr fptr) { time_t seconds; @@ -10604,7 +9936,7 @@ static void f_strftime(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -// "strgetchar()" function +/// "strgetchar()" function static void f_strgetchar(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->vval.v_number = -1; @@ -10632,9 +9964,7 @@ static void f_strgetchar(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/* - * "stridx()" function - */ +/// "stridx()" function static void f_stridx(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->vval.v_number = -1; @@ -10666,26 +9996,20 @@ 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) +/// "string()" function +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); } -/* - * "strlen()" function - */ +/// "strlen()" function static void f_strlen(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->vval.v_number = (varnumber_T)strlen(tv_get_string(&argvars[0])); } -/* - * "strchars()" function - */ +/// "strchars()" function static void f_strchars(typval_T *argvars, typval_T *rettv, FunPtr fptr) { const char *s = tv_get_string(&argvars[0]); @@ -10708,9 +10032,7 @@ static void f_strchars(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/* - * "strdisplaywidth()" function - */ +/// "strdisplaywidth()" function static void f_strdisplaywidth(typval_T *argvars, typval_T *rettv, FunPtr fptr) { const char *const s = tv_get_string(&argvars[0]); @@ -10723,9 +10045,7 @@ static void f_strdisplaywidth(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_number = (varnumber_T)(linetabsize_col(col, (char_u *)s) - col); } -/* - * "strwidth()" function - */ +/// "strwidth()" function static void f_strwidth(typval_T *argvars, typval_T *rettv, FunPtr fptr) { const char *const s = tv_get_string(&argvars[0]); @@ -10733,7 +10053,7 @@ static void f_strwidth(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_number = (varnumber_T)mb_string2cells((const char_u *)s); } -// "strcharpart()" function +/// "strcharpart()" function static void f_strcharpart(typval_T *argvars, typval_T *rettv, FunPtr fptr) { const char *const p = tv_get_string(&argvars[0]); @@ -10787,9 +10107,7 @@ static void f_strcharpart(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_string = (char_u *)xstrndup(p + nbyte, (size_t)len); } -/* - * "strpart()" function - */ +/// "strpart()" function static void f_strpart(typval_T *argvars, typval_T *rettv, FunPtr fptr) { bool error = false; @@ -10835,7 +10153,7 @@ static void f_strpart(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_string = (char_u *)xmemdupz(p + n, (size_t)len); } -// "strptime({format}, {timestring})" function +/// "strptime({format}, {timestring})" function static void f_strptime(typval_T *argvars, typval_T *rettv, FunPtr fptr) { char fmt_buf[NUMBUFLEN]; @@ -10867,9 +10185,7 @@ static void f_strptime(typval_T *argvars, typval_T *rettv, FunPtr fptr) xfree(enc); } -/* - * "strridx()" function - */ +/// "strridx()" function static void f_strridx(typval_T *argvars, typval_T *rettv, FunPtr fptr) { char buf[NUMBUFLEN]; @@ -10912,18 +10228,14 @@ static void f_strridx(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/* - * "strtrans()" function - */ +/// "strtrans()" function static void f_strtrans(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->v_type = VAR_STRING; rettv->vval.v_string = (char_u *)transstr(tv_get_string(&argvars[0]), true); } -/* - * "submatch()" function - */ +/// "submatch()" function static void f_submatch(typval_T *argvars, typval_T *rettv, FunPtr fptr) { bool error = false; @@ -10954,9 +10266,7 @@ static void f_submatch(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/* - * "substitute()" function - */ +/// "substitute()" function static void f_substitute(typval_T *argvars, typval_T *rettv, FunPtr fptr) { char patbuf[NUMBUFLEN]; @@ -11025,9 +10335,7 @@ static void f_synID(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_number = id; } -/* - * "synIDattr(id, what [, mode])" function - */ +/// "synIDattr(id, what [, mode])" function static void f_synIDattr(typval_T *argvars, typval_T *rettv, FunPtr fptr) { const int id = (int)tv_get_number(&argvars[0]); @@ -11082,22 +10390,28 @@ static void f_synIDattr(typval_T *argvars, typval_T *rettv, FunPtr fptr) p = highlight_has_attr(id, HL_STANDOUT, modec); } break; - case 'u': - if (STRLEN(what) <= 5 || TOLOWER_ASC(what[5]) != 'c') { // underline + case 'u': { + const size_t len = STRLEN(what); + if (len <= 5 || (TOLOWER_ASC(what[5]) == 'l' && len <= 9)) { // underline p = highlight_has_attr(id, HL_UNDERLINE, modec); - } else { // undercurl + } else if (TOLOWER_ASC(what[5]) == 'c') { // undercurl p = highlight_has_attr(id, HL_UNDERCURL, modec); + } else if (len > 9 && TOLOWER_ASC(what[9]) == 'l') { // underlineline + p = highlight_has_attr(id, HL_UNDERLINELINE, modec); + } else if (len > 6 && TOLOWER_ASC(what[6]) == 'o') { // underdot + p = highlight_has_attr(id, HL_UNDERDOT, modec); + } else { // underdash + p = highlight_has_attr(id, HL_UNDERDASH, modec); } break; } + } rettv->v_type = VAR_STRING; rettv->vval.v_string = (char_u *)(p == NULL ? p : xstrdup(p)); } -/* - * "synIDtrans(id)" function - */ +/// "synIDtrans(id)" function static void f_synIDtrans(typval_T *argvars, typval_T *rettv, FunPtr fptr) { int id = tv_get_number(&argvars[0]); @@ -11111,9 +10425,7 @@ static void f_synIDtrans(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_number = id; } -/* - * "synconcealed(lnum, col)" function - */ +/// "synconcealed(lnum, col)" function static void f_synconcealed(typval_T *argvars, typval_T *rettv, FunPtr fptr) { int syntax_flags = 0; @@ -11155,9 +10467,7 @@ static void f_synconcealed(typval_T *argvars, typval_T *rettv, FunPtr fptr) tv_list_append_number(rettv->vval.v_list, matchid); } -/* - * "synstack(lnum, col)" function - */ +/// "synstack(lnum, col)" function static void f_synstack(typval_T *argvars, typval_T *rettv, FunPtr fptr) { tv_list_set_ret(rettv, NULL); @@ -11193,9 +10503,7 @@ static void f_systemlist(typval_T *argvars, typval_T *rettv, FunPtr fptr) } -/* - * "tabpagebuflist()" function - */ +/// "tabpagebuflist()" function static void f_tabpagebuflist(typval_T *argvars, typval_T *rettv, FunPtr fptr) { win_T *wp = NULL; @@ -11217,9 +10525,7 @@ static void f_tabpagebuflist(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/* - * "tabpagenr()" function - */ +/// "tabpagenr()" function static void f_tabpagenr(typval_T *argvars, typval_T *rettv, FunPtr fptr) { int nr = 1; @@ -11231,9 +10537,7 @@ static void f_tabpagenr(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (strcmp(arg, "$") == 0) { nr = tabpage_index(NULL) - 1; } else if (strcmp(arg, "#") == 0) { - nr = valid_tabpage(lastused_tabpage) - ? tabpage_index(lastused_tabpage) - : nr; + nr = valid_tabpage(lastused_tabpage) ? tabpage_index(lastused_tabpage) : 0; } else { semsg(_(e_invexpr2), arg); } @@ -11245,9 +10549,7 @@ static void f_tabpagenr(typval_T *argvars, typval_T *rettv, FunPtr fptr) } -/* - * Common code for tabpagewinnr() and winnr(). - */ +/// Common code for tabpagewinnr() and winnr(). static int get_winnr(tabpage_T *tp, typval_T *argvar) { win_T *twin; @@ -11312,9 +10614,7 @@ static int get_winnr(tabpage_T *tp, typval_T *argvar) return nr; } -/* - * "tabpagewinnr()" function - */ +/// "tabpagewinnr()" function static void f_tabpagewinnr(typval_T *argvars, typval_T *rettv, FunPtr fptr) { int nr = 1; @@ -11327,9 +10627,7 @@ static void f_tabpagewinnr(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_number = nr; } -/* - * "tagfiles()" function - */ +/// "tagfiles()" function static void f_tagfiles(typval_T *argvars, typval_T *rettv, FunPtr fptr) { char *fname; @@ -11348,9 +10646,7 @@ static void f_tagfiles(typval_T *argvars, typval_T *rettv, FunPtr fptr) xfree(fname); } -/* - * "taglist()" function - */ +/// "taglist()" function static void f_taglist(typval_T *argvars, typval_T *rettv, FunPtr fptr) { const char *const tag_pattern = tv_get_string(&argvars[0]); @@ -11368,16 +10664,14 @@ static void f_taglist(typval_T *argvars, typval_T *rettv, FunPtr fptr) (char_u *)tag_pattern, (char_u *)fname); } -/* - * "tempname()" function - */ +/// "tempname()" function static void f_tempname(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->v_type = VAR_STRING; rettv->vval.v_string = vim_tempname(); } -// "termopen(cmd[, cwd])" function +/// "termopen(cmd[, cwd])" function static void f_termopen(typval_T *argvars, typval_T *rettv, FunPtr fptr) { if (check_secure()) { @@ -11490,24 +10784,6 @@ static void f_termopen(typval_T *argvars, typval_T *rettv, FunPtr fptr) channel_create_event(chan, NULL); } -// "test_garbagecollect_now()" function -static void f_test_garbagecollect_now(typval_T *argvars, typval_T *rettv, FunPtr fptr) -{ - // This is dangerous, any Lists and Dicts used internally may be freed - // while still in use. - garbage_collect(true); -} - -// "test_write_list_log()" function -static void f_test_write_list_log(typval_T *const argvars, typval_T *const rettv, FunPtr fptr) -{ - const char *const fname = tv_get_string_chk(&argvars[0]); - if (fname == NULL) { - return; - } - list_write_log(fname); -} - /// "timer_info([timer])" function static void f_timer_info(typval_T *argvars, typval_T *rettv, FunPtr fptr) { @@ -11553,6 +10829,9 @@ static void f_timer_start(typval_T *argvars, typval_T *rettv, FunPtr fptr) dict_T *dict; rettv->vval.v_number = -1; + if (check_secure()) { + return; + } if (argvars[2].v_type != VAR_UNKNOWN) { if (argvars[2].v_type != VAR_DICT @@ -11578,7 +10857,7 @@ static void f_timer_start(typval_T *argvars, typval_T *rettv, FunPtr fptr) } -// "timer_stop(timerid)" function +/// "timer_stop(timerid)" function static void f_timer_stop(typval_T *argvars, typval_T *rettv, FunPtr fptr) { if (argvars[0].v_type != VAR_NUMBER) { @@ -11599,9 +10878,7 @@ static void f_timer_stopall(typval_T *argvars, typval_T *unused, FunPtr fptr) timer_stop_all(); } -/* - * "tolower(string)" function - */ +/// "tolower(string)" function static void f_tolower(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->v_type = VAR_STRING; @@ -11609,9 +10886,7 @@ static void f_tolower(typval_T *argvars, typval_T *rettv, FunPtr fptr) false); } -/* - * "toupper(string)" function - */ +/// "toupper(string)" function static void f_toupper(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->v_type = VAR_STRING; @@ -11619,9 +10894,7 @@ static void f_toupper(typval_T *argvars, typval_T *rettv, FunPtr fptr) true); } -/* - * "tr(string, fromstr, tostr)" function - */ +/// "tr(string, fromstr, tostr)" function static void f_tr(typval_T *argvars, typval_T *rettv, FunPtr fptr) { char buf[NUMBUFLEN]; @@ -11701,7 +10974,7 @@ error: return; } -// "trim({expr})" function +/// "trim({expr})" function static void f_trim(typval_T *argvars, typval_T *rettv, FunPtr fptr) { char buf1[NUMBUFLEN]; @@ -11784,9 +11057,7 @@ static void f_trim(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_string = vim_strnsave(head, tail - head); } -/* - * "type(expr)" function - */ +/// "type(expr)" function static void f_type(typval_T *argvars, typval_T *rettv, FunPtr fptr) { int n = -1; @@ -11818,9 +11089,7 @@ static void f_type(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_number = n; } -/* - * "undofile(name)" function - */ +/// "undofile(name)" function static void f_undofile(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->v_type = VAR_STRING; @@ -11839,9 +11108,7 @@ static void f_undofile(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/* - * "undotree()" function - */ +/// "undotree()" function static void f_undotree(typval_T *argvars, typval_T *rettv, FunPtr fptr) { tv_dict_alloc_ret(rettv); @@ -11859,24 +11126,20 @@ static void f_undotree(typval_T *argvars, typval_T *rettv, FunPtr fptr) tv_dict_add_list(dict, S_LEN("entries"), u_eval_tree(curbuf->b_u_oldhead)); } -/* - * "values(dict)" function - */ +/// "values(dict)" function static void f_values(typval_T *argvars, typval_T *rettv, FunPtr fptr) { dict_list(argvars, rettv, 1); } -/* - * "virtcol(string)" function - */ +/// "virtcol(string)" function static void f_virtcol(typval_T *argvars, typval_T *rettv, FunPtr fptr) { colnr_T vcol = 0; 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. @@ -11895,9 +11158,7 @@ static void f_virtcol(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_number = vcol; } -/* - * "visualmode()" function - */ +/// "visualmode()" function static void f_visualmode(typval_T *argvars, typval_T *rettv, FunPtr fptr) { char_u str[2]; @@ -11913,9 +11174,7 @@ static void f_visualmode(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/* - * "wildmenumode()" function - */ +/// "wildmenumode()" function static void f_wildmenumode(typval_T *argvars, typval_T *rettv, FunPtr fptr) { if (wild_menu_showing || ((State & CMDLINE) && pum_visible())) { @@ -11982,6 +11241,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) { @@ -11993,9 +11288,7 @@ static void f_winbufnr(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/* - * "wincol()" function - */ +/// "wincol()" function static void f_wincol(typval_T *argvars, typval_T *rettv, FunPtr fptr) { validate_cursor(); @@ -12013,7 +11306,7 @@ static void f_winheight(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -// "winlayout()" function +/// "winlayout()" function static void f_winlayout(typval_T *argvars, typval_T *rettv, FunPtr fptr) { tabpage_T *tp; @@ -12032,18 +11325,14 @@ static void f_winlayout(typval_T *argvars, typval_T *rettv, FunPtr fptr) get_framelayout(tp->tp_topframe, rettv->vval.v_list, true); } -/* - * "winline()" function - */ +/// "winline()" function static void f_winline(typval_T *argvars, typval_T *rettv, FunPtr fptr) { validate_cursor(); rettv->vval.v_number = curwin->w_wrow + 1; } -/* - * "winnr()" function - */ +/// "winnr()" function static void f_winnr(typval_T *argvars, typval_T *rettv, FunPtr fptr) { int nr = 1; @@ -12052,9 +11341,7 @@ static void f_winnr(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_number = nr; } -/* - * "winrestcmd()" function - */ +/// "winrestcmd()" function static void f_winrestcmd(typval_T *argvars, typval_T *rettv, FunPtr fptr) { garray_T ga; @@ -12081,9 +11368,7 @@ static void f_winrestcmd(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->v_type = VAR_STRING; } -/* - * "winrestview()" function - */ +/// "winrestview()" function static void f_winrestview(typval_T *argvars, typval_T *rettv, FunPtr fptr) { dict_T *dict; @@ -12134,9 +11419,7 @@ static void f_winrestview(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -/* - * "winsaveview()" function - */ +/// "winsaveview()" function static void f_winsaveview(typval_T *argvars, typval_T *rettv, FunPtr fptr) { dict_T *dict; @@ -12167,7 +11450,7 @@ static void f_winwidth(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } -// "windowsversion()" function +/// "windowsversion()" function static void f_windowsversion(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->v_type = VAR_STRING; @@ -12258,9 +11541,8 @@ static void f_writefile(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } } -/* - * "xor(expr, expr)" function - */ + +/// "xor(expr, expr)" function static void f_xor(typval_T *argvars, typval_T *rettv, FunPtr fptr) { rettv->vval.v_number = tv_get_number_chk(&argvars[0], NULL) diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index 3e37e8cbb6..2a432ecb47 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,12 +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" -// TODO(ZyX-I): Move line_breakcheck out of misc1 -#include "nvim/misc1.h" // For line_breakcheck -#include "nvim/os/fileio.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "eval/typval.c.generated.h" @@ -1124,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; } @@ -1143,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; } @@ -1165,6 +1170,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; @@ -1186,6 +1196,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; @@ -2456,13 +2469,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 @@ -3094,7 +3105,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; } @@ -3208,8 +3219,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 9478a8441b..eb5c6e503a 100644 --- a/src/nvim/eval/userfunc.c +++ b/src/nvim/eval/userfunc.c @@ -17,7 +17,6 @@ #include "nvim/getchar.h" #include "nvim/globals.h" #include "nvim/lua/executor.h" -#include "nvim/misc1.h" #include "nvim/os/input.h" #include "nvim/regexp.h" #include "nvim/search.h" @@ -202,7 +201,7 @@ static void register_closure(ufunc_T *fp) } -/// Get a name for a lambda. Returned in static memory. +/// @return a name for a lambda. Returned in static memory. char_u *get_lambda_name(void) { static char_u name[30]; @@ -517,7 +516,7 @@ static char_u *fname_trans_sid(const char_u *const name, char_u *const fname_buf if (llen > 0) { fname_buf[0] = K_SPECIAL; fname_buf[1] = KS_EXTRA; - fname_buf[2] = (int)KE_SNR; + fname_buf[2] = KE_SNR; int i = 3; if (eval_fname_sid((const char *)name)) { // "<SID>" or "s:" if (current_sctx.sc_sid <= 0) { @@ -545,7 +544,8 @@ static char_u *fname_trans_sid(const char_u *const name, char_u *const fname_buf } /// Find a function by name, return pointer to it in ufuncs. -/// @return NULL for unknown function. +/// +/// @return NULL for unknown function. ufunc_T *find_func(const char_u *name) { hashitem_T *hi; @@ -557,11 +557,9 @@ ufunc_T *find_func(const char_u *name) return NULL; } -/* - * Copy the function name of "fp" to buffer "buf". - * "buf" must be able to hold the function name plus three bytes. - * Takes care of script-local function names. - */ +/// Copy the function name of "fp" to buffer "buf". +/// "buf" must be able to hold the function name plus three bytes. +/// Takes care of script-local function names. static void cat_func_name(char_u *buf, ufunc_T *fp) { if (fp->uf_name[0] == K_SPECIAL) { @@ -572,9 +570,7 @@ static void cat_func_name(char_u *buf, ufunc_T *fp) } } -/* - * Add a number variable "name" to dict "dp" with value "nr". - */ +/// Add a number variable "name" to dict "dp" with value "nr". static void add_nr_var(dict_T *dp, dictitem_T *v, char *name, varnumber_T nr) { #ifndef __clang_analyzer__ @@ -587,7 +583,7 @@ static void add_nr_var(dict_T *dp, dictitem_T *v, char *name, varnumber_T nr) v->di_tv.vval.v_number = nr; } -// Free "fc" +/// Free "fc" static void free_funccal(funccall_T *fc) { for (int i = 0; i < fc->fc_funcs.ga_len; i++) { @@ -607,9 +603,9 @@ static void free_funccal(funccall_T *fc) xfree(fc); } -// Free "fc" and what it contains. -// Can be called only when "fc" is kept beyond the period of it called, -// i.e. after cleanup_function_call(fc). +/// Free "fc" and what it contains. +/// Can be called only when "fc" is kept beyond the period of it called, +/// i.e. after cleanup_function_call(fc). static void free_funccal_contents(funccall_T *fc) { // Free all l: variables. @@ -758,7 +754,7 @@ static void func_clear_items(ufunc_T *fp) /// Free all things that a function contains. Does not free the function /// itself, use func_free() for that. /// -/// param[in] force When true, we are exiting. +/// @param[in] force When true, we are exiting. static void func_clear(ufunc_T *fp, bool force) { if (fp->uf_cleared) { @@ -774,7 +770,7 @@ static void func_clear(ufunc_T *fp, bool force) /// Free a function and remove it from the list of functions. Does not free /// what a function contains, call func_clear() first. /// -/// param[in] fp The function to free. +/// @param[in] fp The function to free. static void func_free(ufunc_T *fp) { // only remove it when not done already, otherwise we would remove a newer @@ -787,7 +783,7 @@ static void func_free(ufunc_T *fp) /// Free all things that a function contains and free the function itself. /// -/// param[in] force When true, we are exiting. +/// @param[in] force When true, we are exiting. static void func_clear_free(ufunc_T *fp, bool force) { func_clear(fp, force); @@ -796,13 +792,13 @@ static void func_clear_free(ufunc_T *fp, bool force) /// Call a user function /// -/// @param fp Function to call. -/// @param[in] argcount Number of arguments. -/// @param argvars Arguments. -/// @param[out] rettv Return value. -/// @param[in] firstline First line of range. -/// @param[in] lastline Last line of range. -/// @param selfdict Dictionary for "self" for dictionary functions. +/// @param fp Function to call. +/// @param[in] argcount Number of arguments. +/// @param argvars Arguments. +/// @param[out] rettv Return value. +/// @param[in] firstline First line of range. +/// @param[in] lastline Last line of range. +/// @param selfdict Dictionary for "self" for dictionary functions. void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rettv, linenr_T firstline, linenr_T lastline, dict_T *selfdict) FUNC_ATTR_NONNULL_ARG(1, 3, 4) @@ -1231,8 +1227,8 @@ static bool func_name_refcount(char_u *name) static funccal_entry_T *funccal_stack = NULL; -// Save the current function call pointer, and set it to NULL. -// Used when executing autocommands and for ":source". +/// Save the current function call pointer, and set it to NULL. +/// Used when executing autocommands and for ":source". void save_funccal(funccal_entry_T *entry) { entry->top_funccal = current_funccal; @@ -1385,8 +1381,8 @@ func_call_skip_call: return r; } -// Give an error message for the result of a function. -// Nothing if "error" is FCERR_NONE. +/// Give an error message for the result of a function. +/// Nothing if "error" is FCERR_NONE. static void user_func_error(int error, const char_u *name) FUNC_ATTR_NONNULL_ALL { @@ -1714,7 +1710,7 @@ char_u *trans_function_name(char_u **pp, bool skip, int flags, funcdict_T *fdp, // Check for hard coded <SNR>: already translated function ID (from a user // command). if ((*pp)[0] == K_SPECIAL && (*pp)[1] == KS_EXTRA - && (*pp)[2] == (int)KE_SNR) { + && (*pp)[2] == KE_SNR) { *pp += 3; len = get_id_len((const char **)pp) + 3; return (char_u *)xmemdupz(start, len); @@ -1822,7 +1818,7 @@ char_u *trans_function_name(char_u **pp, bool skip, int flags, funcdict_T *fdp, // Change "<SNR>" to the byte sequence. name[0] = K_SPECIAL; name[1] = KS_EXTRA; - name[2] = (int)KE_SNR; + name[2] = KE_SNR; memmove(name + 3, name + 5, strlen((char *)name + 5) + 1); } goto theend; @@ -1889,7 +1885,7 @@ char_u *trans_function_name(char_u **pp, bool skip, int flags, funcdict_T *fdp, if (!skip && lead > 0) { name[0] = K_SPECIAL; name[1] = KS_EXTRA; - name[2] = (int)KE_SNR; + name[2] = KE_SNR; if (sid_buf_len > 0) { // If it's "<SID>" memcpy(name + 3, sid_buf, sid_buf_len); } @@ -1903,9 +1899,7 @@ theend: return name; } -/* - * ":function" - */ +/// ":function" void ex_function(exarg_T *eap) { char_u *theline; @@ -2574,6 +2568,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; @@ -2595,11 +2590,9 @@ ret_free: } } // NOLINT(readability/fn_size) -/* - * Return 5 if "p" starts with "<SID>" or "<SNR>" (ignoring case). - * Return 2 if "p" starts with "s:". - * Return 0 otherwise. - */ +/// @return 5 if "p" starts with "<SID>" or "<SNR>" (ignoring case). +/// 2 if "p" starts with "s:". +/// 0 otherwise. int eval_fname_script(const char *const p) { // Use mb_strnicmp() because in Turkish comparing the "I" may not work with @@ -2625,10 +2618,10 @@ bool translated_function_exists(const char *name) /// Check whether function with the given name exists /// -/// @param[in] name Function name. -/// @param[in] no_deref Whether to dereference a Funcref. +/// @param[in] name Function name. +/// @param[in] no_deref Whether to dereference a Funcref. /// -/// @return True if it exists, false otherwise. +/// @return true if it exists, false otherwise. bool function_exists(const char *const name, bool no_deref) { const char_u *nm = (const char_u *)name; @@ -2651,10 +2644,8 @@ bool function_exists(const char *const name, bool no_deref) return n; } -/* - * Function given to ExpandGeneric() to obtain the list of user defined - * function names. - */ +/// Function given to ExpandGeneric() to obtain the list of user defined +/// function names. char_u *get_user_func_name(expand_T *xp, int idx) { static size_t done; @@ -2771,10 +2762,8 @@ void ex_delfunction(exarg_T *eap) } } -/* - * Unreference a Function: decrement the reference count and free it when it - * becomes zero. - */ +/// Unreference a Function: decrement the reference count and free it when it +/// becomes zero. void func_unref(char_u *name) { ufunc_T *fp = NULL; @@ -2868,9 +2857,7 @@ static int can_free_funccal(funccall_T *fc, int copyID) && fc->fc_copyID != copyID; } -/* - * ":return [expr]" - */ +/// ":return [expr]" void ex_return(exarg_T *eap) { char_u *arg = eap->arg; @@ -2921,9 +2908,7 @@ void ex_return(exarg_T *eap) // TODO(ZyX-I): move to eval/ex_cmds -/* - * ":1,25call func(arg1, arg2)" function call. - */ +/// ":1,25call func(arg1, arg2)" function call. void ex_call(exarg_T *eap) { char_u *arg = eap->arg; @@ -3050,14 +3035,16 @@ end: xfree(tofree); } -/* - * Return from a function. Possibly makes the return pending. Also called - * for a pending return at the ":endtry" or after returning from an extra - * do_cmdline(). "reanimate" is used in the latter case. "is_cmd" is set - * when called due to a ":return" command. "rettv" may point to a typval_T - * with the return rettv. Returns TRUE when the return can be carried out, - * FALSE when the return gets pending. - */ +/// Return from a function. Possibly makes the return pending. Also called +/// for a pending return at the ":endtry" or after returning from an extra +/// do_cmdline(). "reanimate" is used in the latter case. +/// +/// @param reanimate used after returning from an extra do_cmdline(). +/// @param is_cmd set when called due to a ":return" command. +/// @param rettv may point to a typval_T with the return rettv. +/// +/// @return TRUE when the return can be carried out, +/// FALSE when the return gets pending. int do_return(exarg_T *eap, int reanimate, int is_cmd, void *rettv) { int idx; @@ -3068,12 +3055,10 @@ int do_return(exarg_T *eap, int reanimate, int is_cmd, void *rettv) current_funccal->returned = false; } - // // Cleanup (and deactivate) conditionals, but stop when a try conditional // not in its finally clause (which then is to be executed next) is found. // In this case, make the ":return" pending for execution at the ":endtry". // Otherwise, return normally. - // idx = cleanup_conditionals(eap->cstack, 0, true); if (idx >= 0) { cstack->cs_pending[idx] = CSTP_RETURN; @@ -3126,10 +3111,8 @@ int do_return(exarg_T *eap, int reanimate, int is_cmd, void *rettv) return idx < 0; } -/* - * Generate a return command for producing the value of "rettv". The result - * is an allocated string. Used by report_pending() for verbose messages. - */ +/// Generate a return command for producing the value of "rettv". The result +/// is an allocated string. Used by report_pending() for verbose messages. char_u *get_return_cmd(void *rettv) { char_u *s = NULL; @@ -3151,11 +3134,10 @@ char_u *get_return_cmd(void *rettv) return vim_strsave(IObuff); } -/* - * Get next function line. - * Called by do_cmdline() to get the next line. - * Returns allocated string, or NULL for end of function. - */ +/// Get next function line. +/// Called by do_cmdline() to get the next line. +/// +/// @return allocated string, or NULL for end of function. char_u *get_func_line(int c, void *cookie, int indent, bool do_concat) { funccall_T *fcp = (funccall_T *)cookie; @@ -3206,10 +3188,8 @@ char_u *get_func_line(int c, void *cookie, int indent, bool do_concat) return retval; } -/* - * Return TRUE if the currently active function should be ended, because a - * return was encountered or an error occurred. Used inside a ":while". - */ +/// @return TRUE if the currently active function should be ended, because a +/// return was encountered or an error occurred. Used inside a ":while". int func_has_ended(void *cookie) { funccall_T *fcp = (funccall_T *)cookie; @@ -3220,9 +3200,7 @@ int func_has_ended(void *cookie) || fcp->returned; } -/* - * return TRUE if cookie indicates a function which "abort"s on errors. - */ +/// @return TRUE if cookie indicates a function which "abort"s on errors. int func_has_abort(void *cookie) { return ((funccall_T *)cookie)->func->uf_flags & FC_ABORT; @@ -3289,41 +3267,31 @@ void make_partial(dict_T *const selfdict, typval_T *const rettv) } } -/* - * Return the name of the executed function. - */ +/// @return the name of the executed function. char_u *func_name(void *cookie) { return ((funccall_T *)cookie)->func->uf_name; } -/* - * Return the address holding the next breakpoint line for a funccall cookie. - */ +/// @return the address holding the next breakpoint line for a funccall cookie. linenr_T *func_breakpoint(void *cookie) { return &((funccall_T *)cookie)->breakpoint; } -/* - * Return the address holding the debug tick for a funccall cookie. - */ +/// @return the address holding the debug tick for a funccall cookie. int *func_dbg_tick(void *cookie) { return &((funccall_T *)cookie)->dbg_tick; } -/* - * Return the nesting level for a funccall cookie. - */ +/// @return the nesting level for a funccall cookie. int func_level(void *cookie) { return ((funccall_T *)cookie)->level; } -/* - * Return TRUE when a function was ended by a ":return" command. - */ +/// @return TRUE when a function was ended by a ":return" command. int current_func_returned(void) { return current_funccal->returned; @@ -3372,8 +3340,8 @@ funccall_T *get_funccal(void) return funccal; } -/// Return the hashtable used for local variables in the current funccal. -/// Return NULL if there is no current funccal. +/// @return hashtable used for local variables in the current funccal or +/// NULL if there is no current funccal. hashtab_T *get_funccal_local_ht(void) { if (current_funccal == NULL) { @@ -3382,8 +3350,8 @@ hashtab_T *get_funccal_local_ht(void) return &get_funccal()->l_vars.dv_hashtab; } -/// Return the l: scope variable. -/// Return NULL if there is no current funccal. +/// @return the l: scope variable or +/// NULL if there is no current funccal. dictitem_T *get_funccal_local_var(void) { if (current_funccal == NULL) { @@ -3392,8 +3360,8 @@ dictitem_T *get_funccal_local_var(void) return (dictitem_T *)&get_funccal()->l_vars_var; } -/// Return the hashtable used for argument in the current funccal. -/// Return NULL if there is no current funccal. +/// @return the hashtable used for argument in the current funccal or +/// NULL if there is no current funccal. hashtab_T *get_funccal_args_ht(void) { if (current_funccal == NULL) { @@ -3402,8 +3370,8 @@ hashtab_T *get_funccal_args_ht(void) return &get_funccal()->l_avars.dv_hashtab; } -/// Return the a: scope variable. -/// Return NULL if there is no current funccal. +/// @return the a: scope variable or +/// NULL if there is no current funccal. dictitem_T *get_funccal_args_var(void) { if (current_funccal == NULL) { @@ -3412,9 +3380,7 @@ dictitem_T *get_funccal_args_var(void) return (dictitem_T *)¤t_funccal->l_avars_var; } -/* - * List function variables, if there is a function. - */ +/// List function variables, if there is a function. void list_func_vars(int *first) { if (current_funccal != NULL) { @@ -3423,9 +3389,8 @@ void list_func_vars(int *first) } } -/// If "ht" is the hashtable for local variables in the current funccal, return -/// the dict that contains it. -/// Otherwise return NULL. +/// @return if "ht" is the hashtable for local variables in the current +/// funccal, return the dict that contains it. Otherwise return NULL. dict_T *get_current_funccal_dict(hashtab_T *ht) { if (current_funccal != NULL && ht == ¤t_funccal->l_vars.dv_hashtab) { @@ -3589,7 +3554,7 @@ bool set_ref_in_func_args(int copyID) /// "list_stack" is used to add lists to be marked. Can be NULL. /// "ht_stack" is used to add hashtabs to be marked. Can be NULL. /// -/// @return true if setting references failed somehow. +/// @return true if setting references failed somehow. bool set_ref_in_func(char_u *name, ufunc_T *fp_in, int copyID) { ufunc_T *fp = fp_in; 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/event/process.c b/src/nvim/event/process.c index dae4dad16d..653fffae1c 100644 --- a/src/nvim/event/process.c +++ b/src/nvim/event/process.c @@ -237,7 +237,7 @@ void process_stop(Process *proc) FUNC_ATTR_NONNULL_ALL KILL_TIMEOUT_MS, 0); } -// Frees process-owned resources. +/// Frees process-owned resources. void process_free(Process *proc) FUNC_ATTR_NONNULL_ALL { if (proc->argv != NULL) { diff --git a/src/nvim/event/rstream.c b/src/nvim/event/rstream.c index f070c8179f..26b5ce3b75 100644 --- a/src/nvim/event/rstream.c +++ b/src/nvim/event/rstream.c @@ -11,8 +11,8 @@ #include "nvim/event/loop.h" #include "nvim/event/rstream.h" #include "nvim/log.h" +#include "nvim/main.h" #include "nvim/memory.h" -#include "nvim/misc1.h" #include "nvim/vim.h" #ifdef INCLUDE_GENERATED_DECLARATIONS @@ -85,7 +85,7 @@ static void on_rbuffer_nonfull(RBuffer *buf, void *data) // Callbacks used by libuv -// Called by libuv to allocate memory for reading. +/// Called by libuv to allocate memory for reading. static void alloc_cb(uv_handle_t *handle, size_t suggested, uv_buf_t *buf) { Stream *stream = handle->data; @@ -95,9 +95,9 @@ static void alloc_cb(uv_handle_t *handle, size_t suggested, uv_buf_t *buf) buf->len = UV_BUF_LEN(write_count); } -// Callback invoked by libuv after it copies the data into the buffer provided -// by `alloc_cb`. This is also called on EOF or when `alloc_cb` returns a -// 0-length buffer. +/// Callback invoked by libuv after it copies the data into the buffer provided +/// by `alloc_cb`. This is also called on EOF or when `alloc_cb` returns a +/// 0-length buffer. static void read_cb(uv_stream_t *uvstream, ssize_t cnt, const uv_buf_t *buf) { Stream *stream = uvstream->data; @@ -134,11 +134,11 @@ static void read_cb(uv_stream_t *uvstream, ssize_t cnt, const uv_buf_t *buf) invoke_read_cb(stream, nread, false); } -// Called by the by the 'idle' handle to emulate a reading event -// -// Idle callbacks are invoked once per event loop: -// - to perform some very low priority activity. -// - to keep the loop "alive" (so there is always an event to process) +/// Called by the by the 'idle' handle to emulate a reading event +/// +/// Idle callbacks are invoked once per event loop: +/// - to perform some very low priority activity. +/// - to keep the loop "alive" (so there is always an event to process) static void fread_idle_cb(uv_idle_t *handle) { uv_fs_t req; diff --git a/src/nvim/event/socket.c b/src/nvim/event/socket.c index 7948a7be83..c20716496f 100644 --- a/src/nvim/event/socket.c +++ b/src/nvim/event/socket.c @@ -53,10 +53,8 @@ int socket_watcher_init(Loop *loop, SocketWatcher *watcher, const char *endpoint uv_getaddrinfo_t request; int retval = uv_getaddrinfo(&loop->uv, &request, NULL, addr, port, - &(struct addrinfo){ - .ai_family = AF_UNSPEC, - .ai_socktype = SOCK_STREAM, - }); + &(struct addrinfo){ .ai_family = AF_UNSPEC, + .ai_socktype = SOCK_STREAM, }); if (retval != 0) { ELOG("Host lookup failed: %s", endpoint); return retval; @@ -103,10 +101,9 @@ int socket_watcher_start(SocketWatcher *watcher, int backlog, socket_cb cb) // contain 0 in this case, unless uv_tcp_getsockname() is used first. uv_tcp_getsockname(&watcher->uv.tcp.handle, (struct sockaddr *)&sas, &(int){ sizeof(sas) }); - uint16_t port = (uint16_t)( - (sas.ss_family == AF_INET) - ? (STRUCT_CAST(struct sockaddr_in, &sas))->sin_port - : (STRUCT_CAST(struct sockaddr_in6, &sas))->sin6_port); + uint16_t port = (uint16_t)((sas.ss_family == AF_INET) + ? (STRUCT_CAST(struct sockaddr_in, &sas))->sin_port + : (STRUCT_CAST(struct sockaddr_in6, &sas))->sin6_port); // v:servername uses the string from watcher->addr size_t len = strlen(watcher->addr); snprintf(watcher->addr+len, sizeof(watcher->addr)-len, ":%" PRIu16, diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c index 77944851d2..98aabe89b3 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -38,7 +38,9 @@ #include "nvim/garray.h" #include "nvim/getchar.h" #include "nvim/highlight.h" +#include "nvim/highlight_group.h" #include "nvim/indent.h" +#include "nvim/input.h" #include "nvim/log.h" #include "nvim/main.h" #include "nvim/mark.h" @@ -46,7 +48,6 @@ #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/message.h" -#include "nvim/misc1.h" #include "nvim/mouse.h" #include "nvim/move.h" #include "nvim/normal.h" @@ -225,9 +226,7 @@ void do_ascii(const exarg_T *const eap) msg((char *)IObuff); } -/* - * ":left", ":center" and ":right": align text. - */ +/// ":left", ":center" and ":right": align text. void ex_align(exarg_T *eap) { pos_T save_curpos; @@ -305,9 +304,8 @@ void ex_align(exarg_T *eap) */ do { (void)set_indent(++new_indent, 0); - } - while (linelen(NULL) <= width); - --new_indent; + } while (linelen(NULL) <= width); + new_indent--; break; } --new_indent; @@ -325,9 +323,7 @@ void ex_align(exarg_T *eap) beginline(BL_WHITE | BL_FIX); } -/* - * Get the length of the current line, excluding trailing white space. - */ +/// @return the length of the current line, excluding trailing white space. static int linelen(int *has_tab) { char_u *line; @@ -453,7 +449,7 @@ static int sort_compare(const void *s1, const void *s2) return result; } -// ":sort". +/// ":sort". void ex_sort(exarg_T *eap) { regmatch_T regmatch; @@ -724,9 +720,7 @@ sortend: } } -/* - * ":retab". - */ +/// ":retab". void ex_retab(exarg_T *eap) { linenr_T lnum; @@ -815,7 +809,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) { @@ -848,6 +846,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 @@ -900,11 +902,9 @@ void ex_retab(exarg_T *eap) u_clearline(); } -/* - * :move command - move lines line1-line2 to line dest - * - * return FAIL for failure, OK otherwise - */ +/// :move command - move lines line1-line2 to line dest +/// +/// @return FAIL for failure, OK otherwise int do_move(linenr_T line1, linenr_T line2, linenr_T dest) { char_u *str; @@ -981,8 +981,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 { @@ -992,10 +994,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); @@ -1049,18 +1055,18 @@ int do_move(linenr_T line1, linenr_T line2, linenr_T dest) return OK; } -/* - * ":copy" - */ +/// ":copy" void ex_copy(linenr_T line1, linenr_T line2, linenr_T n) { linenr_T count; 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: @@ -1100,6 +1106,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); } @@ -1114,11 +1123,9 @@ void free_prev_shellcmd(void) #endif -/* - * Handle the ":!cmd" command. Also for ":r !cmd" and ":w !cmd" - * Bangs in the argument are replaced with the previously entered command. - * Remember the argument. - */ +/// Handle the ":!cmd" command. Also for ":r !cmd" and ":w !cmd" +/// Bangs in the argument are replaced with the previously entered command. +/// Remember the argument. void do_bang(int addr_count, exarg_T *eap, bool forceit, bool do_in, bool do_out) FUNC_ATTR_NONNULL_ALL { @@ -1270,12 +1277,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; @@ -1350,7 +1363,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; } @@ -1456,10 +1469,15 @@ error: filterend: + cmdmod.lockmarks = save_lockmarks; if (curbuf != old_curbuf) { - --no_wait_return; + 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); } @@ -1655,9 +1673,7 @@ void print_line_no_prefix(linenr_T lnum, int use_number, int list) msg_prt_line(ml_get(lnum), list); } -/* - * Print a text line. Also in silent mode ("ex -s"). - */ +/// Print a text line. Also in silent mode ("ex -s"). void print_line(linenr_T lnum, int use_number, int list) { int save_silent = silent_mode; @@ -1725,9 +1741,7 @@ int rename_buffer(char_u *new_fname) return OK; } -/* - * ":file[!] [fname]". - */ +/// ":file[!] [fname]". void ex_file(exarg_T *eap) { // ":0file" removes the file name. Check for illegal uses ":3file", @@ -1753,9 +1767,7 @@ void ex_file(exarg_T *eap) } } -/* - * ":update". - */ +/// ":update". void ex_update(exarg_T *eap) { if (curbufIsChanged()) { @@ -1763,9 +1775,7 @@ void ex_update(exarg_T *eap) } } -/* - * ":write" and ":saveas". - */ +/// ":write" and ":saveas". void ex_write(exarg_T *eap) { if (eap->cmdidx == CMD_saveas) { @@ -1781,14 +1791,12 @@ void ex_write(exarg_T *eap) } } -/* - * write current buffer to file 'eap->arg' - * if 'eap->append' is TRUE, append to the file - * - * if *eap->arg == NUL write to current file - * - * return FAIL for failure, OK otherwise - */ +/// write current buffer to file 'eap->arg' +/// if 'eap->append' is TRUE, append to the file +/// +/// if *eap->arg == NUL write to current file +/// +/// @return FAIL for failure, OK otherwise int do_write(exarg_T *eap) { int other; @@ -1914,7 +1922,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); @@ -2041,9 +2049,7 @@ int check_overwrite(exarg_T *eap, buf_T *buf, char_u *fname, char_u *ffname, int return OK; } -/* - * Handle ":wnext", ":wNext" and ":wprevious" commands. - */ +/// Handle ":wnext", ":wNext" and ":wprevious" commands. void ex_wnext(exarg_T *eap) { int i; @@ -2060,9 +2066,7 @@ void ex_wnext(exarg_T *eap) } } -/* - * ":wall", ":wqall" and ":xall": Write all changed files (and exit). - */ +/// ":wall", ":wqall" and ":xall": Write all changed files (and exit). void do_wqall(exarg_T *eap) { int error = 0; @@ -2094,7 +2098,7 @@ void do_wqall(exarg_T *eap) } if (buf->b_ffname == NULL) { semsg(_("E141: No file name for buffer %" PRId64), (int64_t)buf->b_fnum); - ++error; + error++; } else if (check_readonly(&eap->forceit, buf) || check_overwrite(eap, buf, buf->b_fname, buf->b_ffname, FALSE) == FAIL) { @@ -2120,24 +2124,21 @@ void do_wqall(exarg_T *eap) } } -/* - * Check the 'write' option. - * Return TRUE and give a message when it's not st. - */ -int not_writing(void) +/// Check the 'write' option. +/// +/// @return true and give a message when it's not st. +bool not_writing(void) { if (p_write) { - return FALSE; + return false; } emsg(_("E142: File not written: Writing is disabled by 'write' option")); - return TRUE; + return true; } -/* - * Check if a buffer is read-only (either 'readonly' option is set or file is - * read-only). Ask for overruling in a dialog. Return TRUE and give an error - * message when the buffer is readonly. - */ +/// Check if a buffer is read-only (either 'readonly' option is set or file is +/// read-only). Ask for overruling in a dialog. Return TRUE and give an error +/// message when the buffer is readonly. static int check_readonly(int *forceit, buf_T *buf) { // Handle a file being readonly when the 'readonly' option is set or when @@ -2178,15 +2179,16 @@ static int check_readonly(int *forceit, buf_T *buf) return FALSE; } -// Try to abandon the current file and edit a new or existing file. -// "fnum" is the number of the file, if zero use "ffname_arg"/"sfname_arg". -// "lnum" is the line number for the cursor in the new file (if non-zero). -// -// Return: -// GETFILE_ERROR for "normal" error, -// GETFILE_NOT_WRITTEN for "not written" error, -// GETFILE_SAME_FILE for success -// GETFILE_OPEN_OTHER for successfully opening another file. +/// Try to abandon the current file and edit a new or existing file. +/// +/// @param fnum the number of the file, if zero use "ffname_arg"/"sfname_arg". +/// @param lnum the line number for the cursor in the new file (if non-zero). +/// +/// @return: +/// GETFILE_ERROR for "normal" error, +/// GETFILE_NOT_WRITTEN for "not written" error, +/// GETFILE_SAME_FILE for success +/// GETFILE_OPEN_OTHER for successfully opening another file. int getfile(int fnum, char_u *ffname_arg, char_u *sfname_arg, int setpm, linenr_T lnum, int forceit) { char_u *ffname = ffname_arg; @@ -2525,9 +2527,9 @@ int do_ecmd(int fnum, char_u *ffname, char_u *sfname, exarg_T *eap, linenr_T new // Close the link to the current buffer. This will set // oldwin->w_buffer to NULL. u_sync(false); - const bool did_decrement = close_buffer(oldwin, curbuf, - (flags & ECMD_HIDE) || curbuf->terminal ? 0 : DOBUF_UNLOAD, - false); + const bool did_decrement + = close_buffer(oldwin, curbuf, (flags & ECMD_HIDE) || curbuf->terminal ? 0 : DOBUF_UNLOAD, + false, false); // Autocommands may have closed the window. if (win_valid(the_curwin)) { @@ -2873,7 +2875,7 @@ int do_ecmd(int fnum, char_u *ffname, char_u *sfname, exarg_T *eap, linenr_T new redraw_curbuf_later(NOT_VALID); // redraw this buffer later } - if (p_im) { + if (p_im && (State & INSERT) == 0) { need_start_insertmode = true; } @@ -2907,9 +2909,7 @@ static void delbuf_msg(char_u *name) static int append_indent = 0; // autoindent for first line -/* - * ":insert" and ":append", also used by ":change" - */ +/// ":insert" and ":append", also used by ":change" void ex_append(exarg_T *eap) { char_u *theline; @@ -2945,7 +2945,7 @@ void ex_append(exarg_T *eap) } for (;;) { - msg_scroll = TRUE; + msg_scroll = true; need_wait_return = false; if (curbuf->b_p_ai) { if (append_indent >= 0) { @@ -3009,7 +3009,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; @@ -3028,15 +3033,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); @@ -3045,9 +3051,7 @@ void ex_append(exarg_T *eap) ex_no_reprint = true; } -/* - * ":change" - */ +/// ":change" void ex_change(exarg_T *eap) { linenr_T lnum; @@ -3131,7 +3135,7 @@ void ex_z(exarg_T *eap) // the number of '-' and '+' multiplies the distance if (*kind == '-' || *kind == '+') { - for (x = kind + 1; *x == *kind; ++x) { + for (x = kind + 1; *x == *kind; x++) { } } @@ -3214,26 +3218,24 @@ void ex_z(exarg_T *eap) ex_no_reprint = true; } -/* - * Check if the secure flag is set (.exrc or .vimrc in current directory). - * If so, give an error message and return TRUE. - * Otherwise, return FALSE. - */ -int check_secure(void) +/// @return true if the secure flag is set (.exrc or .vimrc in current directory) +/// and also give an error message. +/// Otherwise, return false. +bool check_secure(void) { if (secure) { secure = 2; emsg(_(e_curdir)); - return TRUE; + return true; } // In the sandbox more things are not allowed, including the things // disallowed in secure mode. if (sandbox != 0) { emsg(_(e_sandbox)); - return TRUE; + return true; } - return FALSE; + return false; } /// Previous substitute replacement string @@ -3625,12 +3627,25 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout, bool do_buf_event, handle sub_firstline = NULL; - // ~ in the substitute pattern is replaced with the old pattern. - // 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); - if (!(sub[0] == '\\' && sub[1] == '=')) { - sub = regtilde(sub, p_magic); + + bool sub_needs_free = false; + char_u *sub_copy = NULL; + + // If the substitute pattern starts with "\=" then it's an expression. + // Make a copy, a recursive function may free it. + // Otherwise, '~' in the substitute pattern is replaced with the old + // pattern. We do it here once to avoid it to be replaced over and over + // again. + if (sub[0] == '\\' && sub[1] == '=') { + sub = vim_strsave(sub); + sub_copy = sub; + } else { + char_u *source = sub; + sub = regtilde(sub, p_magic, preview); + // 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. @@ -3852,13 +3867,22 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout, bool do_buf_event, handle prompt = xmallocz(ec + 1); memset(prompt, ' ', sc); memset(prompt + sc, '^', ec - sc + 1); - resp = (char_u *)getcmdline_prompt(NUL, prompt, 0, EXPAND_NOTHING, + resp = (char_u *)getcmdline_prompt(-1, prompt, 0, EXPAND_NOTHING, NULL, CALLBACK_NONE); msg_putchar('\n'); xfree(prompt); if (resp != NULL) { typed = *resp; xfree(resp); + } else { + // getcmdline_prompt() returns NULL if there is no command line to return. + typed = NUL; + } + // When ":normal" runs out of characters we get + // an empty line. Use "q" to get out of the + // loop. + if (ex_normal_busy && typed == NUL) { + typed = 'q'; } } else { char_u *orig_line = NULL; @@ -4289,18 +4313,22 @@ skip: #define PUSH_PREVIEW_LINES() \ do { \ - linenr_T match_lines = current_match.end.lnum \ - - current_match.start.lnum +1; \ - if (preview_lines.subresults.size > 0) { \ - linenr_T last = kv_last(preview_lines.subresults).end.lnum; \ - if (last == current_match.start.lnum) { \ - preview_lines.lines_needed += match_lines - 1; \ + if (preview) { \ + linenr_T match_lines = current_match.end.lnum \ + - current_match.start.lnum +1; \ + if (preview_lines.subresults.size > 0) { \ + linenr_T last = kv_last(preview_lines.subresults).end.lnum; \ + if (last == current_match.start.lnum) { \ + preview_lines.lines_needed += match_lines - 1; \ + } else { \ + preview_lines.lines_needed += match_lines; \ + } \ + } else { \ + preview_lines.lines_needed += match_lines; \ } \ - } else { \ - preview_lines.lines_needed += match_lines; \ + kv_push(preview_lines.subresults, current_match); \ } \ - kv_push(preview_lines.subresults, current_match); \ - } while (0) + } while (0) // Push the match to preview_lines. PUSH_PREVIEW_LINES(); @@ -4351,10 +4379,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 @@ -4393,6 +4423,10 @@ skip: } vim_regfree(regmatch.regprog); + xfree(sub_copy); + if (sub_needs_free) { + xfree(sub); + } // Restore the flag values, they can be used for ":&&". subflags.do_all = save_do_all; @@ -4490,22 +4524,20 @@ static void global_exe_one(char_u *const cmd, const linenr_T lnum) } } -/* - * Execute a global command of the form: - * - * g/pattern/X : execute X on all lines where pattern matches - * v/pattern/X : execute X on all lines where pattern does not match - * - * where 'X' is an EX command - * - * The command character (as well as the trailing slash) is optional, and - * is assumed to be 'p' if missing. - * - * This is implemented in two passes: first we scan the file for the pattern and - * set a mark for each line that (not) matches. Secondly we execute the command - * for each line that has a mark. This is required because after deleting - * lines we do not know where to search for the next match. - */ +/// Execute a global command of the form: +/// +/// g/pattern/X : execute X on all lines where pattern matches +/// v/pattern/X : execute X on all lines where pattern does not match +/// +/// where 'X' is an EX command +/// +/// The command character (as well as the trailing slash) is optional, and +/// is assumed to be 'p' if missing. +/// +/// This is implemented in two passes: first we scan the file for the pattern and +/// set a mark for each line that (not) matches. Secondly we execute the command +/// for each line that has a mark. This is required because after deleting +/// lines we do not know where to search for the next match. void ex_global(exarg_T *eap) { linenr_T lnum; // line number according to old situation @@ -4589,6 +4621,9 @@ void ex_global(exarg_T *eap) // a match on this line? match = vim_regexec_multi(®match, curwin, curbuf, lnum, (colnr_T)0, NULL, NULL); + if (regmatch.regprog == NULL) { + break; // re-compiling regprog failed + } if ((type == 'g' && match) || (type == 'v' && !match)) { ml_setmarked(lnum); ndone++; @@ -4718,9 +4753,7 @@ bool prepare_tagpreview(bool undo_sync) } -/* - * ":help": open a read-only window on a help file - */ +/// ":help": open a read-only window on a help file void ex_help(exarg_T *eap) { char_u *arg; @@ -4903,11 +4936,10 @@ erret: } -/* - * In an argument search for a language specifiers in the form "@xx". - * Changes the "@" to NUL if found, and returns a pointer to "xx". - * Returns NULL if not found. - */ +/// In an argument search for a language specifiers in the form "@xx". +/// Changes the "@" to NUL if found, and returns a pointer to "xx". +/// +/// @return NULL if not found. char_u *check_help_lang(char_u *arg) { int len = (int)STRLEN(arg); @@ -4973,10 +5005,8 @@ int help_heuristic(char_u *matched_string, int offset, int wrong_case) return (int)(100 * num_letters + STRLEN(matched_string) + offset); } -/* - * Compare functions for qsort() below, that checks the help heuristics number - * that has been put after the tagname by find_tags(). - */ +/// Compare functions for qsort() below, that checks the help heuristics number +/// that has been put after the tagname by find_tags(). static int help_compare(const void *s1, const void *s2) { char *p1; @@ -4987,10 +5017,10 @@ static int help_compare(const void *s1, const void *s2) return strcmp(p1, p2); } -// Find all help tags matching "arg", sort them and return in matches[], with -// the number of matches in num_matches. -// The matches will be sorted with a "best" match algorithm. -// When "keep_lang" is true try keeping the language of the current buffer. +/// Find all help tags matching "arg", sort them and return in matches[], with +/// the number of matches in num_matches. +/// The matches will be sorted with a "best" match algorithm. +/// When "keep_lang" is true try keeping the language of the current buffer. int find_help_tags(const char_u *arg, int *num_matches, char_u ***matches, bool keep_lang) { int i; @@ -5065,8 +5095,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, "\\$"); @@ -5263,10 +5292,8 @@ static void prepare_help_buffer(void) set_buflisted(FALSE); } -/* - * After reading a help file: May cleanup a help buffer when syntax - * highlighting is not used. - */ +/// After reading a help file: May cleanup a help buffer when syntax +/// highlighting is not used. void fix_help_buffer(void) { linenr_T lnum; @@ -5462,17 +5489,13 @@ void fix_help_buffer(void) } } -/* - * ":exusage" - */ +/// ":exusage" void ex_exusage(exarg_T *eap) { do_cmdline_cmd("help ex-cmd-index"); } -/* - * ":viusage" - */ +/// ":viusage" void ex_viusage(exarg_T *eap) { do_cmdline_cmd("help normal-index"); @@ -5781,9 +5804,7 @@ static void helptags_cb(char_u *fname, void *cookie) do_helptags(fname, *(bool *)cookie, true); } -/* - * ":helptags" - */ +/// ":helptags" void ex_helptags(exarg_T *eap) { expand_T xpc; @@ -5812,14 +5833,12 @@ void ex_helptags(exarg_T *eap) } } -/* - * ":helpclose": Close one help window - */ +/// ":helpclose": Close one help window 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; } } @@ -5828,7 +5847,7 @@ void ex_helpclose(exarg_T *eap) /// Tries to enter to an existing window of given buffer. If no existing buffer /// is found, creates a new split. /// -/// Returns OK/FAIL. +/// @return OK/FAIL. int sub_preview_win(buf_T *preview_buf) { if (preview_buf != NULL) { @@ -6070,9 +6089,11 @@ void ex_substitute(exarg_T *eap) /// Skip over the pattern argument of ":vimgrep /pat/[g][j]". /// Put the start of the pattern in "*s", unless "s" is NULL. -/// If "flags" is not NULL put the flags in it: VGR_GLOBAL, VGR_NOJUMP. -/// If "s" is not NULL terminate the pattern with a NUL. -/// Return a pointer to the char just past the pattern plus flags. +/// +/// @param flags if not NULL, put the flags in it: VGR_GLOBAL, VGR_NOJUMP. +/// @param s if not NULL, terminate the pattern with a NUL. +/// +/// @return a pointer to the char just past the pattern plus flags. char_u *skip_vimgrep_pat(char_u *p, char_u **s, int *flags) { int c; @@ -6104,12 +6125,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.h b/src/nvim/ex_cmds.h index 241674c24c..1c95b75001 100644 --- a/src/nvim/ex_cmds.h +++ b/src/nvim/ex_cmds.h @@ -21,7 +21,7 @@ // for lnum argument in do_ecmd() #define ECMD_LASTL (linenr_T)0 // use last position in loaded file -#define ECMD_LAST (linenr_T)-1 // use last position in all files +#define ECMD_LAST ((linenr_T)-1) // use last position in all files #define ECMD_ONE (linenr_T)1 // use first line /// Previous :substitute replacement string definition 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 449e6f7bf5..ac1d760bce 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> @@ -35,12 +36,12 @@ #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/message.h" -#include "nvim/misc1.h" #include "nvim/move.h" #include "nvim/normal.h" #include "nvim/ops.h" #include "nvim/option.h" #include "nvim/os/fs_defs.h" +#include "nvim/os/input.h" #include "nvim/os/shell.h" #include "nvim/os_unix.h" #include "nvim/path.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); @@ -450,8 +436,8 @@ static void script_dump_profile(FILE *fd) } } -/// Return true when a function defined in the current script should be -/// profiled. +/// @return true when a function defined in the current script should be +/// profiled. bool prof_def_func(void) { if (current_sctx.sc_sid > 0) { @@ -506,7 +492,7 @@ void autowrite_all(void) } } -/// Return true if buffer was changed and cannot be abandoned. +/// @return true if buffer was changed and cannot be abandoned. /// For flags use the CCGD_ values. bool check_changed(buf_T *buf, int flags) { @@ -630,7 +616,7 @@ bool dialog_close_terminal(buf_T *buf) return ret == VIM_YES; } -/// Return true if the buffer "buf" can be abandoned, either by making it +/// @return true if the buffer "buf" can be abandoned, either by making it /// hidden, autowriting it or unloading it. bool can_abandon(buf_T *buf, int forceit) { @@ -785,8 +771,8 @@ theend: return ret; } -/// Return FAIL if there is no file name, OK if there is one. -/// Give error message for FAIL. +/// @return FAIL if there is no file name, OK if there is one. +/// Give error message for FAIL. int check_fname(void) { if (curbuf->b_ffname == NULL) { @@ -798,7 +784,7 @@ int check_fname(void) /// Flush the contents of a buffer, unless it has no file name. /// -/// @return FAIL for failure, OK otherwise +/// @return FAIL for failure, OK otherwise int buf_write_all(buf_T *buf, int forceit) { int retval; @@ -822,7 +808,8 @@ int buf_write_all(buf_T *buf, int forceit) /// Isolate one argument, taking backticks. /// Changes the argument in-place, puts a NUL after it. Backticks remain. -/// Return a pointer to the start of the next argument. +/// +/// @return a pointer to the start of the next argument. static char_u *do_one_arg(char_u *str) { char_u *p; @@ -873,7 +860,7 @@ static void get_arglist(garray_T *gap, char_u *str, int escaped) /// Parse a list of arguments (file names), expand them and return in /// "fnames[fcountp]". When "wig" is true, removes files matching 'wildignore'. /// -/// @return FAIL or OK. +/// @return FAIL or OK. int get_arglist_exp(char_u *str, int *fcountp, char_u ***fnamesp, bool wig) { garray_T ga; @@ -903,7 +890,7 @@ int get_arglist_exp(char_u *str, int *fcountp, char_u ***fnamesp, bool wig) /// 0 means before first one /// @param will_edit will edit added argument /// -/// @return FAIL for failure, OK otherwise. +/// @return FAIL for failure, OK otherwise. static int do_arglist(char_u *str, int what, int after, bool will_edit) FUNC_ATTR_NONNULL_ALL { @@ -1002,8 +989,8 @@ static void alist_check_arg_idx(void) } } -/// Return true if window "win" is editing the file at the current argument -/// index. +/// @return true if window "win" is editing the file at the current argument +/// index. static bool editing_arg_idx(win_T *win) { return !(win->w_arg_idx >= WARGCOUNT(win) @@ -1483,7 +1470,13 @@ void ex_listdo(exarg_T *eap) size_t qf_idx = qf_get_cur_idx(eap); + // Clear 'shm' to avoid that the file message overwrites + // any output from the command. + p_shm_save = vim_strsave(p_shm); + set_option_value("shm", 0L, "", 0); ex_cnext(eap); + set_option_value("shm", 0L, (char *)p_shm_save, 0); + xfree(p_shm_save); // If jumping to the next quickfix entry fails, quit here. if (qf_get_cur_idx(eap) == qf_idx) { @@ -1613,7 +1606,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 +1653,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) { @@ -1832,7 +1705,7 @@ static bool concat_continued_line(garray_T *const ga, const int init_growsize, return false; } if (ga->ga_len > init_growsize) { - ga_set_growsize(ga, MAX(ga->ga_len, 8000)); + ga_set_growsize(ga, MIN(ga->ga_len, 8000)); } ga_concat_len(ga, (const char *)line + 1, len - 1); return true; @@ -1851,13 +1724,13 @@ linenr_T *source_breakpoint(void *cookie) return &((struct source_cookie *)cookie)->breakpoint; } -/// Return the address holding the debug tick for a source cookie. +/// @return the address holding the debug tick for a source cookie. int *source_dbg_tick(void *cookie) { return &((struct source_cookie *)cookie)->dbg_tick; } -/// Return the nesting level for a source cookie. +/// @return the nesting level for a source cookie. int source_level(void *cookie) { return ((struct source_cookie *)cookie)->level; @@ -1922,7 +1795,8 @@ static char_u *get_str_line(int c, void *cookie, int indent, bool do_concat) /// /// @param name File name of the script. NULL for anonymous :source. /// @param[out] sid_out SID of the new item. -/// @return pointer to the created script item. +/// +/// @return pointer to the created script item. scriptitem_T *new_script_item(char_u *const name, scid_T *const sid_out) { static scid_T last_current_SID = 0; @@ -1930,7 +1804,7 @@ scriptitem_T *new_script_item(char_u *const name, scid_T *const sid_out) if (sid_out != NULL) { *sid_out = sid; } - ga_grow(&script_items, (int)(sid - script_items.ga_len)); + ga_grow(&script_items, sid - script_items.ga_len); while (script_items.ga_len < sid) { script_items.ga_len++; SCRIPT_ITEM(script_items.ga_len).sn_name = NULL; @@ -1957,7 +1831,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; @@ -1984,7 +1860,7 @@ static void cmd_source_buffer(const exarg_T *const eap) for (linenr_T curr_lnum = eap->line1; curr_lnum <= final_lnum; curr_lnum++) { // Adjust growsize to current length to speed up concatenating many lines. if (ga.ga_len > 400) { - ga_set_growsize(&ga, MAX(ga.ga_len, 8000)); + ga_set_growsize(&ga, MIN(ga.ga_len, 8000)); } ga_concat(&ga, (char *)ml_get(curr_lnum)); ga_append(&ga, NL); @@ -2028,7 +1904,7 @@ int do_source_str(const char *cmd, const char *traceback_name) /// @param check_other check for .vimrc and _vimrc /// @param is_vimrc DOSO_ value /// -/// @return FAIL if file could not be opened, OK otherwise +/// @return FAIL if file could not be opened, OK otherwise int do_source(char *fname, int check_other, int is_vimrc) { struct source_cookie cookie; @@ -2038,7 +1914,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 +1961,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 +2037,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 +2071,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 +2151,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 +2213,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(); + } } } } @@ -2729,9 +2623,9 @@ void do_finish(exarg_T *eap, int reanimate) } -/// Return true when a sourced file had the ":finish" command: Don't give error -/// message for missing ":endif". -/// Return false when not sourcing a file. +/// @return true when a sourced file had the ":finish" command: Don't give error +/// message for missing ":endif". +/// false when not sourcing a file. bool source_finished(LineGetter fgetline, void *cookie) { return getline_equal(fgetline, cookie, getsourceline) @@ -2768,8 +2662,8 @@ static char *get_locale_val(int what) } #endif -// Return true when "lang" starts with a valid language name. -// Rejects NULL, empty string, "C", "C.UTF-8" and others. +/// @return true when "lang" starts with a valid language name. +/// Rejects NULL, empty string, "C", "C.UTF-8" and others. static bool is_valid_mess_lang(char *lang) { return lang != NULL && ASCII_ISALPHA(lang[0]) && ASCII_ISALPHA(lang[1]); @@ -2872,11 +2766,10 @@ void set_lang_var(void) } #ifdef HAVE_WORKING_LIBINTL -/// + /// ":language": Set the language (locale). /// /// @param eap -/// void ex_language(exarg_T *eap) { char *loc; @@ -2985,8 +2878,9 @@ static char_u **locales = NULL; // Array of all available locales # ifndef WIN32 static bool did_init_locales = false; -/// Return an array of strings for all available locales + NULL for the -/// last element. Return NULL in case of error. +/// @return an array of strings for all available locales + NULL for the +/// last element or, +/// NULL in case of error. static char_u **find_locales(void) { garray_T locales_ga; diff --git a/src/nvim/ex_cmds_defs.h b/src/nvim/ex_cmds_defs.h index ea899b660b..39dfc1b94c 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 @@ -85,11 +87,40 @@ typedef struct exarg exarg_T; // behavior for bad character, "++bad=" argument #define BAD_REPLACE '?' // replace it with '?' (default) -#define BAD_KEEP -1 // leave it -#define BAD_DROP -2 // erase it +#define BAD_KEEP (-1) // leave it +#define BAD_DROP (-2) // erase it 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 34c3b3889e..cbfe6e3789 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -38,17 +38,19 @@ #include "nvim/getchar.h" #include "nvim/globals.h" #include "nvim/hardcopy.h" +#include "nvim/highlight_group.h" #include "nvim/if_cscope.h" +#include "nvim/input.h" #include "nvim/keymap.h" #include "nvim/lua/executor.h" #include "nvim/main.h" +#include "nvim/match.h" #include "nvim/mark.h" #include "nvim/mbyte.h" #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/menu.h" #include "nvim/message.h" -#include "nvim/misc1.h" #include "nvim/mouse.h" #include "nvim/move.h" #include "nvim/normal.h" @@ -67,6 +69,7 @@ #include "nvim/sign.h" #include "nvim/spell.h" #include "nvim/spellfile.h" +#include "nvim/state.h" #include "nvim/strings.h" #include "nvim/syntax.h" #include "nvim/tag.h" @@ -77,26 +80,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) @@ -196,6 +187,7 @@ void do_exmode(void) exmode_active = true; State = NORMAL; + may_trigger_modechanged(); // When using ":global /pat/ visual" and then "Q" we return to continue // the :global command. @@ -259,8 +251,9 @@ void do_exmode(void) msg_scroll = save_msg_scroll; } -// Print the executed command for when 'verbose' is set. -// When "lnum" is 0 only print the command. +/// Print the executed command for when 'verbose' is set. +/// +/// @param lnum if 0, only print the command. static void msg_verbose_cmd(linenr_T lnum, char_u *cmd) FUNC_ATTR_NONNULL_ALL { @@ -280,9 +273,7 @@ static void msg_verbose_cmd(linenr_T lnum, char_u *cmd) no_wait_return--; } -/* - * Execute a simple command line. Used for translated commands like "*". - */ +/// Execute a simple command line. Used for translated commands like "*". int do_cmdline_cmd(const char *cmd) { return do_cmdline((char_u *)cmd, NULL, NULL, @@ -434,7 +425,7 @@ int do_cmdline(char_u *cmdline, LineGetter fgetline, void *cookie, int flags) && cstack.cs_idx < 0 && !(getline_is_func && func_has_abort(real_cookie))) { - did_emsg = FALSE; + did_emsg = false; } /* @@ -818,7 +809,7 @@ int do_cmdline(char_u *cmdline, LineGetter fgetline, void *cookie, int flags) // of interrupts or errors to exceptions, and ensure that no more // commands are executed. if (current_exception) { - void *p = NULL; + char *p = NULL; char_u *saved_sourcing_name; int saved_sourcing_lnum; struct msglist *messages = NULL; @@ -835,7 +826,7 @@ int do_cmdline(char_u *cmdline, LineGetter fgetline, void *cookie, int flags) vim_snprintf((char *)IObuff, IOSIZE, _("E605: Exception not caught: %s"), current_exception->value); - p = vim_strsave(IObuff); + p = (char *)vim_strsave(IObuff); break; case ET_ERROR: messages = current_exception->messages; @@ -961,9 +952,7 @@ int do_cmdline(char_u *cmdline, LineGetter fgetline, void *cookie, int flags) return retval; } -/* - * Obtain a line when inside a ":while" or ":for" loop. - */ +/// Obtain a line when inside a ":while" or ":for" loop. static char_u *get_loop_line(int c, void *cookie, int indent, bool do_concat) { struct loop_cookie *cp = (struct loop_cookie *)cookie; @@ -995,9 +984,7 @@ static char_u *get_loop_line(int c, void *cookie, int indent, bool do_concat) return vim_strsave(wp->line); } -/* - * Store a line in "gap" so that a ":while" loop can execute it again. - */ +/// Store a line in "gap" so that a ":while" loop can execute it again. static void store_loop_line(garray_T *gap, char_u *line) { wcmd_T *p = GA_APPEND_VIA_PTR(wcmd_T, gap); @@ -1047,11 +1034,10 @@ void *getline_cookie(LineGetter fgetline, void *cookie) return cp; } -/* - * Helper function to apply an offset for buffer commands, i.e. ":bdelete", - * ":bwipeout", etc. - * Returns the buffer number. - */ +/// Helper function to apply an offset for buffer commands, i.e. ":bdelete", +/// ":bwipeout", etc. +/// +/// @return the buffer number. static int compute_buffer_local_count(int addr_type, int lnum, int offset) { buf_T *buf; @@ -1093,8 +1079,8 @@ static int compute_buffer_local_count(int addr_type, int lnum, int offset) return buf->b_fnum; } -// Return the window number of "win". -// When "win" is NULL return the number of windows. +/// @return the window number of "win" or, +/// the number of windows if "win" is NULL static int current_win_nr(const win_T *win) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { @@ -1262,6 +1248,7 @@ static char_u *do_one_cmd(char_u **cmdlinep, int flags, cstack_T *cstack, LineGe const int save_msg_scroll = msg_scroll; cmdmod_T save_cmdmod; const int save_reg_executing = reg_executing; + const bool save_pending_end_reg_executing = pending_end_reg_executing; char_u *cmd; memset(&ea, 0, sizeof(ea)); @@ -1772,7 +1759,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); @@ -2030,6 +2019,7 @@ doend: undo_cmdmod(&ea, save_msg_scroll); cmdmod = save_cmdmod; reg_executing = save_reg_executing; + pending_end_reg_executing = save_pending_end_reg_executing; if (ea.did_sandbox) { sandbox--; @@ -2044,18 +2034,32 @@ doend: return ea.nextcmd; } -// Parse and skip over command modifiers: -// - update eap->cmd -// - store flags in "cmdmod". -// - Set ex_pressedreturn for an empty command line. -// - set msg_silent for ":silent" -// - set 'eventignore' to "all" for ":noautocmd" -// - set p_verbose for ":verbose" -// - Increment "sandbox" for ":sandbox" -// When "skip_only" is true the global variables are not changed, except for -// "cmdmod". -// Return FAIL when the command is not to be executed. -// May set "errormsg" to an error message. +static char ex_error_buf[MSG_BUF_LEN]; + +/// @return an error message with argument included. +/// Uses a static buffer, only the last error will be kept. +/// "msg" will be translated, caller should use N_(). +char *ex_errmsg(const char *const msg, const char_u *const arg) + FUNC_ATTR_NONNULL_ALL +{ + vim_snprintf(ex_error_buf, MSG_BUF_LEN, _(msg), arg); + return ex_error_buf; +} + +/// Parse and skip over command modifiers: +/// - update eap->cmd +/// - store flags in "cmdmod". +/// - Set ex_pressedreturn for an empty command line. +/// - set msg_silent for ":silent" +/// - set 'eventignore' to "all" for ":noautocmd" +/// - set p_verbose for ":verbose" +/// - Increment "sandbox" for ":sandbox" +/// +/// @param skip_only if true, the global variables are not changed, except for +/// "cmdmod". +/// @param[out] errormsg potential error message. +/// +/// @return FAIL when the command is not to be executed. int parse_command_modifiers(exarg_T *eap, char **errormsg, bool skip_only) { char_u *p; @@ -2314,7 +2318,7 @@ int parse_command_modifiers(exarg_T *eap, char **errormsg, bool skip_only) return OK; } -// Undo and free contents of "cmdmod". +/// Undo and free contents of "cmdmod". static void undo_cmdmod(const exarg_T *eap, int save_msg_scroll) FUNC_ATTR_NONNULL_ALL { @@ -2353,9 +2357,10 @@ static void undo_cmdmod(const exarg_T *eap, int save_msg_scroll) } -// Parse the address range, if any, in "eap". -// May set the last search pattern, unless "silent" is true. -// Return FAIL and set "errormsg" or return OK. +/// Parse the address range, if any, in "eap". +/// May set the last search pattern, unless "silent" is true. +/// +/// @return FAIL and set "errormsg" or return OK. int parse_cmd_address(exarg_T *eap, char **errormsg, bool silent) FUNC_ATTR_NONNULL_ALL { @@ -2547,11 +2552,9 @@ int checkforcmd(char_u **pp, char *cmd, int len) return FALSE; } -/* - * Append "cmd" to the error message in IObuff. - * Takes care of limiting the length and handling 0xa0, which would be - * invisible otherwise. - */ +/// Append "cmd" to the error message in IObuff. +/// Takes care of limiting the length and handling 0xa0, which would be +/// invisible otherwise. static void append_command(char_u *cmd) { char_u *s = cmd; @@ -2571,11 +2574,12 @@ static void append_command(char_u *cmd) *d = NUL; } -// Find an Ex command by its name, either built-in or user. -// Start of the name can be found at eap->cmd. -// Sets eap->cmdidx and returns a pointer to char after the command name. -// "full" is set to TRUE if the whole command name matched. -// Returns NULL for an ambiguous user command. +/// Find an Ex command by its name, either built-in or user. +/// Start of the name can be found at eap->cmd. +/// Sets eap->cmdidx and returns a pointer to char after the command name. +/// "full" is set to TRUE if the whole command name matched. +/// +/// @return NULL for an ambiguous user command. static char_u *find_command(exarg_T *eap, int *full) FUNC_ATTR_NONNULL_ARG(1) { @@ -2711,12 +2715,10 @@ 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 = &prevwin_curwin()->w_buffer->b_ucmds; for (;;) { - for (j = 0; j < gap->ga_len; ++j) { + for (j = 0; j < gap->ga_len; j++) { uc = USER_CMD_GA(gap, j); cp = eap->cmd; np = uc->uc_name; @@ -2759,6 +2761,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; @@ -2830,10 +2833,8 @@ static struct cmdmod { { "vertical", 4, false }, }; -/* - * Return length of a command modifier (including optional count). - * Return zero when it's not a modifier. - */ +/// @return length of a command modifier (including optional count) or, +/// zero when it's not a modifier. int modifier_len(char_u *cmd) { char_u *p = cmd; @@ -2844,7 +2845,7 @@ int modifier_len(char_u *cmd) for (int i = 0; i < (int)ARRAY_SIZE(cmdmods); i++) { int j; for (j = 0; p[j] != NUL; j++) { - if (p[j] != cmdmods[i].name[j]) { + if (p[j] != (char_u)cmdmods[i].name[j]) { break; } } @@ -2857,11 +2858,9 @@ int modifier_len(char_u *cmd) return 0; } -/* - * Return > 0 if an Ex command "name" exists. - * Return 2 if there is an exact match. - * Return 3 if there is an ambiguous match. - */ +/// @return > 0 if an Ex command "name" exists or, +/// 2 if there is an exact match or, +/// 3 if there is an ambiguous match. int cmd_exists(const char *const name) { exarg_T ea; @@ -2898,6 +2897,35 @@ int cmd_exists(const char *const name) return ea.cmdidx == CMD_SIZE ? 0 : (full ? 2 : 1); } +/// "fullcommand" function +void f_fullcommand(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + exarg_T ea; + char_u *name = argvars[0].vval.v_string; + + rettv->v_type = VAR_STRING; + rettv->vval.v_string = NULL; + if (name == NULL) { + return; + } + + while (*name == ':') { + name++; + } + name = skip_range(name, NULL); + + ea.cmd = (*name == '2' || *name == '3') ? name + 1 : name; + ea.cmdidx = (cmdidx_T)0; + char_u *p = find_command(&ea, NULL); + if (p == NULL || ea.cmdidx == CMD_SIZE) { + return; + } + + rettv->vval.v_string = vim_strsave(IS_USER_CMDIDX(ea.cmdidx) + ? get_user_command_name(ea.useridx, ea.cmdidx) + : cmdnames[ea.cmdidx].cmd_name); +} + /// This is all pretty much copied from do_one_cmd(), with all the extra stuff /// we don't need/want deleted. Maybe this could be done better if we didn't /// repeat all this stuff. The only problem is that they may not stay @@ -4001,8 +4029,9 @@ static linenr_T get_address(exarg_T *eap, char_u **ptr, cmd_addr_T addr_type, in // When '/' or '?' follows another address, start from // there. - if (lnum != MAXLNUM) { - curwin->w_cursor.lnum = lnum; + if (lnum > 0 && lnum != MAXLNUM) { + curwin->w_cursor.lnum + = lnum > curbuf->b_ml.ml_line_count ? curbuf->b_ml.ml_line_count : lnum; } // Start a forward search at the end of the line (unless @@ -4121,7 +4150,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) { @@ -4140,6 +4173,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; } } @@ -4151,9 +4188,7 @@ error: return lnum; } -/* - * Get flags from an Ex command argument. - */ +/// Get flags from an Ex command argument. static void get_flags(exarg_T *eap) { while (vim_strchr((char_u *)"lp#", *eap->arg) != NULL) { @@ -4188,10 +4223,9 @@ static void ex_script_ni(exarg_T *eap) } } -/* - * Check range in Ex command for validity. - * Return NULL when valid, error message when invalid. - */ +/// Check range in Ex command for validity. +/// +/// @return NULL when valid, error message when invalid. static char *invalid_range(exarg_T *eap) { buf_T *buf; @@ -4277,9 +4311,7 @@ static char *invalid_range(exarg_T *eap) return NULL; } -/* - * Correct the range for zero line number, if required. - */ +/// Correct the range for zero line number, if required. static void correct_range(exarg_T *eap) { if (!(eap->argt & EX_ZEROR)) { // zero in range not allowed @@ -4293,10 +4325,8 @@ static void correct_range(exarg_T *eap) } -/* - * For a ":vimgrep" or ":vimgrepadd" command return a pointer past the - * pattern. Otherwise return eap->arg. - */ +/// For a ":vimgrep" or ":vimgrepadd" command return a pointer past the +/// pattern. Otherwise return eap->arg. static char_u *skip_grep_pat(exarg_T *eap) { char_u *p = eap->arg; @@ -4313,10 +4343,8 @@ static char_u *skip_grep_pat(exarg_T *eap) return p; } -/* - * For the ":make" and ":grep" commands insert the 'makeprg'/'grepprg' option - * in the command line, so that things like % get expanded. - */ +/// For the ":make" and ":grep" commands insert the 'makeprg'/'grepprg' option +/// in the command line, so that things like % get expanded. static char_u *replace_makeprg(exarg_T *eap, char_u *p, char_u **cmdlinep) { char_u *new_cmdline; @@ -4358,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); @@ -4384,9 +4412,10 @@ static char_u *replace_makeprg(exarg_T *eap, char_u *p, char_u **cmdlinep) return p; } -// Expand file name in Ex command argument. -// When an error is detected, "errormsgp" is set to a non-NULL pointer. -// Return FAIL for failure, OK otherwise. +/// Expand file name in Ex command argument. +/// When an error is detected, "errormsgp" is set to a non-NULL pointer. +/// +/// @return FAIL for failure, OK otherwise. int expand_filename(exarg_T *eap, char_u **cmdlinep, char **errormsgp) { int has_wildcards; // need to expand wildcards @@ -4554,13 +4583,12 @@ int expand_filename(exarg_T *eap, char_u **cmdlinep, char **errormsgp) return OK; } -/* - * Replace part of the command line, keeping eap->cmd, eap->arg and - * eap->nextcmd correct. - * "src" points to the part that is to be replaced, of length "srclen". - * "repl" is the replacement string. - * Returns a pointer to the character after the replaced string. - */ +/// Replace part of the command line, keeping eap->cmd, eap->arg and +/// eap->nextcmd correct. +/// "src" points to the part that is to be replaced, of length "srclen". +/// "repl" is the replacement string. +/// +/// @return a pointer to the character after the replaced string. static char_u *repl_cmdline(exarg_T *eap, char_u *src, size_t srclen, char_u *repl, char_u **cmdlinep) { @@ -4606,9 +4634,7 @@ static char_u *repl_cmdline(exarg_T *eap, char_u *src, size_t srclen, char_u *re return src; } -/* - * Check for '|' to separate commands and '"' to start comments. - */ +/// Check for '|' to separate commands and '"' to start comments. void separate_nextcmd(exarg_T *eap) { char_u *p; @@ -4663,9 +4689,7 @@ void separate_nextcmd(exarg_T *eap) } } -/* - * get + command from ex argument - */ +/// get + command from ex argument static char_u *getargcmd(char_u **argp) { char_u *arg = *argp; @@ -4722,10 +4746,9 @@ int get_bad_opt(const char_u *p, exarg_T *eap) return OK; } -/* - * Get "++opt=arg" argument. - * Return FAIL or OK. - */ +/// Get "++opt=arg" argument. +/// +/// @return FAIL or OK. static int getargopt(exarg_T *eap) { char_u *arg = eap->arg + 2; @@ -4805,8 +4828,9 @@ static int getargopt(exarg_T *eap) } /// Handle the argument for a tabpage related ex command. -/// Returns a tabpage number. /// When an error is encountered then eap->errmsg is set. +/// +/// @return a tabpage number. static int get_tabpage_arg(exarg_T *eap) { int tab_number = 0; @@ -4833,7 +4857,13 @@ static int get_tabpage_arg(exarg_T *eap) if (STRCMP(p, "$") == 0) { tab_number = LAST_TAB_NR; } else if (STRCMP(p, "#") == 0) { - tab_number = tabpage_index(lastused_tabpage); + if (valid_tabpage(lastused_tabpage)) { + tab_number = tabpage_index(lastused_tabpage); + } else { + eap->errmsg = ex_errmsg(e_invargval, eap->arg); + tab_number = 0; + goto theend; + } } else if (p == p_save || *p_save == '-' || *p != NUL || tab_number > LAST_TAB_NR) { // No numbers as argument. @@ -4889,17 +4919,13 @@ theend: return tab_number; } -/* - * ":abbreviate" and friends. - */ +/// ":abbreviate" and friends. static void ex_abbreviate(exarg_T *eap) { do_exmap(eap, TRUE); // almost the same as mapping } -/* - * ":map" and friends. - */ +/// ":map" and friends. static void ex_map(exarg_T *eap) { /* @@ -4914,25 +4940,19 @@ static void ex_map(exarg_T *eap) do_exmap(eap, FALSE); } -/* - * ":unmap" and friends. - */ +/// ":unmap" and friends. static void ex_unmap(exarg_T *eap) { do_exmap(eap, FALSE); } -/* - * ":mapclear" and friends. - */ +/// ":mapclear" and friends. static void ex_mapclear(exarg_T *eap) { map_clear_mode(eap->cmd, eap->arg, eap->forceit, false); } -/* - * ":abclear" and friends. - */ +/// ":abclear" and friends. static void ex_abclear(exarg_T *eap) { map_clear_mode(eap->cmd, eap->arg, true, true); @@ -4952,9 +4972,7 @@ static void ex_autocmd(exarg_T *eap) } } -/* - * ":doautocmd": Apply the automatic commands to the current buffer. - */ +/// ":doautocmd": Apply the automatic commands to the current buffer. static void ex_doautocmd(exarg_T *eap) { char_u *arg = eap->arg; @@ -4968,11 +4986,9 @@ static void ex_doautocmd(exarg_T *eap) } } -/* - * :[N]bunload[!] [N] [bufname] unload buffer - * :[N]bdelete[!] [N] [bufname] delete buffer from buffer list - * :[N]bwipeout[!] [N] [bufname] delete buffer really - */ +/// :[N]bunload[!] [N] [bufname] unload buffer +/// :[N]bdelete[!] [N] [bufname] delete buffer from buffer list +/// :[N]bwipeout[!] [N] [bufname] delete buffer really static void ex_bunload(exarg_T *eap) { eap->errmsg = do_bufdel(eap->cmdidx == CMD_bdelete ? DOBUF_DEL @@ -4982,10 +4998,8 @@ static void ex_bunload(exarg_T *eap) eap->addr_count, (int)eap->line1, (int)eap->line2, eap->forceit); } -/* - * :[N]buffer [N] to buffer N - * :[N]sbuffer [N] to buffer N - */ +/// :[N]buffer [N] to buffer N +/// :[N]sbuffer [N] to buffer N static void ex_buffer(exarg_T *eap) { if (*eap->arg) { @@ -5002,10 +5016,8 @@ static void ex_buffer(exarg_T *eap) } } -/* - * :[N]bmodified [N] to next mod. buffer - * :[N]sbmodified [N] to next mod. buffer - */ +/// :[N]bmodified [N] to next mod. buffer +/// :[N]sbmodified [N] to next mod. buffer static void ex_bmodified(exarg_T *eap) { goto_buffer(eap, DOBUF_MOD, FORWARD, (int)eap->line2); @@ -5014,10 +5026,8 @@ static void ex_bmodified(exarg_T *eap) } } -/* - * :[N]bnext [N] to next buffer - * :[N]sbnext [N] split and to next buffer - */ +/// :[N]bnext [N] to next buffer +/// :[N]sbnext [N] split and to next buffer static void ex_bnext(exarg_T *eap) { goto_buffer(eap, DOBUF_CURRENT, FORWARD, (int)eap->line2); @@ -5026,12 +5036,10 @@ static void ex_bnext(exarg_T *eap) } } -/* - * :[N]bNext [N] to previous buffer - * :[N]bprevious [N] to previous buffer - * :[N]sbNext [N] split and to previous buffer - * :[N]sbprevious [N] split and to previous buffer - */ +/// :[N]bNext [N] to previous buffer +/// :[N]bprevious [N] to previous buffer +/// :[N]sbNext [N] split and to previous buffer +/// :[N]sbprevious [N] split and to previous buffer static void ex_bprevious(exarg_T *eap) { goto_buffer(eap, DOBUF_CURRENT, BACKWARD, (int)eap->line2); @@ -5040,12 +5048,10 @@ static void ex_bprevious(exarg_T *eap) } } -/* - * :brewind to first buffer - * :bfirst to first buffer - * :sbrewind split and to first buffer - * :sbfirst split and to first buffer - */ +/// :brewind to first buffer +/// :bfirst to first buffer +/// :sbrewind split and to first buffer +/// :sbfirst split and to first buffer static void ex_brewind(exarg_T *eap) { goto_buffer(eap, DOBUF_FIRST, FORWARD, 0); @@ -5054,10 +5060,8 @@ static void ex_brewind(exarg_T *eap) } } -/* - * :blast to last buffer - * :sblast split and to last buffer - */ +/// :blast to last buffer +/// :sblast split and to last buffer static void ex_blast(exarg_T *eap) { goto_buffer(eap, DOBUF_LAST, BACKWARD, 0); @@ -5071,10 +5075,8 @@ int ends_excmd(int c) FUNC_ATTR_CONST return c == NUL || c == '|' || c == '"' || c == '\n'; } -/* - * Return the next command, after the first '|' or '\n'. - * Return NULL if not found. - */ +/// @return the next command, after the first '|' or '\n' or, +/// NULL if not found. char_u *find_nextcmd(const char_u *p) { while (*p != '|' && *p != '\n') { @@ -5087,7 +5089,8 @@ char_u *find_nextcmd(const char_u *p) } /// Check if *p is a separator between Ex commands, skipping over white space. -/// Return NULL if it isn't, the following character if it is. +/// +/// @return NULL if it isn't, the following character if it is. char_u *check_nextcmd(char_u *p) { char_u *s = skipwhite(p); @@ -5103,9 +5106,10 @@ char_u *check_nextcmd(char_u *p) /// - and this is the last window /// - and forceit not used /// - and not repeated twice on a row -/// @return FAIL and give error message if 'message' TRUE, return OK otherwise /// /// @param message when FALSE check only, no messages +/// +/// @return FAIL and give error message if 'message' TRUE, return OK otherwise static int check_more(int message, bool forceit) { int n = ARGCOUNT - curwin->w_arg_idx - 1; @@ -5133,19 +5137,36 @@ static int check_more(int message, bool forceit) return OK; } -/* - * Function given to ExpandGeneric() to obtain the list of command names. - */ +/// Function given to ExpandGeneric() to obtain the list of command names. 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; @@ -5199,6 +5220,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; } @@ -5228,14 +5251,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; } @@ -5274,6 +5302,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", @@ -5323,11 +5352,9 @@ 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; + const garray_T *gap = &prevwin_curwin()->w_buffer->b_ucmds; for (;;) { - for (i = 0; i < gap->ga_len; ++i) { + for (i = 0; i < gap->ga_len; i++) { cmd = USER_CMD_GA(gap, i); a = cmd->uc_argt; @@ -5497,6 +5524,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 { @@ -5619,9 +5648,7 @@ invalid_count: static char e_complete_used_without_nargs[] = N_("E1208: -complete used without -nargs"); -/* - * ":command ..." - */ +/// ":command ..." static void ex_command(exarg_T *eap) { char_u *name; @@ -5651,23 +5678,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) { @@ -5675,31 +5697,29 @@ 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); } } -/* - * ":comclear" - * Clear all user commands, global and for current buffer. - */ +/// ":comclear" +/// Clear all user commands, global and for current buffer. void ex_comclear(exarg_T *eap) { uc_clear(&ucmds); 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); } -/* - * Clear all user commands for "gap". - */ +/// Clear all user commands for "gap". void uc_clear(garray_T *gap) { GA_DEEP_CLEAR(gap, ucmd_T, free_ucmd); @@ -5709,32 +5729,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) { + 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; @@ -5743,9 +5771,50 @@ static void ex_delcommand(exarg_T *eap) } } -/* - * split and quote args for <f-args> - */ +/// 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. +/// +/// @param[in] arg String to split +/// @param[in] arglen Length of {arg} +/// @param[inout] end Index of last character of previous iteration +/// @param[out] buf Buffer to copy string into +/// @param[out] len Length of string in {buf} +/// +/// @return true if iteration is complete, else false +bool uc_split_args_iter(const char_u *arg, size_t arglen, size_t *end, char *buf, size_t *len) +{ + if (!arglen) { + return true; + } + + size_t pos = *end; + while (pos < arglen && ascii_iswhite(arg[pos])) { + pos++; + } + + size_t l = 0; + for (; pos < arglen - 1; pos++) { + if (arg[pos] == '\\' && (arg[pos + 1] == '\\' || ascii_iswhite(arg[pos + 1]))) { + buf[l++] = arg[++pos]; + } else { + buf[l++] = arg[pos]; + if (ascii_iswhite(arg[pos + 1])) { + *end = pos + 1; + *len = l; + return false; + } + } + } + + if (pos < arglen && !ascii_iswhite(arg[pos])) { + buf[l++] = arg[pos]; + } + + *len = l; + return true; +} + +/// split and quote args for <f-args> static char_u *uc_split_args(char_u *arg, size_t *lenp) { char_u *buf; @@ -5816,7 +5885,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) { @@ -6008,7 +6077,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) { @@ -6017,76 +6086,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? - - // :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? + result += uc_mods((char *)buf); - // :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; @@ -6125,6 +6131,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; @@ -6139,7 +6215,6 @@ static void do_ucmd(exarg_T *eap) size_t split_len = 0; char_u *split_buf = NULL; ucmd_T *cmd; - const sctx_T save_current_sctx = current_sctx; if (eap->cmdidx == CMD_USER) { cmd = USER_CMD(eap->useridx); @@ -6147,6 +6222,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". @@ -6172,7 +6252,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); @@ -6225,36 +6304,42 @@ static void do_ucmd(exarg_T *eap) buf = xmalloc(totlen + 1); } - current_sctx.sc_sid = cmd->uc_script_ctx.sc_sid; + sctx_T save_current_sctx; + bool restore_current_sctx = false; + if ((cmd->uc_argt & EX_KEEPSCRIPT) == 0) { + restore_current_sctx = true; + save_current_sctx = current_sctx; + 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; + + // Careful: Do not use "cmd" here, it may have become invalid if a user + // command was added. + if (restore_current_sctx) { + 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); } -/* - * Function given to ExpandGeneric() to obtain the list of user address type names. - */ + +/// Function given to ExpandGeneric() to obtain the list of user address type names. char_u *get_user_cmd_addr_type(expand_T *xp, int idx) { return (char_u *)addr_type_complete[idx].name; } -/* - * Function given to ExpandGeneric() to obtain the list of user command names. - */ +/// Function given to ExpandGeneric() to obtain the list of user command names. 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 = prevwin_curwin()->w_buffer; if (idx < buf->b_ucmds.ga_len) { return USER_CMD_GA(&buf->b_ucmds, idx)->uc_name; @@ -6266,15 +6351,33 @@ char_u *get_user_commands(expand_T *xp FUNC_ATTR_UNUSED, int idx) return NULL; } -/* - * Function given to ExpandGeneric() to obtain the list of user command - * attributes. - */ +/// Get the name of user command "idx". "cmdidx" can be CMD_USER or +/// CMD_USER_BUF. +/// +/// @return 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. + const buf_T *const buf = prevwin_curwin()->w_buffer; + + 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. 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; @@ -6282,9 +6385,7 @@ char_u *get_user_cmd_flags(expand_T *xp, int idx) return (char_u *)user_cmd_flags[idx]; } -/* - * Function given to ExpandGeneric() to obtain the list of values for -nargs. - */ +/// Function given to ExpandGeneric() to obtain the list of values for -nargs. char_u *get_user_cmd_nargs(expand_T *xp, int idx) { static char *user_cmd_nargs[] = { "0", "1", "*", "?", "+" }; @@ -6295,9 +6396,7 @@ char_u *get_user_cmd_nargs(expand_T *xp, int idx) return (char_u *)user_cmd_nargs[idx]; } -/* - * Function given to ExpandGeneric() to obtain the list of values for -complete. - */ +/// Function given to ExpandGeneric() to obtain the list of values for -complete. char_u *get_user_cmd_complete(expand_T *xp, int idx) { if (idx >= (int)ARRAY_SIZE(command_complete)) { @@ -6311,9 +6410,7 @@ char_u *get_user_cmd_complete(expand_T *xp, int idx) } } -/* - * Parse address type argument - */ +/// Parse address type argument int parse_addr_type_arg(char_u *value, int vallen, cmd_addr_T *addr_type_arg) FUNC_ATTR_NONNULL_ALL { @@ -6340,13 +6437,12 @@ int parse_addr_type_arg(char_u *value, int vallen, cmd_addr_T *addr_type_arg) return OK; } -/* - * Parse a completion argument "value[vallen]". - * The detected completion goes in "*complp", argument type in "*argt". - * When there is an argument, for function and user defined completion, it's - * copied to allocated memory and stored in "*compl_arg". - * Returns FAIL if something is wrong. - */ +/// Parse a completion argument "value[vallen]". +/// The detected completion goes in "*complp", argument type in "*argt". +/// When there is an argument, for function and user defined completion, it's +/// copied to allocated memory and stored in "*compl_arg". +/// +/// @return FAIL if something is wrong. int parse_compl_arg(const char_u *value, int vallen, int *complp, uint32_t *argt, char_u **compl_arg) FUNC_ATTR_NONNULL_ALL @@ -6451,10 +6547,8 @@ static void ex_highlight(exarg_T *eap) } -/* - * Call this function if we thought we were going to exit, but we won't - * (because of an error). May need to restore the terminal mode. - */ +/// Call this function if we thought we were going to exit, but we won't +/// (because of an error). May need to restore the terminal mode. void not_exiting(void) { exiting = false; @@ -6489,8 +6583,8 @@ bool before_quit_autocmds(win_T *wp, bool quit_all, bool forceit) return false; } -// ":quit": quit current window, quit Vim if the last window is closed. -// ":{nr}quit": quit window {nr} +/// ":quit": quit current window, quit Vim if the last window is closed. +/// ":{nr}quit": quit window {nr} static void ex_quit(exarg_T *eap) { if (cmdwin_type != 0) { @@ -6550,12 +6644,13 @@ 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); } } /// ":cquit". static void ex_cquit(exarg_T *eap) + FUNC_ATTR_NORETURN { // this does not always pass on the exit code to the Manx compiler. why? getout(eap->addr_count > 0 ? (int)eap->line2 : EXIT_FAILURE); @@ -6590,9 +6685,7 @@ static void ex_quit_all(exarg_T *eap) not_exiting(); } -/* - * ":close": close current window, unless it is the last one - */ +/// ":close": close current window, unless it is the last one static void ex_close(exarg_T *eap) { win_T *win = NULL; @@ -6618,9 +6711,7 @@ static void ex_close(exarg_T *eap) } } -/* - * ":pclose": Close any preview window. - */ +/// ":pclose": Close any preview window. static void ex_pclose(exarg_T *eap) { FOR_ALL_WINDOWS_IN_TAB(win, curtab) { @@ -6665,16 +6756,14 @@ 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); } } -/* - * ":tabclose": close current tab page, unless it is the last one. - * ":tabclose N": close tab page N. - */ +/// ":tabclose": close current tab page, unless it is the last one. +/// ":tabclose N": close tab page N. static void ex_tabclose(exarg_T *eap) { tabpage_T *tp; @@ -6735,9 +6824,7 @@ static void ex_tabonly(exarg_T *eap) } } -/* - * Close the current tab page. - */ +/// Close the current tab page. void tabpage_close(int forceit) { // First close all the windows but the current one. If that worked then @@ -6753,12 +6840,10 @@ void tabpage_close(int forceit) } } -/* - * Close tab page "tp", which is not the current tab page. - * Note that autocommands may make "tp" invalid. - * Also takes care of the tab pages line disappearing when closing the - * last-but-one tab page. - */ +/// Close tab page "tp", which is not the current tab page. +/// Note that autocommands may make "tp" invalid. +/// Also takes care of the tab pages line disappearing when closing the +/// last-but-one tab page. void tabpage_close_other(tabpage_T *tp, int forceit) { int done = 0; @@ -6775,7 +6860,7 @@ void tabpage_close_other(tabpage_T *tp, int forceit) // Autocommands may delete the tab page under our fingers and we may // fail to close a window with a modified buffer. - if (!valid_tabpage(tp) || tp->tp_firstwin == wp) { + if (!valid_tabpage(tp) || tp->tp_lastwin == wp) { break; } } @@ -6786,9 +6871,7 @@ void tabpage_close_other(tabpage_T *tp, int forceit) } } -/* - * ":only". - */ +/// ":only". static void ex_only(exarg_T *eap) { win_T *wp; @@ -6812,10 +6895,8 @@ static void ex_only(exarg_T *eap) close_others(TRUE, eap->forceit); } -/* - * ":all" and ":sall". - * Also used for ":tab drop file ..." after setting the argument list. - */ +/// ":all" and ":sall". +/// Also used for ":tab drop file ..." after setting the argument list. void ex_all(exarg_T *eap) { if (eap->addr_count == 0) { @@ -6829,7 +6910,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; @@ -6844,7 +6925,7 @@ static void ex_hide(exarg_T *eap) if (win == NULL) { win = lastwin; } - win_close(win, false); + win_close(win, false, eap->forceit); } } } @@ -6870,7 +6951,7 @@ static void ex_stop(exarg_T *eap) apply_autocmds(EVENT_VIMRESUME, NULL, NULL, false, NULL); } -// ":exit", ":xit" and ":wq": Write file and quit the current window. +/// ":exit", ":xit" and ":wq": Write file and quit the current window. static void ex_exit(exarg_T *eap) { if (cmdwin_type != 0) { @@ -6902,13 +6983,11 @@ 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); } } -/* - * ":print", ":list", ":number". - */ +/// ":print", ":list", ":number". static void ex_print(exarg_T *eap) { if (curbuf->b_ml.ml_flags & ML_EMPTY) { @@ -6938,29 +7017,23 @@ static void ex_goto(exarg_T *eap) goto_byte(eap->line2); } -/* - * Clear an argument list: free all file names and reset it to zero entries. - */ +/// Clear an argument list: free all file names and reset it to zero entries. void alist_clear(alist_T *al) { #define FREE_AENTRY_FNAME(arg) xfree(arg->ae_fname) GA_DEEP_CLEAR(&al->al_ga, aentry_T, FREE_AENTRY_FNAME); } -/* - * Init an argument list. - */ +/// Init an argument list. void alist_init(alist_T *al) { ga_init(&al->al_ga, (int)sizeof(aentry_T), 5); } -/* - * Remove a reference from an argument list. - * Ignored when the argument list is the global one. - * If the argument list is no longer used by any window, free it. - */ +/// Remove a reference from an argument list. +/// Ignored when the argument list is the global one. +/// If the argument list is no longer used by any window, free it. void alist_unlink(alist_T *al) { if (al != &global_alist && --al->al_refcount <= 0) { @@ -6969,9 +7042,7 @@ void alist_unlink(alist_T *al) } } -/* - * Create a new argument list and use it for the current window. - */ +/// Create a new argument list and use it for the current window. void alist_new(void) { curwin->w_alist = xmalloc(sizeof(*curwin->w_alist)); @@ -6981,11 +7052,10 @@ void alist_new(void) } #if !defined(UNIX) -/* - * Expand the file names in the global argument list. - * If "fnum_list" is not NULL, use "fnum_list[fnum_len]" as a list of buffer - * numbers to be re-used. - */ + +/// Expand the file names in the global argument list. +/// If "fnum_list" is not NULL, use "fnum_list[fnum_len]" as a list of buffer +/// numbers to be re-used. void alist_expand(int *fnum_list, int fnum_len) { char_u **old_arg_files; @@ -7016,10 +7086,8 @@ void alist_expand(int *fnum_list, int fnum_len) } #endif -/* - * Set the argument list for the current window. - * Takes over the allocated files[] and the allocated fnames in it. - */ +/// Set the argument list for the current window. +/// Takes over the allocated files[] and the allocated fnames in it. void alist_set(alist_T *al, int count, char_u **files, int use_curbuf, int *fnum_list, int fnum_len) { int i; @@ -7083,9 +7151,8 @@ void alist_add(alist_T *al, char_u *fname, int set_fnum) } #if defined(BACKSLASH_IN_FILENAME) -/* - * Adjust slashes in file names. Called after 'shellslash' was set. - */ + +/// Adjust slashes in file names. Called after 'shellslash' was set. void alist_slash_adjust(void) { for (int i = 0; i < GARGCOUNT; ++i) { @@ -7110,7 +7177,6 @@ void alist_slash_adjust(void) /// ":preserve". static void ex_preserve(exarg_T *eap) { - curbuf->b_flags |= BF_PRESERVED; ml_preserve(curbuf, true, true); } @@ -7131,27 +7197,23 @@ static void ex_recover(exarg_T *eap) recoverymode = false; } -/* - * Command modifier used in a wrong way. - */ +/// Command modifier used in a wrong way. static void ex_wrongmodifier(exarg_T *eap) { eap->errmsg = e_invcmd; } -/* - * :sview [+command] file split window with new file, read-only - * :split [[+command] file] split window with current or new file - * :vsplit [[+command] file] split window vertically with current or new file - * :new [[+command] file] split window with no or new file - * :vnew [[+command] file] split vertically window with no or new file - * :sfind [+command] file split window with file in 'path' - * - * :tabedit open new Tab page with empty window - * :tabedit [+command] file open new Tab page and edit "file" - * :tabnew [[+command] file] just like :tabedit - * :tabfind [+command] file open new Tab page and find "file" - */ +/// :sview [+command] file split window with new file, read-only +/// :split [[+command] file] split window with current or new file +/// :vsplit [[+command] file] split window vertically with current or new file +/// :new [[+command] file] split window with no or new file +/// :vnew [[+command] file] split vertically window with no or new file +/// :sfind [+command] file split window with file in 'path' +/// +/// :tabedit open new Tab page with empty window +/// :tabedit [+command] file open new Tab page and edit "file" +/// :tabnew [[+command] file] just like :tabedit +/// :tabfind [+command] file open new Tab page and find "file" void ex_splitview(exarg_T *eap) { win_T *old_curwin = curwin; @@ -7214,9 +7276,7 @@ theend: xfree(fname); } -/* - * Open a new tab page. - */ +/// Open a new tab page. void tabpage_new(void) { exarg_T ea; @@ -7228,9 +7288,7 @@ void tabpage_new(void) ex_splitview(&ea); } -/* - * :tabnext command - */ +/// :tabnext command static void ex_tabnext(exarg_T *eap) { int tab_number; @@ -7277,9 +7335,7 @@ static void ex_tabnext(exarg_T *eap) } } -/* - * :tabmove command - */ +/// :tabmove command static void ex_tabmove(exarg_T *eap) { int tab_number = get_tabpage_arg(eap); @@ -7288,9 +7344,7 @@ static void ex_tabmove(exarg_T *eap) } } -/* - * :tabs command: List tabs and their contents. - */ +/// :tabs command: List tabs and their contents. static void ex_tabs(exarg_T *eap) { int tabcount = 1; @@ -7336,10 +7390,8 @@ static void ex_tabs(exarg_T *eap) } -/* - * ":mode": - * If no argument given, get the screen size and redraw. - */ +/// ":mode": +/// If no argument given, get the screen size and redraw. static void ex_mode(exarg_T *eap) { if (*eap->arg == NUL) { @@ -7350,10 +7402,8 @@ static void ex_mode(exarg_T *eap) } } -/* - * ":resize". - * set, increment or decrement current window height - */ +/// ":resize". +/// set, increment or decrement current window height static void ex_resize(exarg_T *eap) { int n; @@ -7383,9 +7433,7 @@ static void ex_resize(exarg_T *eap) } } -/* - * ":find [+command] <file>" command. - */ +/// ":find [+command] <file>" command. static void ex_find(exarg_T *eap) { char_u *fname; @@ -7416,7 +7464,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) @@ -7462,9 +7510,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), @@ -7504,7 +7551,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 @@ -7563,11 +7610,9 @@ static void ex_swapname(exarg_T *eap) } } -/* - * ":syncbind" forces all 'scrollbind' windows to have the same relative - * offset. - * (1998-11-02 16:21:01 R. Edward Ralston <eralston@computer.org>) - */ +/// ":syncbind" forces all 'scrollbind' windows to have the same relative +/// offset. +/// (1998-11-02 16:21:01 R. Edward Ralston <eralston@computer.org>) static void ex_syncbind(exarg_T *eap) { win_T *save_curwin = curwin; @@ -7698,7 +7743,7 @@ void free_cd_dir(void) #endif -// Get the previous directory for the given chdir scope. +/// Get the previous directory for the given chdir scope. static char_u *get_prevdir(CdScope scope) { switch (scope) { @@ -7716,7 +7761,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); @@ -7753,10 +7798,11 @@ void post_chdir(CdScope scope, bool trigger_dirchanged) abort(); } + last_chdir_reason = NULL; shorten_fnames(true); if (trigger_dirchanged) { - do_autocmd_dirchanged(cwd, scope, kCdCauseManual); + do_autocmd_dirchanged(cwd, scope, kCdCauseManual, false); } } @@ -7766,14 +7812,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); @@ -7784,45 +7827,51 @@ 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) - // On Unix ":cd" means: go to home directory. if (*new_dir == NUL) { +#else + if (*new_dir == NUL && p_cdh) { +#endif // Use NameBuff for home directory name. expand_env((char_u *)"$HOME", NameBuff, MAXPATHL); new_dir = NameBuff; } -#endif - if (vim_chdir(new_dir) == 0) { - bool dir_differs = pdir == NULL || pathcmp((char *)pdir, (char *)new_dir, -1) != 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". @@ -7830,9 +7879,9 @@ void ex_cd(exarg_T *eap) { char_u *new_dir; new_dir = eap->arg; -#if !defined(UNIX) && !defined(VMS) - // for non-UNIX ":cd" means: print current directory - if (*new_dir == NUL) { +#if !defined(UNIX) + // for non-UNIX ":cd" means: print current directory unless 'cdhome' is set + if (*new_dir == NUL && !p_cdh) { ex_pwd(NULL); } else #endif @@ -7859,9 +7908,7 @@ void ex_cd(exarg_T *eap) } } -/* - * ":pwd". - */ +/// ":pwd". static void ex_pwd(exarg_T *eap) { if (os_dirname(NameBuff, MAXPATHL) == OK) { @@ -7870,7 +7917,9 @@ static void ex_pwd(exarg_T *eap) #endif if (p_verbose > 0) { char *context = "global"; - if (curwin->w_localdir != NULL) { + if (last_chdir_reason != NULL) { + context = last_chdir_reason; + } else if (curwin->w_localdir != NULL) { context = "window"; } else if (curtab->tp_localdir != NULL) { context = "tabpage"; @@ -7884,9 +7933,7 @@ static void ex_pwd(exarg_T *eap) } } -/* - * ":=". - */ +/// ":=". static void ex_equal(exarg_T *eap) { smsg("%" PRId64, (int64_t)eap->line2); @@ -7917,9 +7964,7 @@ static void ex_sleep(exarg_T *eap) do_sleep(len); } -/* - * Sleep for "msec" milliseconds, but keep checking for a CTRL-C every second. - */ +/// Sleep for "msec" milliseconds, but keep checking for a CTRL-C every second. void do_sleep(long msec) { ui_flush(); // flush before waiting @@ -7955,9 +8000,7 @@ static void do_exmap(exarg_T *eap, int isabbrev) } } -/* - * ":winsize" command (obsolete). - */ +/// ":winsize" command (obsolete). static void ex_winsize(exarg_T *eap) { char_u *arg = eap->arg; @@ -8008,9 +8051,7 @@ static void ex_wincmd(exarg_T *eap) } } -/* - * Handle command that work like operators: ":delete", ":yank", ":>" and ":<". - */ +/// Handle command that work like operators: ":delete", ":yank", ":>" and ":<". static void ex_operators(exarg_T *eap) { oparg_T oa; @@ -8040,7 +8081,7 @@ static void ex_operators(exarg_T *eap) case CMD_yank: oa.op_type = OP_YANK; - (void)op_yank(&oa, true, false); + (void)op_yank(&oa, true); break; default: // CMD_rshift or CMD_lshift @@ -8057,9 +8098,7 @@ static void ex_operators(exarg_T *eap) ex_may_print(eap); } -/* - * ":put". - */ +/// ":put". static void ex_put(exarg_T *eap) { // ":0put" works like ":1put!". @@ -8068,13 +8107,12 @@ 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); } -/* - * Handle ":copy" and ":move". - */ +/// Handle ":copy" and ":move". static void ex_copymove(exarg_T *eap) { long n = get_address(eap, &eap->arg, eap->addr_type, false, false, false, 1); @@ -8104,9 +8142,7 @@ static void ex_copymove(exarg_T *eap) ex_may_print(eap); } -/* - * Print the current line if flags were given to the Ex command. - */ +/// Print the current line if flags were given to the Ex command. void ex_may_print(exarg_T *eap) { if (eap->flags != 0) { @@ -8126,9 +8162,7 @@ static void ex_submagic(exarg_T *eap) p_magic = magic_save; } -/* - * ":join". - */ +/// ":join". static void ex_join(exarg_T *eap) { curwin->w_cursor.lnum = eap->line1; @@ -8147,9 +8181,7 @@ static void ex_join(exarg_T *eap) ex_may_print(eap); } -/* - * ":[addr]@r": execute register - */ +/// ":[addr]@r": execute register static void ex_at(exarg_T *eap) { int prev_len = typebuf.tb_len; @@ -8185,17 +8217,13 @@ static void ex_at(exarg_T *eap) } } -/* - * ":!". - */ +/// ":!". static void ex_bang(exarg_T *eap) { do_bang(eap->addr_count, eap, eap->forceit, true, true); } -/* - * ":undo". - */ +/// ":undo". static void ex_undo(exarg_T *eap) { if (eap->addr_count == 1) { // :undo 123 @@ -8261,9 +8289,7 @@ static void ex_later(exarg_T *eap) } } -/* - * ":redir": start/stop redirection. - */ +/// ":redir": start/stop redirection. static void ex_redir(exarg_T *eap) { char *mode; @@ -8334,9 +8360,7 @@ static void ex_redir(exarg_T *eap) if (var_redir_start(skipwhite(arg), append) == OK) { redir_vname = 1; } - } - // TODO: redirect to a buffer - else { + } else { // TODO(vim): redirect to a buffer semsg(_(e_invarg2), eap->arg); } } @@ -8406,7 +8430,7 @@ static void ex_redrawstatus(exarg_T *eap) ui_flush(); } -// ":redrawtabline": force redraw of the tabline +/// ":redrawtabline": force redraw of the tabline static void ex_redrawtabline(exarg_T *eap FUNC_ATTR_UNUSED) { const int r = RedrawingDisabled; @@ -8480,9 +8504,7 @@ FILE *open_exfile(char_u *fname, int forceit, char *mode) return fd; } -/* - * ":mark" and ":k". - */ +/// ":mark" and ":k". static void ex_mark(exarg_T *eap) { pos_T pos; @@ -8502,9 +8524,7 @@ static void ex_mark(exarg_T *eap) } } -/* - * Update w_topline, w_leftcol and the cursor position. - */ +/// Update w_topline, w_leftcol and the cursor position. void update_topline_cursor(void) { check_cursor(); // put cursor on valid line @@ -8515,8 +8535,9 @@ void update_topline_cursor(void) update_curswant(); } -// Save the current State and go to Normal mode. -// Return true if the typeahead could be saved. +/// Save the current State and go to Normal mode. +/// +/// @return true if the typeahead could be saved. bool save_current_state(save_state_T *sst) FUNC_ATTR_NONNULL_ALL { @@ -8528,6 +8549,7 @@ bool save_current_state(save_state_T *sst) sst->save_finish_op = finish_op; sst->save_opcount = opcount; sst->save_reg_executing = reg_executing; + sst->save_pending_end_reg_executing = pending_end_reg_executing; msg_scroll = false; // no msg scrolling in Normal mode restart_edit = 0; // don't go to Insert mode @@ -8558,6 +8580,7 @@ void restore_current_state(save_state_T *sst) finish_op = sst->save_finish_op; opcount = sst->save_opcount; reg_executing = sst->save_reg_executing; + pending_end_reg_executing = sst->save_pending_end_reg_executing; // don't reset msg_didout now msg_didout |= sst->save_msg_didout; @@ -8568,9 +8591,7 @@ void restore_current_state(save_state_T *sst) ui_cursor_shape(); // may show different cursor shape } -/* - * ":normal[!] {commands}": Execute normal mode commands. - */ +/// ":normal[!] {commands}": Execute normal mode commands. static void ex_normal(exarg_T *eap) { if (curbuf->terminal && State & TERM_FOCUS) { @@ -8591,7 +8612,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. { @@ -8600,8 +8621,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; } } @@ -8652,9 +8672,7 @@ static void ex_normal(exarg_T *eap) xfree(arg); } -/* - * ":startinsert", ":startreplace" and ":startgreplace" - */ +/// ":startinsert", ":startreplace" and ":startgreplace" static void ex_startinsert(exarg_T *eap) { if (eap->forceit) { @@ -8691,9 +8709,7 @@ static void ex_startinsert(exarg_T *eap) } } -/* - * ":stopinsert" - */ +/// ":stopinsert" static void ex_stopinsert(exarg_T *eap) { restart_edit = 0; @@ -8701,10 +8717,8 @@ static void ex_stopinsert(exarg_T *eap) clearmode(); } -/* - * Execute normal mode command "cmd". - * "remap" can be REMAP_NONE or REMAP_YES. - */ +/// Execute normal mode command "cmd". +/// "remap" can be REMAP_NONE or REMAP_YES. void exec_normal_cmd(char_u *cmd, int remap, bool silent) { // Stuff the argument into the typeahead buffer. @@ -8737,9 +8751,7 @@ static void ex_checkpath(exarg_T *eap) (linenr_T)1, (linenr_T)MAXLNUM); } -/* - * ":psearch" - */ +/// ":psearch" static void ex_psearch(exarg_T *eap) { g_do_tagpreview = p_pvh; @@ -8802,18 +8814,14 @@ static void ex_findpat(exarg_T *eap) } -/* - * ":ptag", ":ptselect", ":ptjump", ":ptnext", etc. - */ +/// ":ptag", ":ptselect", ":ptjump", ":ptnext", etc. static void ex_ptag(exarg_T *eap) { g_do_tagpreview = p_pvh; // will be reset to 0 in ex_tag_cmd() ex_tag_cmd(eap, cmdnames[eap->cmdidx].cmd_name + 1); } -/* - * ":pedit" - */ +/// ":pedit" static void ex_pedit(exarg_T *eap) { win_T *curwin_save = curwin; @@ -8834,9 +8842,7 @@ static void ex_pedit(exarg_T *eap) g_do_tagpreview = 0; } -/* - * ":stag", ":stselect" and ":stjump". - */ +/// ":stag", ":stselect" and ":stjump". static void ex_stag(exarg_T *eap) { postponed_split = -1; @@ -8847,9 +8853,7 @@ static void ex_stag(exarg_T *eap) postponed_split_tab = 0; } -/* - * ":tag", ":tselect", ":tjump", ":tnext", etc. - */ +/// ":tag", ":tselect", ":tjump", ":tnext", etc. static void ex_tag(exarg_T *eap) { ex_tag_cmd(eap, cmdnames[eap->cmdidx].cmd_name); @@ -8918,11 +8922,9 @@ enum { // SPEC_CLIENT, }; -/* - * Check "str" for starting with a special cmdline variable. - * If found return one of the SPEC_ values and set "*usedlen" to the length of - * the variable. Otherwise return -1 and "*usedlen" is unchanged. - */ +/// Check "str" for starting with a special cmdline variable. +/// If found return one of the SPEC_ values and set "*usedlen" to the length of +/// the variable. Otherwise return -1 and "*usedlen" is unchanged. ssize_t find_cmdline_var(const char_u *src, size_t *usedlen) FUNC_ATTR_NONNULL_ALL { @@ -9235,11 +9237,9 @@ char_u *eval_vars(char_u *src, char_u *srcstart, size_t *usedlen, linenr_T *lnum return result; } -/* - * Concatenate all files in the argument list, separated by spaces, and return - * it in one allocated string. - * Spaces and backslashes in the file names are escaped with a backslash. - */ +/// Concatenate all files in the argument list, separated by spaces, and return +/// it in one allocated string. +/// Spaces and backslashes in the file names are escaped with a backslash. static char_u *arg_all(void) { int len; @@ -9298,11 +9298,9 @@ static char_u *arg_all(void) return retval; } -/* - * Expand the <sfile> string in "arg". - * - * Returns an allocated string, or NULL for any error. - */ +/// Expand the <sfile> string in "arg". +/// +/// @return an allocated string, or NULL for any error. char_u *expand_sfile(char_u *arg) { char *errormsg; @@ -9348,9 +9346,7 @@ char_u *expand_sfile(char_u *arg) return result; } -/* - * ":rshada" and ":wshada". - */ +/// ":rshada" and ":wshada". static void ex_shada(exarg_T *eap) { char_u *save_shada; @@ -9367,10 +9363,8 @@ static void ex_shada(exarg_T *eap) p_shada = save_shada; } -/* - * Make a dialog message in "buff[DIALOG_MSG_SIZE]". - * "format" must contain "%s". - */ +/// Make a dialog message in "buff[DIALOG_MSG_SIZE]". +/// "format" must contain "%s". void dialog_msg(char_u *buff, char *format, char_u *fname) { if (fname == NULL) { @@ -9379,9 +9373,7 @@ void dialog_msg(char_u *buff, char *format, char_u *fname) vim_snprintf((char *)buff, DIALOG_MSG_SIZE, format, fname); } -/* - * ":behave {mswin,xterm}" - */ +/// ":behave {mswin,xterm}" static void ex_behave(exarg_T *eap) { if (STRCMP(eap->arg, "mswin") == 0) { @@ -9399,10 +9391,8 @@ static void ex_behave(exarg_T *eap) } } -/* - * Function given to ExpandGeneric() to obtain the possible arguments of the - * ":behave {mswin,xterm}" command. - */ +/// Function given to ExpandGeneric() to obtain the possible arguments of the +/// ":behave {mswin,xterm}" command. char_u *get_behave_arg(expand_T *xp, int idx) { if (idx == 0) { @@ -9414,8 +9404,8 @@ char_u *get_behave_arg(expand_T *xp, int idx) return NULL; } -// Function given to ExpandGeneric() to obtain the possible arguments of the -// ":messages {clear}" command. +/// Function given to ExpandGeneric() to obtain the possible arguments of the +/// ":messages {clear}" command. char_u *get_messages_arg(expand_T *xp FUNC_ATTR_UNUSED, int idx) { if (idx == 0) { @@ -9436,15 +9426,13 @@ static TriState filetype_detect = kNone; static TriState filetype_plugin = kNone; static TriState filetype_indent = kNone; -/* - * ":filetype [plugin] [indent] {on,off,detect}" - * on: Load the filetype.vim file to install autocommands for file types. - * off: Load the ftoff.vim file to remove all autocommands for file types. - * plugin on: load filetype.vim and ftplugin.vim - * plugin off: load ftplugof.vim - * indent on: load filetype.vim and indent.vim - * indent off: load indoff.vim - */ +/// ":filetype [plugin] [indent] {on,off,detect}" +/// on: Load the filetype.vim file to install autocommands for file types. +/// off: Load the ftoff.vim file to remove all autocommands for file types. +/// plugin on: load filetype.vim and ftplugin.vim +/// plugin off: load ftplugof.vim +/// indent on: load filetype.vim and indent.vim +/// indent off: load indoff.vim static void ex_filetype(exarg_T *eap) { char_u *arg = eap->arg; @@ -9510,23 +9498,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 overridden) 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) { @@ -9553,97 +9552,19 @@ 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; set_vim_var_nr(VV_HLSEARCH, !no_hlsearch && p_hls); } -/* - * ":nohlsearch" - */ +/// ":nohlsearch" static void ex_nohlsearch(exarg_T *eap) { set_no_hlsearch(true); redraw_all_later(SOME_VALID); } -// ":[N]match {group} {pattern}" -// Sets nextcmd to the start of the next command, if any. Also called when -// skipping commands to find the next command. -static void ex_match(exarg_T *eap) -{ - char_u *p; - char_u *g = NULL; - char_u *end; - int c; - int id; - - if (eap->line2 <= 3) { - id = eap->line2; - } else { - emsg(e_invcmd); - return; - } - - // First clear any old pattern. - if (!eap->skip) { - match_delete(curwin, id, false); - } - - if (ends_excmd(*eap->arg)) { - end = eap->arg; - } else if ((STRNICMP(eap->arg, "none", 4) == 0 - && (ascii_iswhite(eap->arg[4]) || ends_excmd(eap->arg[4])))) { - end = eap->arg + 4; - } else { - p = skiptowhite(eap->arg); - if (!eap->skip) { - g = vim_strnsave(eap->arg, p - eap->arg); - } - p = skipwhite(p); - if (*p == NUL) { - // There must be two arguments. - xfree(g); - semsg(_(e_invarg2), eap->arg); - return; - } - end = skip_regexp(p + 1, *p, true, NULL); - if (!eap->skip) { - if (*end != NUL && !ends_excmd(*skipwhite(end + 1))) { - xfree(g); - eap->errmsg = e_trailing; - return; - } - if (*end != *p) { - xfree(g); - semsg(_(e_invarg2), p); - return; - } - - c = *end; - *end = NUL; - match_add(curwin, (const char *)g, (const char *)p + 1, 10, id, - NULL, NULL); - xfree(g); - *end = c; - } - } - eap->nextcmd = find_nextcmd(end); -} - static void ex_fold(exarg_T *eap) { if (foldManualAllowed(true)) { @@ -9673,8 +9594,8 @@ static void ex_folddo(exarg_T *eap) ml_clearmarked(); // clear rest of the marks } -// Returns true if the supplied Ex cmdidx is for a location list command -// instead of a quickfix command. +/// @return true if the supplied Ex cmdidx is for a location list command +/// instead of a quickfix command. bool is_loclist_cmd(int cmdidx) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { @@ -9799,6 +9720,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 7ec4fad277..874e0e599e 100644 --- a/src/nvim/ex_docmd.h +++ b/src/nvim/ex_docmd.h @@ -1,6 +1,7 @@ #ifndef NVIM_EX_DOCMD_H #define NVIM_EX_DOCMD_H +#include "nvim/eval/funcs.h" #include "nvim/ex_cmds_defs.h" #include "nvim/globals.h" @@ -28,9 +29,30 @@ typedef struct { bool save_finish_op; long save_opcount; int save_reg_executing; + bool save_pending_end_reg_executing; 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 f60f0ebe98..3c7c635d98 100644 --- a/src/nvim/ex_eval.c +++ b/src/nvim/ex_eval.c @@ -88,27 +88,26 @@ */ static int cause_abort = FALSE; -// Return true when immediately aborting on error, or when an interrupt -// occurred or an exception was thrown but not caught. Use for ":{range}call" -// to check whether an aborted function that does not handle a range itself -// should be called again for the next line in the range. Also used for -// cancelling expression evaluation after a function call caused an immediate -// abort. Note that the first emsg() call temporarily resets "force_abort" -// until the throw point for error messages has been reached. That is, during -// cancellation of an expression evaluation after an aborting function call or -// due to a parsing error, aborting() always returns the same value. -// "got_int" is also set by calling interrupt(). +/// @return true when immediately aborting on error, or when an interrupt +/// occurred or an exception was thrown but not caught. +/// +/// Use for ":{range}call" to check whether an aborted function that does not +/// handle a range itself should be called again for the next line in the range. +/// Also used for cancelling expression evaluation after a function call caused +/// an immediate abort. Note that the first emsg() call temporarily resets +/// "force_abort" until the throw point for error messages has been reached. +/// That is, during cancellation of an expression evaluation after an aborting +/// function call or due to a parsing error, aborting() always returns the same +/// value. "got_int" is also set by calling interrupt(). int aborting(void) { return (did_emsg && force_abort) || got_int || current_exception; } -/* - * The value of "force_abort" is temporarily reset by the first emsg() call - * during an expression evaluation, and "cause_abort" is used instead. It might - * be necessary to restore "force_abort" even before the throw point for the - * error message has been reached. update_force_abort() should be called then. - */ +/// The value of "force_abort" is temporarily reset by the first emsg() call +/// during an expression evaluation, and "cause_abort" is used instead. It might +/// be necessary to restore "force_abort" even before the throw point for the +/// error message has been reached. update_force_abort() should be called then. void update_force_abort(void) { if (cause_abort) { @@ -116,23 +115,19 @@ void update_force_abort(void) } } -/* - * Return TRUE if a command with a subcommand resulting in "retcode" should - * abort the script processing. Can be used to suppress an autocommand after - * execution of a failing subcommand as long as the error message has not been - * displayed and actually caused the abortion. - */ +/// @return TRUE if a command with a subcommand resulting in "retcode" should +/// abort the script processing. Can be used to suppress an autocommand after +/// execution of a failing subcommand as long as the error message has not been +/// displayed and actually caused the abortion. int should_abort(int retcode) { return (retcode == FAIL && trylevel != 0 && !emsg_silent) || aborting(); } -/* - * Return TRUE if a function with the "abort" flag should not be considered - * ended on an error. This means that parsing commands is continued in order - * to find finally clauses to be executed, and that some errors in skipped - * commands are still reported. - */ +/// @return TRUE if a function with the "abort" flag should not be considered +/// ended on an error. This means that parsing commands is continued in order +/// to find finally clauses to be executed, and that some errors in skipped +/// commands are still reported. int aborted_in_try(void) { // This function is only called after an error. In this case, "force_abort" @@ -140,13 +135,15 @@ int aborted_in_try(void) return force_abort; } -// cause_errthrow(): Cause a throw of an error exception if appropriate. -// Return true if the error message should not be displayed by emsg(). -// Sets "ignore", if the emsg() call should be ignored completely. -// -// When several messages appear in the same command, the first is usually the -// most specific one and used as the exception value. The "severe" flag can be -// set to true, if a later but severer message should be used instead. +/// cause_errthrow(): Cause a throw of an error exception if appropriate. +/// +/// @return true if the error message should not be displayed by emsg(). +/// +/// Sets "ignore", if the emsg() call should be ignored completely. +/// +/// When several messages appear in the same command, the first is usually the +/// most specific one and used as the exception value. The "severe" flag can be +/// set to true, if a later but severer message should be used instead. bool cause_errthrow(const char_u *mesg, bool severe, bool *ignore) FUNC_ATTR_NONNULL_ALL { @@ -279,9 +276,7 @@ bool cause_errthrow(const char_u *mesg, bool severe, bool *ignore) } } -/* - * Free a "msg_list" and the messages it contains. - */ +/// Free a "msg_list" and the messages it contains. static void free_msglist(struct msglist *l) { struct msglist *messages, *next; @@ -295,21 +290,17 @@ static void free_msglist(struct msglist *l) } } -/* - * Free global "*msg_list" and the messages it contains, then set "*msg_list" - * to NULL. - */ +/// Free global "*msg_list" and the messages it contains, then set "*msg_list" +/// to NULL. void free_global_msglist(void) { free_msglist(*msg_list); *msg_list = NULL; } -/* - * Throw the message specified in the call to cause_errthrow() above as an - * error exception. If cstack is NULL, postpone the throw until do_cmdline() - * has returned (see do_one_cmd()). - */ +/// Throw the message specified in the call to cause_errthrow() above as an +/// error exception. If cstack is NULL, postpone the throw until do_cmdline() +/// has returned (see do_one_cmd()). void do_errthrow(cstack_T *cstack, char_u *cmdname) { /* @@ -339,11 +330,11 @@ void do_errthrow(cstack_T *cstack, char_u *cmdname) *msg_list = NULL; } -/* - * do_intthrow(): Replace the current exception by an interrupt or interrupt - * exception if appropriate. Return TRUE if the current exception is discarded, - * FALSE otherwise. - */ +/// do_intthrow(): Replace the current exception by an interrupt or interrupt +/// exception if appropriate. +/// +/// @return TRUE if the current exception is discarded or, +/// FALSE otherwise. int do_intthrow(cstack_T *cstack) { // If no interrupt occurred or no try conditional is active and no exception @@ -386,7 +377,7 @@ int do_intthrow(cstack_T *cstack) return true; } -// Get an exception message that is to be stored in current_exception->value. +/// Get an exception message that is to be stored in current_exception->value. char *get_exception_string(void *value, except_type_T type, char_u *cmdname, int *should_free) { char *ret, *mesg; @@ -430,14 +421,14 @@ char *get_exception_string(void *value, except_type_T type, char_u *cmdname, int STRCAT(val, p); p[-2] = NUL; - sprintf((char *)(val + STRLEN(p)), " (%s)", &mesg[1]); + snprintf(val + STRLEN(p), strlen(" (%s)"), " (%s)", &mesg[1]); p[-2] = '"'; } break; } } } else { - *should_free = FALSE; + *should_free = false; ret = value; } @@ -445,10 +436,12 @@ char *get_exception_string(void *value, except_type_T type, char_u *cmdname, int } -// Throw a new exception. Return FAIL when out of memory or it was tried to -// throw an illegal user exception. "value" is the exception string for a -// user or interrupt exception, or points to a message list in case of an -// error exception. +/// Throw a new exception. "value" is the exception string for a +/// user or interrupt exception, or points to a message list in case of an +/// error exception. +/// +/// @return FAIL when out of memory or it was tried to throw an illegal user +/// exception. static int throw_exception(void *value, except_type_T type, char_u *cmdname) { except_T *excp; @@ -524,10 +517,8 @@ fail: return FAIL; } -/* - * Discard an exception. "was_finished" is set when the exception has been - * caught and the catch clause has been ended normally. - */ +/// Discard an exception. "was_finished" is set when the exception has been +/// caught and the catch clause has been ended normally. static void discard_exception(except_T *excp, bool was_finished) { char_u *saved_IObuff; @@ -579,9 +570,7 @@ static void discard_exception(except_T *excp, bool was_finished) xfree(excp); } -/* - * Discard the exception currently being thrown. - */ +/// Discard the exception currently being thrown. void discard_current_exception(void) { if (current_exception != NULL) { @@ -592,14 +581,12 @@ void discard_current_exception(void) need_rethrow = false; } -/* - * Put an exception on the caught stack. - */ +/// Put an exception on the caught stack. 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), @@ -640,9 +627,7 @@ static void catch_exception(except_T *excp) } } -/* - * Remove an exception from the caught stack. - */ +/// Remove an exception from the caught stack. static void finish_exception(except_T *excp) { if (excp != caught_stack) { @@ -650,7 +635,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, @@ -682,13 +667,11 @@ static void finish_exception(except_T *excp) #define RP_RESUME 1 #define RP_DISCARD 2 -/* - * Report information about something pending in a finally clause if required by - * the 'verbose' option or when debugging. "action" tells whether something is - * made pending or something pending is resumed or discarded. "pending" tells - * what is pending. "value" specifies the return value for a pending ":return" - * or the exception value for a pending exception. - */ +/// Report information about something pending in a finally clause if required by +/// the 'verbose' option or when debugging. "action" tells whether something is +/// made pending or something pending is resumed or discarded. "pending" tells +/// what is pending. "value" specifies the return value for a pending ":return" +/// or the exception value for a pending exception. static void report_pending(int action, int pending, void *value) { char *mesg; @@ -733,7 +716,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) { @@ -764,10 +747,8 @@ static void report_pending(int action, int pending, void *value) } } -/* - * If something is made pending in a finally clause, report it if required by - * the 'verbose' option or when debugging. - */ +/// If something is made pending in a finally clause, report it if required by +/// the 'verbose' option or when debugging. void report_make_pending(int pending, void *value) { if (p_verbose >= 14 || debug_break_level > 0) { @@ -781,10 +762,8 @@ void report_make_pending(int pending, void *value) } } -/* - * If something pending in a finally clause is resumed at the ":endtry", report - * it if required by the 'verbose' option or when debugging. - */ +/// If something pending in a finally clause is resumed at the ":endtry", report +/// it if required by the 'verbose' option or when debugging. void report_resume_pending(int pending, void *value) { if (p_verbose >= 14 || debug_break_level > 0) { @@ -798,10 +777,8 @@ void report_resume_pending(int pending, void *value) } } -/* - * If something pending in a finally clause is discarded, report it if required - * by the 'verbose' option or when debugging. - */ +/// If something pending in a finally clause is discarded, report it if required +/// by the 'verbose' option or when debugging. void report_discard_pending(int pending, void *value) { if (p_verbose >= 14 || debug_break_level > 0) { @@ -815,7 +792,7 @@ void report_discard_pending(int pending, void *value) } } -// ":eval". +/// Handle ":eval". void ex_eval(exarg_T *eap) { typval_T tv; @@ -825,9 +802,7 @@ void ex_eval(exarg_T *eap) } } -/* - * ":if". - */ +/// Handle ":if". void ex_if(exarg_T *eap) { int skip; @@ -856,9 +831,7 @@ void ex_if(exarg_T *eap) } } -/* - * ":endif". - */ +/// Handle ":endif". void ex_endif(exarg_T *eap) { did_endif = true; @@ -883,16 +856,13 @@ void ex_endif(exarg_T *eap) } } -/* - * ":else" and ":elseif". - */ +/// Handle ":else" and ":elseif". void ex_else(exarg_T *eap) { - int skip; int result; cstack_T *const cstack = eap->cstack; - skip = CHECK_SKIP; + bool skip = CHECK_SKIP; if (cstack->cs_idx < 0 || (cstack->cs_flags[cstack->cs_idx] @@ -902,14 +872,14 @@ void ex_else(exarg_T *eap) return; } eap->errmsg = N_("E582: :elseif without :if"); - skip = TRUE; + skip = true; } else if (cstack->cs_flags[cstack->cs_idx] & CSF_ELSE) { if (eap->cmdidx == CMD_else) { eap->errmsg = N_("E583: multiple :else"); return; } eap->errmsg = N_("E584: :elseif after :else"); - skip = TRUE; + skip = true; } // if skipping or the ":if" was TRUE, reset ACTIVE, otherwise set it @@ -917,7 +887,7 @@ void ex_else(exarg_T *eap) if (eap->errmsg == NULL) { cstack->cs_flags[cstack->cs_idx] = CSF_TRUE; } - skip = TRUE; // don't evaluate an ":elseif" + skip = true; // don't evaluate an ":elseif" } else { cstack->cs_flags[cstack->cs_idx] = CSF_ACTIVE; } @@ -932,7 +902,7 @@ void ex_else(exarg_T *eap) // later on. if (!skip && dbg_check_skipped(eap) && got_int) { (void)do_intthrow(cstack); - skip = TRUE; + skip = true; } if (eap->cmdidx == CMD_elseif) { @@ -959,9 +929,7 @@ void ex_else(exarg_T *eap) } } -/* - * Handle ":while" and ":for". - */ +/// Handle ":while" and ":for". void ex_while(exarg_T *eap) { bool error; @@ -1042,9 +1010,7 @@ void ex_while(exarg_T *eap) } } -/* - * ":continue" - */ +/// Handle ":continue" void ex_continue(exarg_T *eap) { int idx; @@ -1076,9 +1042,7 @@ void ex_continue(exarg_T *eap) } } -/* - * ":break" - */ +/// Handle ":break" void ex_break(exarg_T *eap) { int idx; @@ -1099,9 +1063,7 @@ void ex_break(exarg_T *eap) } } -/* - * ":endwhile" and ":endfor" - */ +/// Handle ":endwhile" and ":endfor" void ex_endwhile(exarg_T *eap) { cstack_T *const cstack = eap->cstack; @@ -1176,9 +1138,7 @@ void ex_endwhile(exarg_T *eap) } -/* - * ":throw expr" - */ +/// Handle ":throw expr" void ex_throw(exarg_T *eap) { const char *arg = (const char *)eap->arg; @@ -1203,11 +1163,9 @@ void ex_throw(exarg_T *eap) } } -/* - * Throw the current exception through the specified cstack. Common routine - * for ":throw" (user exception) and error and interrupt exceptions. Also - * used for rethrowing an uncaught exception. - */ +/// Throw the current exception through the specified cstack. Common routine +/// for ":throw" (user exception) and error and interrupt exceptions. Also +/// used for rethrowing an uncaught exception. void do_throw(cstack_T *cstack) { int idx; @@ -1264,9 +1222,7 @@ void do_throw(cstack_T *cstack) } } -/* - * ":try" - */ +/// Handle ":try" void ex_try(exarg_T *eap) { int skip; @@ -1316,15 +1272,13 @@ void ex_try(exarg_T *eap) } } -/* - * ":catch /{pattern}/" and ":catch" - */ +/// Handle ":catch /{pattern}/" and ":catch" void ex_catch(exarg_T *eap) { int idx = 0; - int give_up = FALSE; - int skip = FALSE; - int caught = FALSE; + bool give_up = false; + bool skip = false; + bool caught = false; char_u *end; char_u save_char = 0; char_u *save_cpo; @@ -1335,13 +1289,13 @@ void ex_catch(exarg_T *eap) if (cstack->cs_trylevel <= 0 || cstack->cs_idx < 0) { eap->errmsg = N_("E603: :catch without :try"); - give_up = TRUE; + give_up = true; } else { if (!(cstack->cs_flags[cstack->cs_idx] & CSF_TRY)) { // Report what's missing if the matching ":try" is not in its // finally clause. eap->errmsg = get_end_emsg(cstack); - skip = TRUE; + skip = true; } for (idx = cstack->cs_idx; idx > 0; --idx) { if (cstack->cs_flags[idx] & CSF_TRY) { @@ -1352,7 +1306,7 @@ void ex_catch(exarg_T *eap) // Give up for a ":catch" after ":finally" and ignore it. // Just parse. eap->errmsg = N_("E604: :catch after :finally"); - give_up = TRUE; + give_up = true; } else { rewind_conditionals(cstack, idx, CSF_WHILE | CSF_FOR, &cstack->cs_looplevel); @@ -1425,7 +1379,7 @@ void ex_catch(exarg_T *eap) // CTRL-C while matching should abort it. // prev_got_int = got_int; - got_int = FALSE; + got_int = false; caught = vim_regexec_nl(®match, (char_u *)current_exception->value, (colnr_T)0); got_int |= prev_got_int; @@ -1472,9 +1426,7 @@ void ex_catch(exarg_T *eap) } } -/* - * ":finally" - */ +/// Handle ":finally" void ex_finally(exarg_T *eap) { int idx; @@ -1596,15 +1548,12 @@ void ex_finally(exarg_T *eap) } } -/* - * ":endtry" - */ +/// Handle ":endtry" void ex_endtry(exarg_T *eap) { int idx; - int skip; - int rethrow = FALSE; - int pending = CSTP_NONE; + bool rethrow = false; + char pending = CSTP_NONE; void *rettv = NULL; cstack_T *const cstack = eap->cstack; @@ -1621,20 +1570,19 @@ void ex_endtry(exarg_T *eap) // made inactive by a ":continue", ":break", ":return", or ":finish" in // the finally clause. The latter case need not be tested since then // anything pending has already been discarded. - skip = (did_emsg || got_int || current_exception - || !(cstack->cs_flags[cstack->cs_idx] & CSF_TRUE)); + bool skip = did_emsg || got_int || current_exception + || !(cstack->cs_flags[cstack->cs_idx] & CSF_TRUE); if (!(cstack->cs_flags[cstack->cs_idx] & CSF_TRY)) { eap->errmsg = get_end_emsg(cstack); // Find the matching ":try" and report what's missing. idx = cstack->cs_idx; do { - --idx; - } - while (idx > 0 && !(cstack->cs_flags[idx] & CSF_TRY)); + idx--; + } while (idx > 0 && !(cstack->cs_flags[idx] & CSF_TRY)); rewind_conditionals(cstack, idx, CSF_WHILE | CSF_FOR, &cstack->cs_looplevel); - skip = TRUE; + skip = true; /* * If an exception is being thrown, discard it to prevent it from @@ -1677,7 +1625,7 @@ void ex_endtry(exarg_T *eap) // before the ":endtry". That is, throw an interrupt exception and // set "skip" and "rethrow". if (got_int) { - skip = TRUE; + skip = true; (void)do_intthrow(cstack); // The do_intthrow() call may have reset current_exception or // cstack->cs_pending[idx]. @@ -1787,14 +1735,12 @@ void ex_endtry(exarg_T *eap) * error/interrupt/exception state. */ -/* - * This function works a bit like ex_finally() except that there was not - * actually an extra try block around the part that failed and an error or - * interrupt has not (yet) been converted to an exception. This function - * saves the error/interrupt/ exception state and prepares for the call to - * do_cmdline() that is going to be made for the cleanup autocommand - * execution. - */ +/// This function works a bit like ex_finally() except that there was not +/// actually an extra try block around the part that failed and an error or +/// interrupt has not (yet) been converted to an exception. This function +/// saves the error/interrupt/ exception state and prepares for the call to +/// do_cmdline() that is going to be made for the cleanup autocommand +/// execution. void enter_cleanup(cleanup_T *csp) { int pending = CSTP_NONE; @@ -1837,21 +1783,19 @@ void enter_cleanup(cleanup_T *csp) } } -/* - * See comment above enter_cleanup() for how this function is used. - * - * This function is a bit like ex_endtry() except that there was not actually - * an extra try block around the part that failed and an error or interrupt - * had not (yet) been converted to an exception when the cleanup autocommand - * sequence was invoked. - * - * This function has to be called with the address of the cleanup_T structure - * filled by enter_cleanup() as an argument; it restores the error/interrupt/ - * exception state saved by that function - except there was an aborting - * error, an interrupt or an uncaught exception during execution of the - * cleanup autocommands. In the latter case, the saved error/interrupt/ - * exception state is discarded. - */ +/// This function is a bit like ex_endtry() except that there was not actually +/// an extra try block around the part that failed and an error or interrupt +/// had not (yet) been converted to an exception when the cleanup autocommand +/// sequence was invoked. +/// +/// See comment above enter_cleanup() for how this function is used. +/// +/// This function has to be called with the address of the cleanup_T structure +/// filled by enter_cleanup() as an argument; it restores the error/interrupt/ +/// exception state saved by that function - except there was an aborting +/// error, an interrupt or an uncaught exception during execution of the +/// cleanup autocommands. In the latter case, the saved error/interrupt/ +/// exception state is discarded. void leave_cleanup(cleanup_T *csp) { int pending = csp->pending; @@ -1916,22 +1860,25 @@ void leave_cleanup(cleanup_T *csp) } -/* - * Make conditionals inactive and discard what's pending in finally clauses - * until the conditional type searched for or a try conditional not in its - * finally clause is reached. If this is in an active catch clause, finish - * the caught exception. - * Return the cstack index where the search stopped. - * Values used for "searched_cond" are (CSF_WHILE | CSF_FOR) or CSF_TRY or 0, - * the latter meaning the innermost try conditional not in its finally clause. - * "inclusive" tells whether the conditional searched for should be made - * inactive itself (a try conditional not in its finally clause possibly find - * before is always made inactive). If "inclusive" is TRUE and - * "searched_cond" is CSF_TRY|CSF_SILENT, the saved former value of - * "emsg_silent", if reset when the try conditional finally reached was - * entered, is restored (used by ex_endtry()). This is normally done only - * when such a try conditional is left. - */ +/// Make conditionals inactive and discard what's pending in finally clauses +/// until the conditional type searched for or a try conditional not in its +/// finally clause is reached. If this is in an active catch clause, finish +/// the caught exception. +/// +/// +/// @param searched_cond Possible values are (CSF_WHILE | CSF_FOR) or CSF_TRY or 0, +/// the latter meaning the innermost try conditional not +/// in its finally clause. +/// @param inclusive tells whether the conditional searched for should be made +/// inactive itself (a try conditional not in its finally +/// clause possibly find before is always made inactive). +/// +/// If "inclusive" is TRUE and "searched_cond" is CSF_TRY|CSF_SILENT, the saved +/// former value of "emsg_silent", if reset when the try conditional finally +/// reached was entered, is restored (used by ex_endtry()). This is normally +/// done only when such a try conditional is left. +/// +/// @return the cstack index where the search stopped. int cleanup_conditionals(cstack_T *cstack, int searched_cond, int inclusive) { int idx; @@ -2040,9 +1987,7 @@ int cleanup_conditionals(cstack_T *cstack, int searched_cond, int inclusive) return idx; } -/* - * Return an appropriate error message for a missing endwhile/endfor/endif. - */ +/// @return an appropriate error message for a missing endwhile/endfor/endif. static char *get_end_emsg(cstack_T *cstack) { if (cstack->cs_flags[cstack->cs_idx] & CSF_WHILE) { @@ -2055,13 +2000,11 @@ static char *get_end_emsg(cstack_T *cstack) } -/* - * Rewind conditionals until index "idx" is reached. "cond_type" and - * "cond_level" specify a conditional type and the address of a level variable - * which is to be decremented with each skipped conditional of the specified - * type. - * Also free "for info" structures where needed. - */ +/// Rewind conditionals until index "idx" is reached. "cond_type" and +/// "cond_level" specify a conditional type and the address of a level variable +/// which is to be decremented with each skipped conditional of the specified +/// type. +/// Also free "for info" structures where needed. void rewind_conditionals(cstack_T *cstack, int idx, int cond_type, int *cond_level) { while (cstack->cs_idx > idx) { @@ -2075,17 +2018,13 @@ void rewind_conditionals(cstack_T *cstack, int idx, int cond_type, int *cond_lev } } -/* - * ":endfunction" when not after a ":function" - */ +/// Handle ":endfunction" when not after a ":function" void ex_endfunction(exarg_T *eap) { emsg(_("E193: :endfunction not inside a function")); } -/* - * Return TRUE if the string "p" looks like a ":while" or ":for" command. - */ +/// @return TRUE if the string "p" looks like a ":while" or ":for" command. int has_loop_cmd(char_u *p) { int len; @@ -2107,4 +2046,3 @@ int has_loop_cmd(char_u *p) } return FALSE; } - diff --git a/src/nvim/ex_eval.h b/src/nvim/ex_eval.h index 25b30b2805..15da4b2d60 100644 --- a/src/nvim/ex_eval.h +++ b/src/nvim/ex_eval.h @@ -17,8 +17,8 @@ #define CSF_THROWN 0x0400 // exception thrown to this try conditional #define CSF_CAUGHT 0x0800 // exception caught by this try conditional #define CSF_SILENT 0x1000 // "emsg_silent" reset by ":try" -/* Note that CSF_ELSE is only used when CSF_TRY and CSF_WHILE are unset - * (an ":if"), and CSF_SILENT is only used when CSF_TRY is set. */ +// Note that CSF_ELSE is only used when CSF_TRY and CSF_WHILE are unset +// (an ":if"), and CSF_SILENT is only used when CSF_TRY is set. /* * What's pending for being reactivated at the ":endtry" of this try diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index 3b5953ae69..a7c0b06050 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -35,6 +35,7 @@ #include "nvim/getchar.h" #include "nvim/highlight.h" #include "nvim/highlight_defs.h" +#include "nvim/highlight_group.h" #include "nvim/if_cscope.h" #include "nvim/indent.h" #include "nvim/keymap.h" @@ -48,7 +49,6 @@ #include "nvim/memory.h" #include "nvim/menu.h" #include "nvim/message.h" -#include "nvim/misc1.h" #include "nvim/mouse.h" #include "nvim/move.h" #include "nvim/ops.h" @@ -192,13 +192,12 @@ typedef struct command_line_state { typedef struct cmdline_info CmdlineInfo; -/* The current cmdline_info. It is initialized in getcmdline() and after that - * used by other functions. When invoking getcmdline() recursively it needs - * to be saved with save_cmdline() and restored with restore_cmdline(). - * TODO: make it local to getcmdline() and pass it around. */ +/// The current cmdline_info. It is initialized in getcmdline() and after that +/// used by other functions. When invoking getcmdline() recursively it needs +/// to be saved with save_cmdline() and restored with restore_cmdline(). static struct cmdline_info ccline; -static int cmd_showtail; // Only show path tail in lists ? +static int cmd_showtail; // Only show path tail in lists ? static int new_cmdpos; // position set by set_cmdline_pos() @@ -232,7 +231,7 @@ static int compl_selected; /// |:checkhealth| completion items /// -/// Regenerates on every new command line prompt, to accomodate changes on the +/// Regenerates on every new command line prompt, to accommodate changes on the /// runtime files. typedef struct { garray_T names; // healthcheck names @@ -312,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. @@ -633,7 +632,7 @@ static void may_do_incsearch_highlighting(int firstc, long count, incsearch_stat validate_cursor(); // May redraw the status line to show the cursor position. - if (p_ru && curwin->w_status_height > 0) { + if (p_ru && (curwin->w_status_height > 0 || global_stl_height() > 0)) { curwin->w_redr_status = true; } @@ -732,9 +731,10 @@ static void finish_incsearch_highlighting(int gotesc, incsearch_state_T *s, bool /// Internal entry point for cmdline mode. /// -/// caller must use save_cmdline and restore_cmdline. Best is to use -/// getcmdline or getcmdline_prompt, instead of calling this directly. -static uint8_t *command_line_enter(int firstc, long count, int indent) +/// @param count only used for incremental search +/// @param indent indent for inside conditionals +/// @param init_ccline clear ccline first +static uint8_t *command_line_enter(int firstc, long count, int indent, bool init_ccline) { // can be invoked recursively, identify each level static int cmdline_level = 0; @@ -751,6 +751,20 @@ static uint8_t *command_line_enter(int firstc, long count, int indent) CommandLineState *s = &state; s->save_p_icm = vim_strsave(p_icm); init_incsearch_state(&s->is_state); + CmdlineInfo save_ccline; + bool did_save_ccline = false; + + if (ccline.cmdbuff != NULL) { + // Currently ccline can never be in use if init_ccline is false. + // Some changes will be needed if this is no longer the case. + assert(init_ccline); + // Being called recursively. Since ccline is global, we need to save + // the current buffer and restore it when returning. + save_cmdline(&save_ccline); + did_save_ccline = true; + } else if (init_ccline) { + memset(&ccline, 0, sizeof(struct cmdline_info)); + } if (s->firstc == -1) { s->firstc = NUL; @@ -773,7 +787,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; @@ -880,7 +894,8 @@ static uint8_t *command_line_enter(int firstc, long count, int indent) TryState tstate; Error err = ERROR_INIT; bool tl_ret = true; - dict_T *dict = get_vim_var_dict(VV_EVENT); + save_v_event_T save_v_event; + dict_T *dict = get_v_event(&save_v_event); char firstcbuf[2]; firstcbuf[0] = (char)(firstc > 0 ? firstc : '-'); firstcbuf[1] = 0; @@ -894,7 +909,7 @@ static uint8_t *command_line_enter(int firstc, long count, int indent) apply_autocmds(EVENT_CMDLINEENTER, (char_u *)firstcbuf, (char_u *)firstcbuf, false, curbuf); - tv_dict_clear(dict); + restore_v_event(dict, &save_v_event); tl_ret = try_leave(&tstate, &err); @@ -906,6 +921,7 @@ static uint8_t *command_line_enter(int firstc, long count, int indent) } tl_ret = true; } + may_trigger_modechanged(); state_enter(&s->state); @@ -924,7 +940,7 @@ static uint8_t *command_line_enter(int firstc, long count, int indent) if (tv_dict_get_number(dict, "abort") != 0) { s->gotesc = 1; } - tv_dict_clear(dict); + restore_v_event(dict, &save_v_event); } cmdmsg_rl = false; @@ -995,6 +1011,13 @@ static uint8_t *command_line_enter(int firstc, long count, int indent) } cmdline_level--; + + if (did_save_ccline) { + restore_cmdline(&save_ccline); + } else { + ccline.cmdbuff = NULL; + } + return p; } @@ -1023,11 +1046,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) { @@ -1336,15 +1361,9 @@ static int command_line_execute(VimState *state, int key) s->c = get_expr_register(); if (s->c == '=') { - // Need to save and restore ccline. And set "textlock" - // to avoid nasty things like going to another buffer when - // evaluating an expression. - CmdlineInfo save_ccline; - save_cmdline(&save_ccline); textlock++; p = get_expr_line(); textlock--; - restore_cmdline(&save_ccline); if (p != NULL) { len = (int)STRLEN(p); @@ -1587,6 +1606,10 @@ static int may_do_command_line_next_incsearch(int firstc, long count, incsearch_ if (search_delim == ccline.cmdbuff[skiplen]) { pat = last_search_pattern(); + if (pat == NULL) { + restore_last_search_pattern(); + return FAIL; + } skiplen = 0; patlen = (int)STRLEN(pat); } else { @@ -1885,10 +1908,7 @@ static int command_line_handle_key(CommandLineState *s) beep_flush(); s->c = ESC; } else { - CmdlineInfo save_ccline; - save_cmdline(&save_ccline); s->c = get_expr_register(); - restore_cmdline(&save_ccline); } } @@ -2116,7 +2136,7 @@ static int command_line_handle_key(CommandLineState *s) int len = 0; int old_firstc; - xfree(ccline.cmdbuff); + XFREE_CLEAR(ccline.cmdbuff); s->xpc.xp_context = EXPAND_NOTHING; if (s->hiscnt == hislen) { p = s->lookfor; // back to the old one @@ -2289,7 +2309,8 @@ static int command_line_changed(CommandLineState *s) if (has_event(EVENT_CMDLINECHANGED)) { TryState tstate; Error err = ERROR_INIT; - dict_T *dict = get_vim_var_dict(VV_EVENT); + save_v_event_T save_v_event; + dict_T *dict = get_v_event(&save_v_event); char firstcbuf[2]; firstcbuf[0] = (char)(s->firstc > 0 ? s->firstc : '-'); @@ -2303,7 +2324,7 @@ static int command_line_changed(CommandLineState *s) apply_autocmds(EVENT_CMDLINECHANGED, (char_u *)firstcbuf, (char_u *)firstcbuf, false, curbuf); - tv_dict_clear(dict); + restore_v_event(dict, &save_v_event); bool tl_ret = try_leave(&tstate, &err); if (!tl_ret && ERROR_SET(&err)) { @@ -2398,14 +2419,7 @@ static void abandon_cmdline(void) /// @param indent indent for inside conditionals char_u *getcmdline(int firstc, long count, int indent, bool do_concat FUNC_ATTR_UNUSED) { - // Be prepared for situations where cmdline can be invoked recursively. - // That includes cmd mappings, event handlers, as well as update_screen() - // (custom status line eval), which all may invoke ":normal :". - CmdlineInfo save_ccline; - save_cmdline(&save_ccline); - char_u *retval = command_line_enter(firstc, count, indent); - restore_cmdline(&save_ccline); - return retval; + return command_line_enter(firstc, count, indent, true); } /// Get a command line with a prompt @@ -2429,8 +2443,14 @@ char *getcmdline_prompt(const char firstc, const char *const prompt, const int a const int msg_col_save = msg_col; CmdlineInfo save_ccline; - save_cmdline(&save_ccline); - + bool did_save_ccline = false; + if (ccline.cmdbuff != NULL) { + // Save the values of the current cmdline and restore them below. + save_cmdline(&save_ccline); + did_save_ccline = true; + } else { + memset(&ccline, 0, sizeof(struct cmdline_info)); + } ccline.prompt_id = last_prompt_id++; ccline.cmdprompt = (char_u *)prompt; ccline.cmdattr = attr; @@ -2442,9 +2462,11 @@ char *getcmdline_prompt(const char firstc, const char *const prompt, const int a int msg_silent_saved = msg_silent; msg_silent = 0; - char *const ret = (char *)command_line_enter(firstc, 1L, 0); + char *const ret = (char *)command_line_enter(firstc, 1L, 0, false); - restore_cmdline(&save_ccline); + if (did_save_ccline) { + restore_cmdline(&save_ccline); + } msg_silent = msg_silent_saved; // Restore msg_col, the prompt from input() may have changed it. // But only if called recursively and the commandline is therefore being @@ -2488,27 +2510,25 @@ char *get_text_locked_msg(void) } /// Check if "curbuf->b_ro_locked" or "allbuf_lock" is set and -/// return TRUE when it is and give an error message. -int curbuf_locked(void) +/// return true when it is and give an error message. +bool curbuf_locked(void) { if (curbuf->b_ro_locked > 0) { emsg(_("E788: Not allowed to edit another buffer now")); - return TRUE; + return true; } return allbuf_locked(); } -/* - * Check if "allbuf_lock" is set and return TRUE when it is and give an error - * message. - */ -int allbuf_locked(void) +// Check if "allbuf_lock" is set and return true when it is and give an error +// message. +bool allbuf_locked(void) { if (allbuf_lock > 0) { emsg(_("E811: Not allowed to change buffer information now")); - return TRUE; + return true; } - return FALSE; + return false; } static int cmdline_charsize(int idx) @@ -2598,7 +2618,6 @@ bool cmdline_at_end(void) /* * Allocate a new command line buffer. * Assigns the new buffer to ccline.cmdbuff and ccline.cmdbufflen. - * Returns the new value of ccline.cmdbuff and ccline.cmdbufflen. */ static void alloc_cmdbuff(int len) { @@ -3364,53 +3383,23 @@ void put_on_cmdline(char_u *str, int len, int redraw) } } -/* - * Save ccline, because obtaining the "=" register may execute "normal :cmd" - * and overwrite it. But get_cmdline_str() may need it, thus make it - * available globally in prev_ccline. - */ +/// Save ccline, because obtaining the "=" register may execute "normal :cmd" +/// and overwrite it. static void save_cmdline(struct cmdline_info *ccp) { *ccp = ccline; + memset(&ccline, 0, sizeof(struct cmdline_info)); ccline.prev_ccline = ccp; - ccline.cmdbuff = NULL; - ccline.cmdprompt = NULL; - ccline.xpc = NULL; - ccline.special_char = NUL; - ccline.level = 0; + ccline.cmdbuff = NULL; // signal that ccline is not in use } -/* - * Restore ccline after it has been saved with save_cmdline(). - */ +/// Restore ccline after it has been saved with save_cmdline(). static void restore_cmdline(struct cmdline_info *ccp) FUNC_ATTR_NONNULL_ALL { ccline = *ccp; } -/* - * Save the command line into allocated memory. Returns a pointer to be - * passed to restore_cmdline_alloc() later. - */ -char_u *save_cmdline_alloc(void) - FUNC_ATTR_NONNULL_RET -{ - struct cmdline_info *p = xmalloc(sizeof(struct cmdline_info)); - save_cmdline(p); - return (char_u *)p; -} - -/* - * Restore the command line from the return value of save_cmdline_alloc(). - */ -void restore_cmdline_alloc(char_u *p) - FUNC_ATTR_NONNULL_ALL -{ - restore_cmdline((struct cmdline_info *)p); - xfree(p); -} - /// Paste a yank register into the command line. /// Used by CTRL-R command in command-line mode. /// insert_reg() can't be used here, because special characters from the @@ -3426,7 +3415,6 @@ static bool cmdline_paste(int regname, bool literally, bool remcr) char_u *arg; char_u *p; bool allocated; - struct cmdline_info save_ccline; // check for valid regname; also accept special characters for CTRL-R in // the command line @@ -3444,13 +3432,11 @@ static bool cmdline_paste(int regname, bool literally, bool remcr) } - // Need to save and restore ccline. And set "textlock" to avoid nasty - // things like going to another buffer when evaluating an expression. - save_cmdline(&save_ccline); + // Need to set "textlock" to avoid nasty things like going to another + // buffer when evaluating an expression. textlock++; const bool i = get_spec_reg(regname, &arg, &allocated, true); textlock--; - restore_cmdline(&save_ccline); if (i) { // Got the value of a special register in "arg". @@ -3629,7 +3615,7 @@ void compute_cmdrow(void) } else { win_T *wp = lastwin_nofloating(); cmdline_row = wp->w_winrow + wp->w_height - + wp->w_status_height; + + wp->w_hsep_height + wp->w_status_height + global_stl_height(); } lines_left = cmdline_row; } @@ -4983,6 +4969,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 +5033,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 }, @@ -5301,7 +5290,6 @@ static void *call_user_expand_func(user_expand_func_T user_expand_func, expand_T typval_T args[4]; char_u *pat = NULL; const sctx_T save_current_sctx = current_sctx; - struct cmdline_info save_ccline; if (xp->xp_arg == NULL || xp->xp_arg[0] == '\0' || xp->xp_line == NULL) { return NULL; @@ -5323,15 +5311,10 @@ static void *call_user_expand_func(user_expand_func_T user_expand_func, expand_T args[1].vval.v_string = xp->xp_line; args[2].vval.v_number = xp->xp_col; - // Save the cmdline, we don't know what the function may do. - save_ccline = ccline; - ccline.cmdbuff = NULL; - ccline.cmdprompt = NULL; current_sctx = xp->xp_script_ctx; void *const ret = user_expand_func(xp->xp_arg, 3, args); - ccline = save_ccline; current_sctx = save_current_sctx; if (ccline.cmdbuff != NULL) { ccline.cmdbuff[ccline.cmdlen] = keep; @@ -5411,6 +5394,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 @@ -5912,10 +5924,8 @@ int get_history_idx(int histype) } -/* - * Get pointer to the command line info to use. cmdline_paste() may clear - * ccline and put the previous value in prev_ccline. - */ +/// Get pointer to the command line info to use. save_cmdline() may clear +/// ccline and put the previous value in ccline.prev_ccline. static struct cmdline_info *get_ccline_ptr(void) { if ((State & CMDLINE) == 0) { @@ -6315,6 +6325,11 @@ int hist_type2char(int type) return NUL; } +void cmdline_init(void) +{ + memset(&ccline, 0, sizeof(struct cmdline_info)); +} + /// Open a window on the current command line and history. Allow editing in /// the window. Returns when the window is closed. /// Returns: @@ -6323,7 +6338,6 @@ int hist_type2char(int type) /// K_IGNORE if editing continues static int open_cmdwin(void) { - struct cmdline_info save_ccline; bufref_T old_curbuf; bufref_T bufref; win_T *old_curwin = curwin; @@ -6424,9 +6438,6 @@ static int open_cmdwin(void) } redraw_later(curwin, SOME_VALID); - // Save the command line info, can be used recursively. - save_cmdline(&save_ccline); - // No Ex mode here! exmode_active = false; @@ -6464,8 +6475,6 @@ static int open_cmdwin(void) // Restore KeyTyped in case it is modified by autocommands KeyTyped = save_KeyTyped; - // Restore the command line info. - restore_cmdline(&save_ccline); cmdwin_type = 0; cmdwin_level = 0; @@ -6529,13 +6538,13 @@ 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 // set to 'wipe', autocommands may have closed other windows if (bufref_valid(&bufref) && bufref.br_buf != curbuf) { - close_buffer(NULL, bufref.br_buf, DOBUF_WIPE, false); + close_buffer(NULL, bufref.br_buf, DOBUF_WIPE, false, false); } // Restore window sizes. @@ -6547,11 +6556,19 @@ static int open_cmdwin(void) cmdmsg_rl = save_cmdmsg_rl; State = save_State; + may_trigger_modechanged(); setmouse(); return cmdwin_result; } +/// @return true if in the cmdwin, not editing the command line. +bool is_in_cmdwin(void) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT +{ + 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 f80a63560c..1235087500 100644 --- a/src/nvim/ex_session.c +++ b/src/nvim/ex_session.c @@ -28,7 +28,6 @@ #include "nvim/getchar.h" #include "nvim/globals.h" #include "nvim/keymap.h" -#include "nvim/misc1.h" #include "nvim/move.h" #include "nvim/option.h" #include "nvim/os/input.h" @@ -73,7 +72,7 @@ static int ses_winsizes(FILE *fd, int restore_size, win_T *tab_firstwin) n++; // restore height when not full height - if (wp->w_height + wp->w_status_height < topframe->fr_height + if (wp->w_height + wp->w_hsep_height + wp->w_status_height < topframe->fr_height && (fprintf(fd, "exe '%dresize ' . ((&lines * %" PRId64 " + %" PRId64 ") / %" PRId64 ")\n", @@ -99,10 +98,11 @@ static int ses_winsizes(FILE *fd, int restore_size, win_T *tab_firstwin) return OK; } -// Write commands to "fd" to recursively create windows for frame "fr", -// horizontally and vertically split. -// After the commands the last window in the frame is the current window. -// Returns FAIL when writing the commands to "fd" fails. +/// Write commands to "fd" to recursively create windows for frame "fr", +/// horizontally and vertically split. +/// After the commands the last window in the frame is the current window. +/// +/// @return FAIL when writing the commands to "fd" fails. static int ses_win_rec(FILE *fd, frame_T *fr) { frame_T *frc; @@ -145,8 +145,9 @@ static int ses_win_rec(FILE *fd, frame_T *fr) return OK; } -// Skip frames that don't contain windows we want to save in the Session. -// Returns NULL when there none. +/// Skip frames that don't contain windows we want to save in the Session. +/// +/// @return NULL when there none. static frame_T *ses_skipframe(frame_T *fr) { frame_T *frc; @@ -159,8 +160,8 @@ static frame_T *ses_skipframe(frame_T *fr) return frc; } -// Return true if frame "fr" has a window somewhere that we want to save in -// the Session. +/// @return true if frame "fr" has a window somewhere that we want to save in +/// the Session. static bool ses_do_frame(const frame_T *fr) FUNC_ATTR_NONNULL_ARG(1) { @@ -177,7 +178,7 @@ static bool ses_do_frame(const frame_T *fr) return false; } -/// Return non-zero if window "wp" is to be stored in the Session. +/// @return non-zero if window "wp" is to be stored in the Session. static int ses_do_win(win_T *wp) { if (wp->w_buffer->b_fname == NULL @@ -230,7 +231,7 @@ static int ses_arglist(FILE *fd, char *cmd, garray_T *gap, int fullname, unsigne return OK; } -/// Gets the buffer name for `buf`. +/// @return the buffer name for `buf`. static char *ses_get_fname(buf_T *buf, unsigned *flagp) { // Use the short file name if the current directory is known at the time @@ -250,7 +251,8 @@ static char *ses_get_fname(buf_T *buf, unsigned *flagp) /// Write a buffer name to the session file. /// Also ends the line, if "add_eol" is true. -/// Returns FAIL if writing fails. +/// +/// @return FAIL if writing fails. static int ses_fname(FILE *fd, buf_T *buf, unsigned *flagp, bool add_eol) { char *name = ses_get_fname(buf, flagp); @@ -261,11 +263,11 @@ static int ses_fname(FILE *fd, buf_T *buf, unsigned *flagp, bool add_eol) return OK; } -// Escapes a filename for session writing. -// Takes care of "slash" flag in 'sessionoptions' and escapes special -// characters. -// -// Returns allocated string or NULL. +/// Escapes a filename for session writing. +/// Takes care of "slash" flag in 'sessionoptions' and escapes special +/// characters. +/// +/// @return allocated string or NULL. static char *ses_escape_fname(char *name, unsigned *flagp) { char *p; @@ -284,10 +286,11 @@ static char *ses_escape_fname(char *name, unsigned *flagp) return p; } -// Write a file name to the session file. -// Takes care of the "slash" option in 'sessionoptions' and escapes special -// characters. -// Returns FAIL if writing fails. +/// Write a file name to the session file. +/// Takes care of the "slash" option in 'sessionoptions' and escapes special +/// characters. +/// +/// @return FAIL if writing fails. static int ses_put_fname(FILE *fd, char_u *name, unsigned *flagp) { char *p = ses_escape_fname((char *)name, flagp); @@ -339,14 +342,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). // @@ -354,7 +369,7 @@ static int put_view(FILE *fd, win_T *wp, int add_edit, unsigned *flagp, int curr // edit that buffer, to not lose folding information (:edit resets // folds in other buffers) if (fprintf(fd, - "if bufexists(\"%s\") | buffer %s | else | edit %s | endif\n" + "if bufexists(fnamemodify(\"%s\", \":p\")) | buffer %s | else | edit %s | endif\n" // Fixup :terminal buffer name. #7836 "if &buftype ==# 'terminal'\n" " silent file %s\n" @@ -574,12 +589,36 @@ static int makeopens(FILE *fd, char_u *dirnow) "if expand('%') == '' && !&modified && line('$') <= 1" " && getline(1) == ''\n" " let s:wipebuf = bufnr('%')\n" - "endif\n" - // Now save the current files, current buffer first. - "set shortmess=aoO\n") < 0) { + "endif\n") < 0) { return FAIL; } + // save 'shortmess' if not storing options + if ((ssop_flags & SSOP_OPTIONS) == 0) { + PUTLINE_FAIL("let s:shortmess_save = &shortmess"); + } + + // Now save the current files, current buffer first. + PUTLINE_FAIL("set shortmess=aoO"); + + // 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) { @@ -615,7 +654,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; } } @@ -793,25 +835,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. // @@ -825,15 +848,21 @@ static int makeopens(FILE *fd, char_u *dirnow) return FAIL; } - // Re-apply 'winheight', 'winwidth' and 'shortmess'. - if (fprintf(fd, - "set winheight=%" PRId64 " winwidth=%" PRId64 - " shortmess=%s\n", - (int64_t)p_wh, - (int64_t)p_wiw, - p_shm) < 0) { + // Re-apply 'winheight' and 'winwidth'. + if (fprintf(fd, "set winheight=%" PRId64 " winwidth=%" PRId64 "\n", + (int64_t)p_wh, (int64_t)p_wiw) < 0) { return FAIL; } + + // Restore 'shortmess'. + if (ssop_flags & SSOP_OPTIONS) { + if (fprintf(fd, "set shortmess=%s\n", p_shm) < 0) { + return FAIL; + } + } else { + PUTLINE_FAIL("let &shortmess = s:shortmess_save"); + } + if (tab_firstwin != NULL && tab_firstwin->w_next != NULL) { // Restore 'winminheight' and 'winminwidth'. PUTLINE_FAIL("let &winminheight = s:save_winminheight"); @@ -1039,7 +1068,7 @@ void ex_mkrc(exarg_T *eap) xfree(viewFile); } -/// Get the name of the view file for the current buffer. +/// @return the name of the view file for the current buffer. static char *get_view_file(int c) { if (curbuf->b_ffname == NULL) { @@ -1087,7 +1116,7 @@ static char *get_view_file(int c) return retval; } -// TODO(justinmk): remove this, not needed after 5ba3cecb68cd. +/// TODO(justinmk): remove this, not needed after 5ba3cecb68cd. int put_eol(FILE *fd) { if (putc('\n', fd) < 0) { @@ -1096,7 +1125,7 @@ int put_eol(FILE *fd) return OK; } -// TODO(justinmk): remove this, not needed after 5ba3cecb68cd. +/// TODO(justinmk): remove this, not needed after 5ba3cecb68cd. int put_line(FILE *fd, char *s) { if (fprintf(fd, "%s\n", s) < 0) { diff --git a/src/nvim/extmark.c b/src/nvim/extmark.c index c4d8f75a21..a3fcc7d784 100644 --- a/src/nvim/extmark.c +++ b/src/nvim/extmark.c @@ -48,28 +48,31 @@ # 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_has_sign(decor)) { + 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,74 +80,94 @@ 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) { if (kv_size(decor->virt_lines)) { buf->b_virt_line_blocks++; } + if (decor_has_sign(decor)) { + buf->b_signs++; + } + if (decor->sign_text) { + // TODO(lewis6991): smarter invalidation + buf_signcols_add_check(buf, NULL); + } decor_redraw(buf, row, end_row > -1 ? end_row : row, decor); } 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; } @@ -152,47 +175,38 @@ static bool extmark_setraw(buf_T *buf, uint64_t mark, int row, colnr_T col) return true; } -// Remove an extmark -// Returns 0 on missing id -bool extmark_del(buf_T *buf, uint64_t ns_id, uint64_t id) +/// Remove an extmark +/// +/// @return 0 on missing 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) +/// Free extmarks in a ns between lines +/// if ns = 0, it means clear all namespaces +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 +215,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 +275,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); @@ -284,13 +288,14 @@ bool extmark_clear(buf_T *buf, uint64_t ns_id, int l_row, colnr_T l_col, int u_r return marks_cleared; } -// Returns the position of marks between a range, -// marks found at the start or end index will be included, -// if upper_lnum or upper_col are negative the buffer -// 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, +/// @return the position of marks between a range, +/// marks found at the start or end index will be included. +/// +/// if upper_lnum or upper_col are negative the buffer +/// 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, 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 +305,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) { @@ -335,72 +336,62 @@ next_mark: return array; } -// Lookup an extmark by id -ExtmarkInfo extmark_from_id(buf_T *buf, uint64_t ns_id, uint64_t id) +/// Lookup an extmark by 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; } -// free extmarks from the buffer +/// free extmarks from the buffer void extmark_free_all(buf_T *buf) { if (!map_size(buf->b_extmark_ns)) { 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); } -// Save info for undo/redo of set marks +/// Save info for undo/redo of set marks static void u_extmark_set(buf_T *buf, uint64_t mark, int row, colnr_T col) { u_header_T *uhp = u_force_get_undo_header(buf); @@ -437,16 +428,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; @@ -510,7 +501,7 @@ void extmark_apply_undo(ExtmarkUndoObject undo_info, bool undo) } -// Adjust extmark row for inserted/deleted rows (columns stay fixed). +/// Adjust extmark row for inserted/deleted rows (columns stay fixed). void extmark_adjust(buf_T *buf, linenr_T line1, linenr_T line2, long amount, long amount_after, ExtmarkOp undo) { diff --git a/src/nvim/extmark.h b/src/nvim/extmark.h index c70db9f7aa..b856a1148f 100644 --- a/src/nvim/extmark.h +++ b/src/nvim/extmark.h @@ -2,6 +2,7 @@ #define NVIM_EXTMARK_H #include "nvim/buffer_defs.h" +#include "nvim/decoration.h" #include "nvim/extmark_defs.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 3a9acbd61c..15f1d3d065 100644 --- a/src/nvim/file_search.c +++ b/src/nvim/file_search.c @@ -57,7 +57,6 @@ #include "nvim/globals.h" #include "nvim/memory.h" #include "nvim/message.h" -#include "nvim/misc1.h" #include "nvim/option.h" #include "nvim/os/fs_defs.h" #include "nvim/os/input.h" @@ -484,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); @@ -524,9 +529,7 @@ error_return: return NULL; } -/* - * Get the stopdir string. Check that ';' is not escaped. - */ +/// @return the stopdir string. Check that ';' is not escaped. char_u *vim_findfile_stopdir(char_u *buf) { char_u *r_ptr = buf; @@ -549,9 +552,7 @@ char_u *vim_findfile_stopdir(char_u *buf) return r_ptr; } -/* - * Clean up the given search context. Can handle a NULL pointer. - */ +/// Clean up the given search context. Can handle a NULL pointer. void vim_findfile_cleanup(void *ctx) { if (ctx == NULL) { @@ -563,18 +564,19 @@ void vim_findfile_cleanup(void *ctx) xfree(ctx); } -/* - * Find a file in a search context. - * The search context was created with vim_findfile_init() above. - * Return a pointer to an allocated file name or NULL if nothing found. - * To get all matching files call this function until you get NULL. - * - * If the passed search_context is NULL, NULL is returned. - * - * The search algorithm is depth first. To change this replace the - * stack with a list (don't forget to leave partly searched directories on the - * top of the list). - */ +/// Find a file in a search context. +/// The search context was created with vim_findfile_init() above. +/// +/// To get all matching files call this function until you get NULL. +/// +/// If the passed search_context is NULL, NULL is returned. +/// +/// The search algorithm is depth first. To change this replace the +/// stack with a list (don't forget to leave partly searched directories on the +/// top of the list). +/// +/// @return a pointer to an allocated file name or, +/// NULL if nothing found. char_u *vim_findfile(void *search_ctx_arg) { char_u *file_path; @@ -608,7 +610,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; @@ -994,10 +996,8 @@ fail: return NULL; } -/* - * Free the list of lists of visited files and directories - * Can handle it if the passed search_context is NULL; - */ +/// Free the list of lists of visited files and directories +/// Can handle it if the passed search_context is NULL; void vim_findfile_free_visited(void *search_ctx_arg) { ff_search_ctx_T *search_ctx; @@ -1039,10 +1039,8 @@ static void ff_free_visited_list(ff_visited_T *vl) vl = NULL; } -/* - * Returns the already visited list for the given filename. If none is found it - * allocates a new one. - */ +/// @return the already visited list for the given filename. If none is found it +/// allocates a new one. static ff_visited_list_hdr_T *ff_get_visited_list(char_u *filename, ff_visited_list_hdr_T **list_headp) { @@ -1089,13 +1087,13 @@ static ff_visited_list_hdr_T *ff_get_visited_list(char_u *filename, return retptr; } -// Check if two wildcard paths are equal. -// They are equal if: -// - both paths are NULL -// - they have the same length -// - char by char comparison is OK -// - the only differences are in the counters behind a '**', so -// '**\20' is equal to '**\24' +/// Check if two wildcard paths are equal. +/// They are equal if: +/// - both paths are NULL +/// - they have the same length +/// - char by char comparison is OK +/// - the only differences are in the counters behind a '**', so +/// '**\20' is equal to '**\24' static bool ff_wc_equal(char_u *s1, char_u *s2) { int i, j; @@ -1129,18 +1127,17 @@ static bool ff_wc_equal(char_u *s1, char_u *s2) return s1[i] == s2[j]; } -/* - * maintains the list of already visited files and dirs - * returns FAIL if the given file/dir is already in the list - * returns OK if it is newly added - */ +/// maintains the list of already visited files and dirs +/// +/// @return FAIL if the given file/dir is already in the list or, +/// OK if it is newly added static int ff_check_visited(ff_visited_T **visited_list, char_u *fname, char_u *wc_path) { ff_visited_T *vp; 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); @@ -1191,9 +1188,7 @@ static int ff_check_visited(ff_visited_T **visited_list, char_u *fname, char_u * return OK; } -/* - * create stack element from given path pieces - */ +/// create stack element from given path pieces static ff_stack_T *ff_create_stack_element(char_u *fix_part, char_u *wc_part, int level, int star_star_empty) { @@ -1221,9 +1216,7 @@ static ff_stack_T *ff_create_stack_element(char_u *fix_part, char_u *wc_part, in return new; } -/* - * Push a dir on the directory stack. - */ +/// Push a dir on the directory stack. static void ff_push(ff_search_ctx_T *search_ctx, ff_stack_T *stack_ptr) { /* check for NULL pointer, not to return an error to the user, but @@ -1234,10 +1227,9 @@ static void ff_push(ff_search_ctx_T *search_ctx, ff_stack_T *stack_ptr) } } -/* - * Pop a dir from the directory stack. - * Returns NULL if stack is empty. - */ +/// Pop a dir from the directory stack. +/// +/// @return NULL if stack is empty. static ff_stack_T *ff_pop(ff_search_ctx_T *search_ctx) { ff_stack_T *sptr; @@ -1250,9 +1242,7 @@ static ff_stack_T *ff_pop(ff_search_ctx_T *search_ctx) return sptr; } -/* - * free the given stack element - */ +/// free the given stack element static void ff_free_stack_element(ff_stack_T *const stack_ptr) { if (stack_ptr == NULL) { @@ -1270,9 +1260,7 @@ static void ff_free_stack_element(ff_stack_T *const stack_ptr) xfree(stack_ptr); } -/* - * Clear the search context, but NOT the visited list. - */ +/// Clear the search context, but NOT the visited list. static void ff_clear(ff_search_ctx_T *search_ctx) { ff_stack_T *sptr; @@ -1306,10 +1294,9 @@ static void ff_clear(ff_search_ctx_T *search_ctx) search_ctx->ffsc_level = 0; } -/* - * check if the given path is in the stopdirs - * returns TRUE if yes else FALSE - */ +/// check if the given path is in the stopdirs +/// +/// @return TRUE if yes else FALSE static int ff_path_in_stoplist(char_u *path, int path_len, char_u **stopdirs_v) { int i = 0; @@ -1434,7 +1421,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; @@ -1591,11 +1582,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; @@ -1603,7 +1596,8 @@ void do_autocmd_dirchanged(char *new_dir, CdScope scope, CdCause cause) recursive = true; - dict_T *dict = get_vim_var_dict(VV_EVENT); + save_v_event_T save_v_event; + dict_T *dict = get_v_event(&save_v_event); char buf[8]; switch (scope) { @@ -1628,8 +1622,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,17 +1643,17 @@ 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); - tv_dict_clear(dict); + restore_v_event(dict, &save_v_event); recursive = false; } /// Change to a file's directory. /// Caller must call shorten_fnames()! -/// @return OK or FAIL +/// +/// @return OK or FAIL int vim_chdirfile(char_u *fname, CdCause cause) { char dir[MAXPATHL]; @@ -1667,14 +1665,23 @@ int vim_chdirfile(char_u *fname, CdCause cause) NameBuff[0] = NUL; } - if (os_chdir(dir) == 0) { - if (cause != kCdCauseOther && pathcmp(dir, (char *)NameBuff, -1) != 0) { - do_autocmd_dirchanged(dir, kCdScopeWindow, cause); - } - } else { + if (pathcmp(dir, (char *)NameBuff, -1) == 0) { + // nothing to do + 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, false); + } + return OK; } diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c index 7573064fa9..d4407b4982 100644 --- a/src/nvim/fileio.c +++ b/src/nvim/fileio.c @@ -3,6 +3,8 @@ // fileio.c: read from and write to a file +// uncrustify:off + #include <assert.h> #include <errno.h> #include <fcntl.h> @@ -20,6 +22,7 @@ #include "nvim/diff.h" #include "nvim/edit.h" #include "nvim/eval/userfunc.h" +#include "nvim/eval/typval.h" #include "nvim/ex_cmds.h" #include "nvim/ex_docmd.h" #include "nvim/ex_eval.h" @@ -30,12 +33,12 @@ #include "nvim/getchar.h" #include "nvim/hashtab.h" #include "nvim/iconv.h" +#include "nvim/input.h" #include "nvim/mbyte.h" #include "nvim/memfile.h" #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/message.h" -#include "nvim/misc1.h" #include "nvim/move.h" #include "nvim/normal.h" #include "nvim/option.h" @@ -77,7 +80,7 @@ #define FIO_ENDIAN_L 0x80 // little endian #define FIO_NOCONVERT 0x2000 // skip encoding conversion #define FIO_UCSBOM 0x4000 // check for BOM at start of file -#define FIO_ALL -1 // allow all formats +#define FIO_ALL (-1) // allow all formats /* When converting, a read() or write() may leave some bytes to be converted * for the next call. The value is guessed... */ @@ -100,8 +103,8 @@ struct bw_info { char_u bw_rest[CONV_RESTLEN]; // not converted bytes int bw_restlen; // nr of bytes in bw_rest[] int bw_first; // first write call - char_u *bw_conv_buf; // buffer for writing converted chars - int bw_conv_buflen; // size of bw_conv_buf + char_u *bw_conv_buf; // buffer for writing converted chars + size_t bw_conv_buflen; // size of bw_conv_buf int bw_conv_error; // set for conversion error linenr_T bw_conv_error_lnum; // first line with error or zero linenr_T bw_start_lnum; // line number at start of buffer @@ -242,6 +245,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 +302,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 +335,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) { @@ -362,7 +362,7 @@ int readfile(char_u *fname, char_u *sfname, linenr_T from, linenr_T lines_to_ski filemess(curbuf, fname, (char_u *)_(msg_is_a_directory), 0); msg_end(); msg_scroll = msg_save; - return FAIL; + return NOTDONE; } } @@ -408,6 +408,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 +425,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 +579,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 +620,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; @@ -1012,27 +1015,27 @@ retry: } read_buf_col += n; break; - } else { - // Append whole line and new-line. Change NL - // to NUL to reverse the effect done below. - for (ni = 0; ni < n; ni++) { - if (p[ni] == NL) { - ptr[tlen++] = NUL; - } else { - ptr[tlen++] = p[ni]; - } + } + + // Append whole line and new-line. Change NL + // to NUL to reverse the effect done below. + for (ni = 0; ni < n; ni++) { + if (p[ni] == NL) { + ptr[tlen++] = NUL; + } else { + ptr[tlen++] = p[ni]; } - ptr[tlen++] = NL; - read_buf_col = 0; - if (++read_buf_lnum > from) { - // When the last line didn't have an - // end-of-line don't add it now either. - if (!curbuf->b_p_eol) { - --tlen; - } - size = tlen; - break; + } + ptr[tlen++] = NL; + read_buf_col = 0; + if (++read_buf_lnum > from) { + // When the last line didn't have an + // end-of-line don't add it now either. + if (!curbuf->b_p_eol) { + tlen--; } + size = tlen; + break; } } } @@ -1360,6 +1363,10 @@ retry: u8c += (unsigned)(*--p) << 16; u8c += (unsigned)(*--p) << 24; } + // Replace characters over INT_MAX with Unicode replacement character + if (u8c > INT_MAX) { + u8c = 0xfffd; + } } else { // UTF-8 if (*--p < 0x80) { u8c = *p; @@ -1847,7 +1854,7 @@ failed: msg_scrolled_ign = true; if (!read_stdin && !read_buffer) { - p = (char_u *)msg_trunc_attr((char *)IObuff, FALSE, 0); + p = (char_u *)msg_trunc_attr((char *)IObuff, false, 0); } if (read_stdin || read_buffer || restart_edit != 0 @@ -1884,13 +1891,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; @@ -2009,10 +2016,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 { @@ -2029,9 +2034,7 @@ void prep_exarg(exarg_T *eap, const buf_T *buf) eap->forceit = FALSE; } -/* - * Set default or forced 'fileformat' and 'binary'. - */ +/// Set default or forced 'fileformat' and 'binary'. void set_file_options(int set_options, exarg_T *eap) { // set default 'fileformat' @@ -2052,9 +2055,7 @@ void set_file_options(int set_options, exarg_T *eap) } } -/* - * Set forced 'fileencoding'. - */ +/// Set forced 'fileencoding'. void set_forced_fenc(exarg_T *eap) { if (eap->force_enc != 0) { @@ -2064,12 +2065,12 @@ void set_forced_fenc(exarg_T *eap) } } -// Find next fileencoding to use from 'fileencodings'. -// "pp" points to fenc_next. It's advanced to the next item. -// When there are no more items, an empty string is returned and *pp is set to -// NULL. -// When *pp is not set to NULL, the result is in allocated memory and "alloced" -// is set to true. +/// Find next fileencoding to use from 'fileencodings'. +/// "pp" points to fenc_next. It's advanced to the next item. +/// When there are no more items, an empty string is returned and *pp is set to +/// NULL. +/// When *pp is not set to NULL, the result is in allocated memory and "alloced" +/// is set to true. static char_u *next_fenc(char_u **pp, bool *alloced) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET { @@ -2145,10 +2146,8 @@ static char_u *readfile_charconvert(char_u *fname, char_u *fenc, int *fdp) } -/* - * Read marks for the current buffer from the ShaDa file, when we support - * buffer marks and the buffer has a name. - */ +/// Read marks for the current buffer from the ShaDa file, when we support +/// buffer marks and the buffer has a name. static void check_marks_read(void) { if (!curbuf->b_marks_read && get_shada_parameter('\'') > 0 @@ -2248,6 +2247,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; @@ -2428,7 +2429,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")); @@ -2509,6 +2516,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 @@ -3304,7 +3316,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; @@ -3681,11 +3693,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; } } } @@ -3743,10 +3756,8 @@ nofail: #undef SET_ERRMSG_NUM } -/* - * Set the name of the current buffer. Use when the buffer doesn't have a - * name and a ":r" or ":w" command with a file name is used. - */ +/// Set the name of the current buffer. Use when the buffer doesn't have a +/// name and a ":r" or ":w" command with a file name is used. static int set_rw_fname(char_u *fname, char_u *sfname) { buf_T *buf = curbuf; @@ -3780,7 +3791,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); @@ -3836,9 +3847,7 @@ static bool msg_add_fileformat(int eol_type) return false; } -/* - * Append line and character count to IObuff. - */ +/// Append line and character count to IObuff. void msg_add_lines(int insert_space, long lnum, off_T nchars) { char_u *p; @@ -3849,7 +3858,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), @@ -3857,30 +3866,25 @@ 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); } } -/* - * Append message for missing line separator to IObuff. - */ +/// Append message for missing line separator to IObuff. static void msg_add_eol(void) { STRCAT(IObuff, shortmess(SHM_LAST) ? _("[noeol]") : _("[Incomplete last line]")); } -/* - * Check modification time of file, before writing to it. - * The size isn't checked, because using a tool like "gzip" takes care of - * using the same timestamp but can't set the size. - */ +/// Check modification time of file, before writing to it. +/// The size isn't checked, because using a tool like "gzip" takes care of +/// using the same timestamp but can't set the size. 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. @@ -3894,28 +3898,24 @@ 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 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!!! + || file_info->stat.st_mtim.tv_sec - mtime > 1 + || mtime - file_info->stat.st_mtim.tv_sec > 1; #else - return t1 != t2; + || (long)file_info->stat.st_mtim.tv_sec != mtime; #endif } -/* - * Call write() to write a number of bytes to the file. - * Handles 'encoding' conversion. - * - * Return FAIL for failure, OK otherwise. - */ +/// Call write() to write a number of bytes to the file. +/// Handles 'encoding' conversion. +/// +/// @return FAIL for failure, OK otherwise. static int buf_write_bytes(struct bw_info *ip) { int wlen; @@ -4243,12 +4243,11 @@ static int get_fio_flags(const char_u *name) } -/* - * Check for a Unicode BOM (Byte Order Mark) at the start of p[size]. - * "size" must be at least 2. - * Return the name of the encoding and set "*lenp" to the length. - * Returns NULL when no BOM found. - */ +/// Check for a Unicode BOM (Byte Order Mark) at the start of p[size]. +/// "size" must be at least 2. +/// +/// @return the name of the encoding and set "*lenp" to the length or, +/// NULL when no BOM found. static char_u *check_for_bom(char_u *p, long size, int *lenp, int flags) { char *name = NULL; @@ -4289,10 +4288,9 @@ static char_u *check_for_bom(char_u *p, long size, int *lenp, int flags) return (char_u *)name; } -/* - * Generate a BOM in "buf[4]" for encoding "name". - * Return the length of the BOM (zero when no BOM). - */ +/// Generate a BOM in "buf[4]" for encoding "name". +/// +/// @return the length of the BOM (zero when no BOM). static int make_bom(char_u *buf, char_u *name) { int flags; @@ -4317,10 +4315,12 @@ static int make_bom(char_u *buf, char_u *name) } /// Shorten filename of a buffer. -/// When "force" is TRUE: Use full path from now on for files currently being -/// edited, both for file name and swap file name. Try to shorten the file -/// names a bit, if safe to do so. -/// When "force" is FALSE: Only try to shorten absolute file names. +/// +/// @param force when TRUE: Use full path from now on for files currently being +/// edited, both for file name and swap file name. Try to shorten the file +/// names a bit, if safe to do so. +/// when FALSE: Only try to shorten absolute file names. +/// /// For buffers that have buftype "nofile" or "scratch": never change the file /// name. void shorten_buf_fname(buf_T *buf, char_u *dirname, int force) @@ -4498,7 +4498,8 @@ bool vim_fgets(char_u *buf, int size, FILE *fp) FUNC_ATTR_NONNULL_ALL } /// Read 2 bytes from "fd" and turn them into an int, MSB first. -/// Returns -1 when encountering EOF. +/// +/// @return -1 when encountering EOF. int get2c(FILE *fd) { const int n = getc(fd); @@ -4513,7 +4514,8 @@ int get2c(FILE *fd) } /// Read 3 bytes from "fd" and turn them into an int, MSB first. -/// Returns -1 when encountering EOF. +/// +/// @return -1 when encountering EOF. int get3c(FILE *fd) { int n = getc(fd); @@ -4533,7 +4535,8 @@ int get3c(FILE *fd) } /// Read 4 bytes from "fd" and turn them into an int, MSB first. -/// Returns -1 when encountering EOF. +/// +/// @return -1 when encountering EOF. int get4c(FILE *fd) { // Use unsigned rather than int otherwise result is undefined @@ -4564,7 +4567,8 @@ int get4c(FILE *fd) } /// Read 8 bytes from `fd` and turn them into a time_t, MSB first. -/// Returns -1 when encountering EOF. +/// +/// @return -1 when encountering EOF. time_t get8ctime(FILE *fd) { time_t n = 0; @@ -4580,7 +4584,8 @@ time_t get8ctime(FILE *fd) } /// Reads a string of length "cnt" from "fd" into allocated memory. -/// @return pointer to the string or NULL when unable to read that many bytes. +/// +/// @return pointer to the string or NULL when unable to read that many bytes. char *read_string(FILE *fd, size_t cnt) { char *str = xmallocz(cnt); @@ -4596,7 +4601,8 @@ char *read_string(FILE *fd, size_t cnt) } /// Writes a number to file "fd", most significant bit first, in "len" bytes. -/// @returns false in case of an error. +/// +/// @return false in case of an error. bool put_bytes(FILE *fd, uintmax_t number, size_t len) { assert(len > 0); @@ -4609,7 +4615,8 @@ bool put_bytes(FILE *fd, uintmax_t number, size_t len) } /// Writes time_t to file "fd" in 8 bytes. -/// @returns FAIL when the write failed. +/// +/// @return FAIL when the write failed. int put_time(FILE *fd, time_t time_) { uint8_t buf[8]; @@ -4620,7 +4627,7 @@ int put_time(FILE *fd, time_t time_) /// os_rename() only works if both files are on the same file system, this /// function will (attempts to?) copy the file across if rename fails -- webb /// -/// @return -1 for failure, 0 for success +/// @return -1 for failure, 0 for success int vim_rename(const char_u *from, const char_u *to) FUNC_ATTR_NONNULL_ALL { @@ -4845,11 +4852,10 @@ int check_timestamps(int focus) return didit; } -/* - * Move all the lines from buffer "frombuf" to buffer "tobuf". - * Return OK or FAIL. When FAIL "tobuf" is incomplete and/or "frombuf" is not - * empty. - */ +/// Move all the lines from buffer "frombuf" to buffer "tobuf". +/// +/// @return OK or FAIL. +/// When FAIL "tobuf" is incomplete and/or "frombuf" is not empty. static int move_lines(buf_T *frombuf, buf_T *tobuf) { buf_T *tbuf = curbuf; @@ -4886,13 +4892,12 @@ 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 or, +/// 2 if a message has been displayed or, +/// 0 otherwise. int buf_check_timestamp(buf_T *buf) FUNC_ATTR_NONNULL_ALL { @@ -4901,7 +4906,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; @@ -4929,7 +4938,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; @@ -4946,15 +4955,14 @@ int buf_check_timestamp(buf_T *buf) buf_store_file_info(buf, &file_info); } - // Don't do anything for a directory. Might contain the file - // explorer. if (os_isdir(buf->b_fname)) { + // Don't do anything for a directory. Might contain the file explorer. } else if ((buf->b_p_ar >= 0 ? buf->b_p_ar : p_ar) && !bufIsChanged(buf) && file_info_ok) { // 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"; @@ -4985,7 +4993,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 { @@ -5020,6 +5030,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; } } } @@ -5048,9 +5059,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) { @@ -5084,9 +5101,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]; @@ -5104,13 +5121,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; @@ -5125,11 +5140,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; @@ -5248,14 +5267,13 @@ 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; } -/* - * Adjust the line with missing eol, used for the next write. - * Used for do_filter(), when the input lines for the filter are deleted. - */ +/// Adjust the line with missing eol, used for the next write. +/// Used for do_filter(), when the input lines for the filter are deleted. void write_lnum_adjust(linenr_T offset) { if (curbuf->b_no_eol_lnum != 0) { // only if there is a missing eol @@ -5326,35 +5344,85 @@ static void vim_maketempdir(void) (void)umask(umask_save); } +/// Core part of "readdir()" function. +/// Retrieve the list of files/directories of "path" into "gap". +/// +/// @return OK for success, FAIL for failure. +int readdir_core(garray_T *gap, const char *path, void *context, CheckItem checkitem) + FUNC_ATTR_NONNULL_ARG(1, 2) +{ + ga_init(gap, (int)sizeof(char *), 20); + + Directory dir; + if (!os_scandir(&dir, path)) { + smsg(_(e_notopen), path); + return FAIL; + } + + for (;;) { + const char *p = os_scandir_next(&dir); + if (p == NULL) { + break; + } + + bool ignore = (p[0] == '.' && (p[1] == NUL || (p[1] == '.' && p[2] == NUL))); + if (!ignore && checkitem != NULL) { + varnumber_T r = checkitem(context, p); + if (r < 0) { + break; + } + if (r == 0) { + ignore = true; + } + } + + if (!ignore) { + ga_grow(gap, 1); + ((char **)gap->ga_data)[gap->ga_len++] = xstrdup(p); + } + } + + os_closedir(&dir); + + if (gap->ga_len > 0) { + sort_strings((char_u **)gap->ga_data, gap->ga_len); + } + + return OK; +} + /// Delete "name" and everything in it, recursively. -/// @param name The path which should be deleted. -/// @return 0 for success, -1 if some file was not deleted. +/// +/// @param name The path which should be deleted. +/// +/// @return 0 for success, -1 if some file was not deleted. int delete_recursive(const char *name) + FUNC_ATTR_NONNULL_ALL { int result = 0; if (os_isrealdir(name)) { - snprintf((char *)NameBuff, MAXPATHL, "%s/*", name); // NOLINT - - char_u **files; - int file_count; - char_u *exp = vim_strsave(NameBuff); - if (gen_expand_wildcards(1, &exp, &file_count, &files, - EW_DIR | EW_FILE | EW_SILENT | EW_ALLLINKS - | EW_DODOT | EW_EMPTYOK) == OK) { - for (int i = 0; i < file_count; i++) { - if (delete_recursive((const char *)files[i]) != 0) { + char *exp = xstrdup(name); + garray_T ga; + if (readdir_core(&ga, exp, NULL, NULL) == OK) { + for (int i = 0; i < ga.ga_len; i++) { + vim_snprintf((char *)NameBuff, MAXPATHL, "%s/%s", exp, ((char_u **)ga.ga_data)[i]); + if (delete_recursive((const char *)NameBuff) != 0) { + // Remember the failure but continue deleting any further + // entries. result = -1; } } - FreeWild(file_count, files); + ga_clear_strings(&ga); + if (os_rmdir(exp) != 0) { + result = -1; + } } else { result = -1; } - xfree(exp); - os_rmdir(name); } else { + // Delete symlink only. result = os_remove(name) == 0 ? 0 : -1; } @@ -5372,8 +5440,8 @@ void vim_deltempdir(void) } } -/// Get the name of temp directory. This directory would be created on the first -/// call to this function. +/// @return the name of temp directory. This directory would be created on the first +/// call to this function. char_u *vim_gettempdir(void) { if (vim_tempdir == NULL) { @@ -5407,8 +5475,8 @@ static bool vim_settempdir(char *tempdir) /// /// @note The temp file is NOT created. /// -/// @return pointer to the temp file name or NULL if Neovim can't create -/// temporary directory for its own temporary files. +/// @return pointer to the temp file name or NULL if Neovim can't create +/// temporary directory for its own temporary files. char_u *vim_tempname(void) { // Temp filename counter. @@ -5719,10 +5787,9 @@ char_u *file_pat_to_reg_pat(const char_u *pat, const char_u *pat_end, char *allo } #if defined(EINTR) -/* - * Version of read() that retries when interrupted by EINTR (possibly - * by a SIGWINCH). - */ + +/// Version of read() that retries when interrupted by EINTR (possibly +/// by a SIGWINCH). long read_eintr(int fd, void *buf, size_t bufsize) { long ret; @@ -5736,10 +5803,8 @@ long read_eintr(int fd, void *buf, size_t bufsize) return ret; } -/* - * Version of write() that retries when interrupted by EINTR (possibly - * by a SIGWINCH). - */ +/// Version of write() that retries when interrupted by EINTR (possibly +/// by a SIGWINCH). long write_eintr(int fd, void *buf, size_t bufsize) { long ret = 0; diff --git a/src/nvim/fileio.h b/src/nvim/fileio.h index 186d0b90ab..62d8b6142e 100644 --- a/src/nvim/fileio.h +++ b/src/nvim/fileio.h @@ -3,6 +3,8 @@ #include "nvim/autocmd.h" #include "nvim/buffer_defs.h" +#include "nvim/eval/typval.h" +#include "nvim/garray.h" #include "nvim/os/os.h" // Values for readfile() flags @@ -17,6 +19,8 @@ #define READ_STRING(x, y) (char_u *)read_string((x), (size_t)(y)) +typedef varnumber_T (*CheckItem)(void *expr, const char *name); + #ifdef INCLUDE_GENERATED_DECLARATIONS // Events for autocommands # include "fileio.h.generated.h" diff --git a/src/nvim/fold.c b/src/nvim/fold.c index 4a8be7a31b..3643ceb460 100644 --- a/src/nvim/fold.c +++ b/src/nvim/fold.c @@ -28,10 +28,10 @@ #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/message.h" -#include "nvim/misc1.h" #include "nvim/move.h" #include "nvim/ops.h" #include "nvim/option.h" +#include "nvim/os/input.h" #include "nvim/plines.h" #include "nvim/screen.h" #include "nvim/strings.h" @@ -121,9 +121,7 @@ static size_t foldendmarkerlen; // Exported folding functions. {{{1 // copyFoldingState() {{{2 -/* - * Copy that folding state from window "wp_from" to window "wp_to". - */ +/// Copy that folding state from window "wp_from" to window "wp_to". void copyFoldingState(win_T *wp_from, win_T *wp_to) { wp_to->w_fold_manual = wp_from->w_fold_manual; @@ -132,9 +130,7 @@ void copyFoldingState(win_T *wp_from, win_T *wp_to) } // hasAnyFolding() {{{2 -/* - * Return TRUE if there may be folded lines in the current window. - */ +/// @return TRUE if there may be folded lines in the current window. int hasAnyFolding(win_T *win) { // very simple now, but can become more complex later @@ -259,9 +255,7 @@ bool hasFoldingWin(win_T *const win, const linenr_T lnum, linenr_T *const firstp } // foldLevel() {{{2 -/* - * Return fold level at line number "lnum" in the current window. - */ +/// @return fold level at line number "lnum" in the current window. int foldLevel(linenr_T lnum) { // While updating the folds lines between invalid_top and invalid_bot have @@ -283,15 +277,16 @@ int foldLevel(linenr_T lnum) } // lineFolded() {{{2 -// Low level function to check if a line is folded. Doesn't use any caching. -// Return true if line is folded. -// Return false if line is not folded. +/// Low level function to check if a line is folded. Doesn't use any caching. +/// +/// @return true if line is folded or, +/// false if line is not folded. bool lineFolded(win_T *const win, const linenr_T lnum) { return fold_info(win, lnum).fi_lines != 0; } -/// fold_info() {{{2 +// fold_info() {{{2 /// /// Count the number of lines that are folded at line number "lnum". /// Normally "lnum" is the first line of a possible fold, and the returned @@ -316,61 +311,49 @@ foldinfo_T fold_info(win_T *win, linenr_T lnum) } // foldmethodIsManual() {{{2 -/* - * Return TRUE if 'foldmethod' is "manual" - */ +/// @return TRUE if 'foldmethod' is "manual" int foldmethodIsManual(win_T *wp) { return wp->w_p_fdm[3] == 'u'; } // foldmethodIsIndent() {{{2 -/* - * Return TRUE if 'foldmethod' is "indent" - */ +/// @return TRUE if 'foldmethod' is "indent" int foldmethodIsIndent(win_T *wp) { return wp->w_p_fdm[0] == 'i'; } // foldmethodIsExpr() {{{2 -/* - * Return TRUE if 'foldmethod' is "expr" - */ +/// @return TRUE if 'foldmethod' is "expr" int foldmethodIsExpr(win_T *wp) { return wp->w_p_fdm[1] == 'x'; } // foldmethodIsMarker() {{{2 -/* - * Return TRUE if 'foldmethod' is "marker" - */ +/// @return TRUE if 'foldmethod' is "marker" int foldmethodIsMarker(win_T *wp) { return wp->w_p_fdm[2] == 'r'; } // foldmethodIsSyntax() {{{2 -/* - * Return TRUE if 'foldmethod' is "syntax" - */ +/// @return TRUE if 'foldmethod' is "syntax" int foldmethodIsSyntax(win_T *wp) { return wp->w_p_fdm[0] == 's'; } // foldmethodIsDiff() {{{2 -/* - * Return TRUE if 'foldmethod' is "diff" - */ +/// @return TRUE if 'foldmethod' is "diff" int foldmethodIsDiff(win_T *wp) { return wp->w_p_fdm[0] == 'd'; } // closeFold() {{{2 -/// Close fold for current window at line "lnum". +/// Close fold for current window at position "pos". /// Repeat "count" times. void closeFold(pos_T pos, long count) { @@ -378,9 +361,7 @@ void closeFold(pos_T pos, long count) } // closeFoldRecurse() {{{2 -/* - * Close fold for current window at line "lnum" recursively. - */ +/// Close fold for current window at position `pos` recursively. void closeFoldRecurse(pos_T pos) { (void)setManualFold(pos, false, true, NULL); @@ -427,28 +408,22 @@ void opFoldRange(pos_T firstpos, pos_T lastpos, int opening, int recurse, int ha } // openFold() {{{2 -/* - * Open fold for current window at line "lnum". - * Repeat "count" times. - */ +/// Open fold for current window at position "pos". +/// Repeat "count" times. void openFold(pos_T pos, long count) { setFoldRepeat(pos, count, true); } // openFoldRecurse() {{{2 -/* - * Open fold for current window at line "lnum" recursively. - */ +/// Open fold for current window at position `pos` recursively. void openFoldRecurse(pos_T pos) { (void)setManualFold(pos, true, true, NULL); } // foldOpenCursor() {{{2 -/* - * Open folds until the cursor line is not in a closed fold. - */ +/// Open folds until the cursor line is not in a closed fold. void foldOpenCursor(void) { int done; @@ -466,9 +441,7 @@ void foldOpenCursor(void) } // newFoldLevel() {{{2 -/* - * Set new foldlevel for current window. - */ +/// Set new foldlevel for current window. void newFoldLevel(void) { newFoldLevelWin(curwin); @@ -505,9 +478,7 @@ static void newFoldLevelWin(win_T *wp) } // foldCheckClose() {{{2 -/* - * Apply 'foldlevel' to all folds that don't contain the cursor. - */ +/// Apply 'foldlevel' to all folds that don't contain the cursor. void foldCheckClose(void) { if (*p_fcl != NUL) { // can only be "all" right now @@ -543,8 +514,8 @@ static int checkCloseRec(garray_T *gap, linenr_T lnum, int level) } // foldCreateAllowed() {{{2 -/// Return TRUE if it's allowed to manually create or delete a fold. -/// Give an error message and return FALSE if not. +/// @return TRUE if it's allowed to manually create or delete a fold or, +/// give an error message and return FALSE if not. int foldManualAllowed(bool create) { if (foldmethodIsManual(curwin) || foldmethodIsMarker(curwin)) { @@ -790,9 +761,7 @@ void deleteFold(win_T *const wp, const linenr_T start, const linenr_T end, const } // clearFolding() {{{2 -/* - * Remove all folding for window "win". - */ +/// Remove all folding for window "win". void clearFolding(win_T *win) { deleteFoldRecurse(win->w_buffer, &win->w_folds); @@ -800,12 +769,10 @@ void clearFolding(win_T *win) } // foldUpdate() {{{2 -/* - * Update folds for changes in the buffer of a window. - * Note that inserted/deleted lines must have already been taken care of by - * calling foldMarkAdjust(). - * The changes in lines from top to bot (inclusive). - */ +/// Update folds for changes in the buffer of a window. +/// Note that inserted/deleted lines must have already been taken care of by +/// calling foldMarkAdjust(). +/// The changes in lines from top to bot (inclusive). void foldUpdate(win_T *wp, linenr_T top, linenr_T bot) { if (compl_busy || State & INSERT) { @@ -856,12 +823,10 @@ void foldUpdateAfterInsert(void) } // foldUpdateAll() {{{2 -/* - * Update all lines in a window for folding. - * Used when a fold setting changes or after reloading the buffer. - * The actual updating is postponed until fold info is used, to avoid doing - * every time a setting is changed or a syntax item is added. - */ +/// Update all lines in a window for folding. +/// Used when a fold setting changes or after reloading the buffer. +/// The actual updating is postponed until fold info is used, to avoid doing +/// every time a setting is changed or a syntax item is added. void foldUpdateAll(win_T *win) { win->w_foldinvalid = true; @@ -992,21 +957,18 @@ int foldMoveTo(const bool updown, const int dir, const long count) } // foldInitWin() {{{2 -/* - * Init the fold info in a new window. - */ +/// Init the fold info in a new window. void foldInitWin(win_T *new_win) { ga_init(&new_win->w_folds, (int)sizeof(fold_T), 10); } // find_wl_entry() {{{2 -/* - * Find an entry in the win->w_lines[] array for buffer line "lnum". - * Only valid entries are considered (for entries where wl_valid is FALSE the - * line number can be wrong). - * Returns index of entry or -1 if not found. - */ +/// Find an entry in the win->w_lines[] array for buffer line "lnum". +/// Only valid entries are considered (for entries where wl_valid is FALSE the +/// line number can be wrong). +/// +/// @return index of entry or -1 if not found. int find_wl_entry(win_T *win, linenr_T lnum) { int i; @@ -1025,9 +987,7 @@ int find_wl_entry(win_T *win, linenr_T lnum) } // foldAdjustVisual() {{{2 -/* - * Adjust the Visual area to include any fold at the start or end completely. - */ +/// Adjust the Visual area to include any fold at the start or end completely. void foldAdjustVisual(void) { pos_T *start, *end; @@ -1059,9 +1019,7 @@ void foldAdjustVisual(void) } // cursor_foldstart() {{{2 -/* - * Move the cursor to the first line of a closed fold. - */ +/// Move the cursor to the first line of a closed fold. void foldAdjustCursor(void) { (void)hasFolding(curwin->w_cursor.lnum, &curwin->w_cursor.lnum, NULL); @@ -1069,9 +1027,7 @@ void foldAdjustCursor(void) // Internal functions for "fold_T" {{{1 // cloneFoldGrowArray() {{{2 -/* - * Will "clone" (i.e deep copy) a garray_T of folds. - */ +/// Will "clone" (i.e deep copy) a garray_T of folds. void cloneFoldGrowArray(garray_T *from, garray_T *to) { fold_T *from_p; @@ -1143,9 +1099,7 @@ static bool foldFind(const garray_T *gap, linenr_T lnum, fold_T **fpp) } // foldLevelWin() {{{2 -/* - * Return fold level at line number "lnum" in window "wp". - */ +/// @return fold level at line number "lnum" in window "wp". static int foldLevelWin(win_T *wp, linenr_T lnum) { fold_T *fp; @@ -1169,9 +1123,7 @@ static int foldLevelWin(win_T *wp, linenr_T lnum) } // checkupdate() {{{2 -/* - * Check if the folds in window "wp" are invalid and update them if needed. - */ +/// Check if the folds in window "wp" are invalid and update them if needed. static void checkupdate(win_T *wp) { if (wp->w_foldinvalid) { @@ -1181,10 +1133,8 @@ static void checkupdate(win_T *wp) } // setFoldRepeat() {{{2 -/* - * Open or close fold for current window at line "lnum". - * Repeat "count" times. - */ +/// Open or close fold for current window at position `pos`. +/// Repeat "count" times. static void setFoldRepeat(pos_T pos, long count, int do_open) { int done; @@ -1204,7 +1154,6 @@ static void setFoldRepeat(pos_T pos, long count, int do_open) } // setManualFold() {{{2 -/// /// Open or close the fold in the current window which contains "lnum". /// Also does this for other windows in diff mode when needed. /// @@ -1344,9 +1293,7 @@ static linenr_T setManualFoldWin(win_T *wp, linenr_T lnum, int opening, int recu } // foldOpenNested() {{{2 -/* - * Open all nested folds in fold "fpr" recursively. - */ +/// Open all nested folds in fold "fpr" recursively. static void foldOpenNested(fold_T *fpr) { fold_T *fp; @@ -1359,9 +1306,10 @@ static void foldOpenNested(fold_T *fpr) } // deleteFoldEntry() {{{2 -// Delete fold "idx" from growarray "gap". -// When "recursive" is true also delete all the folds contained in it. -// When "recursive" is false contained folds are moved one level up. +/// Delete fold "idx" from growarray "gap". +/// +/// @param recursive when true, also delete all the folds contained in it. +/// when false, contained folds are moved one level up. static void deleteFoldEntry(win_T *const wp, garray_T *const gap, const int idx, const bool recursive) { @@ -1408,9 +1356,7 @@ static void deleteFoldEntry(win_T *const wp, garray_T *const gap, const int idx, } // deleteFoldRecurse() {{{2 -/* - * Delete nested folds in a fold. - */ +/// Delete nested folds in a fold. void deleteFoldRecurse(buf_T *bp, garray_T *gap) { #define DELETE_FOLD_NESTED(fd) deleteFoldRecurse(bp, &((fd)->fd_nested)) @@ -1418,9 +1364,7 @@ void deleteFoldRecurse(buf_T *bp, garray_T *gap) } // foldMarkAdjust() {{{2 -/* - * Update line numbers of folds for inserted/deleted lines. - */ +/// Update line numbers of folds for inserted/deleted lines. void foldMarkAdjust(win_T *wp, linenr_T line1, linenr_T line2, long amount, long amount_after) { // If deleting marks from line1 to line2, but not deleting all those @@ -1538,10 +1482,8 @@ static void foldMarkAdjustRecurse(win_T *wp, garray_T *gap, linenr_T line1, line } // getDeepestNesting() {{{2 -/* - * Get the lowest 'foldlevel' value that makes the deepest nested fold in the - * current window open. - */ +/// Get the lowest 'foldlevel' value that makes the deepest nested fold in +/// window `wp`. int getDeepestNesting(win_T *wp) { checkupdate(wp); @@ -1633,7 +1575,7 @@ static void checkSmall(win_T *const wp, fold_T *const fp, const linenr_T lnum_of } // setSmallMaybe() {{{2 -// Set small flags in "gap" to kNone. +/// Set small flags in "gap" to kNone. static void setSmallMaybe(garray_T *gap) { fold_T *fp = (fold_T *)gap->ga_data; @@ -1643,10 +1585,8 @@ static void setSmallMaybe(garray_T *gap) } // foldCreateMarkers() {{{2 -/* - * Create a fold from line "start" to line "end" (inclusive) in the current - * window by adding markers. - */ +/// Create a fold from line "start" to line "end" (inclusive) in window `wp` +/// by adding markers. static void foldCreateMarkers(win_T *wp, pos_T start, pos_T end) { buf_T *buf = wp->w_buffer; @@ -1672,9 +1612,7 @@ static void foldCreateMarkers(win_T *wp, pos_T start, pos_T end) } // foldAddMarker() {{{2 -/* - * Add "marker[markerlen]" in 'commentstring' to line "lnum". - */ +/// Add "marker[markerlen]" in 'commentstring' to position `pos`. static void foldAddMarker(buf_T *buf, pos_T pos, const char_u *marker, size_t markerlen) { char_u *cms = buf->b_p_cms; @@ -1731,11 +1669,10 @@ static void deleteFoldMarkers(win_T *wp, fold_T *fp, int recursive, linenr_T lnu } // foldDelMarker() {{{2 -// -// Delete marker "marker[markerlen]" at the end of line "lnum". -// Delete 'commentstring' if it matches. -// If the marker is not found, there is no error message. Could be a missing -// close-marker. +/// Delete marker "marker[markerlen]" at the end of line "lnum". +/// Delete 'commentstring' if it matches. +/// If the marker is not found, there is no error message. Could be a missing +/// close-marker. static void foldDelMarker(buf_T *buf, linenr_T lnum, char_u *marker, size_t markerlen) { char_u *newline; @@ -1892,9 +1829,7 @@ char_u *get_foldtext(win_T *wp, linenr_T lnum, linenr_T lnume, foldinfo_T foldin } // foldtext_cleanup() {{{2 -/* - * Remove 'foldmarker' and 'commentstring' from "str" (in-place). - */ +/// Remove 'foldmarker' and 'commentstring' from "str" (in-place). void foldtext_cleanup(char_u *str) { char_u *s; @@ -1974,10 +1909,8 @@ void foldtext_cleanup(char_u *str) // Function declarations. {{{2 // foldUpdateIEMS() {{{2 -/* - * Update the folding for window "wp", at least from lines "top" to "bot". - * IEMS = "Indent Expr Marker Syntax" - */ +/// Update the folding for window "wp", at least from lines "top" to "bot". +/// IEMS = "Indent Expr Marker Syntax" static void foldUpdateIEMS(win_T *const wp, linenr_T top, linenr_T bot) { fline_T fline; @@ -2332,7 +2265,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; @@ -2640,9 +2573,7 @@ static linenr_T foldUpdateIEMSRecurse(garray_T *const gap, const int level, } // foldInsert() {{{2 -/* - * Insert a new fold in "gap" at position "i". - */ +/// Insert a new fold in "gap" at position "i". static void foldInsert(garray_T *gap, int i) { fold_T *fp; @@ -2658,13 +2589,11 @@ static void foldInsert(garray_T *gap, int i) } // foldSplit() {{{2 -/* - * Split the "i"th fold in "gap", which starts before "top" and ends below - * "bot" in two pieces, one ending above "top" and the other starting below - * "bot". - * The caller must first have taken care of any nested folds from "top" to - * "bot"! - */ +/// Split the "i"th fold in "gap", which starts before "top" and ends below +/// "bot" in two pieces, one ending above "top" and the other starting below +/// "bot". +/// The caller must first have taken care of any nested folds from "top" to +/// "bot"! static void foldSplit(buf_T *buf, garray_T *const gap, const int i, const linenr_T top, const linenr_T bot) { @@ -2704,24 +2633,22 @@ static void foldSplit(buf_T *buf, garray_T *const gap, const int i, const linenr } // foldRemove() {{{2 -/* - * Remove folds within the range "top" to and including "bot". - * Check for these situations: - * 1 2 3 - * 1 2 3 - * top 2 3 4 5 - * 2 3 4 5 - * bot 2 3 4 5 - * 3 5 6 - * 3 5 6 - * - * 1: not changed - * 2: truncate to stop above "top" - * 3: split in two parts, one stops above "top", other starts below "bot". - * 4: deleted - * 5: made to start below "bot". - * 6: not changed - */ +/// Remove folds within the range "top" to and including "bot". +/// Check for these situations: +/// 1 2 3 +/// 1 2 3 +/// top 2 3 4 5 +/// 2 3 4 5 +/// bot 2 3 4 5 +/// 3 5 6 +/// 3 5 6 +/// +/// 1: not changed +/// 2: truncate to stop above "top" +/// 3: split in two parts, one stops above "top", other starts below "bot". +/// 4: deleted +/// 5: made to start below "bot". +/// 6: not changed static void foldRemove(win_T *const wp, garray_T *gap, linenr_T top, linenr_T bot) { fold_T *fp = NULL; @@ -2786,35 +2713,35 @@ static void foldReverseOrder(garray_T *gap, const linenr_T start_arg, const line } // foldMoveRange() {{{2 -// Move folds within the inclusive range "line1" to "line2" to after "dest" -// require "line1" <= "line2" <= "dest" -// -// There are the following situations for the first fold at or below line1 - 1. -// 1 2 3 4 -// 1 2 3 4 -// line1 2 3 4 -// 2 3 4 5 6 7 -// line2 3 4 5 6 7 -// 3 4 6 7 8 9 -// dest 4 7 8 9 -// 4 7 8 10 -// 4 7 8 10 -// -// In the following descriptions, "moved" means moving in the buffer, *and* in -// the fold array. -// Meanwhile, "shifted" just means moving in the buffer. -// 1. not changed -// 2. truncated above line1 -// 3. length reduced by line2 - line1, folds starting between the end of 3 and -// dest are truncated and shifted up -// 4. internal folds moved (from [line1, line2] to dest) -// 5. moved to dest. -// 6. truncated below line2 and moved. -// 7. length reduced by line2 - dest, folds starting between line2 and dest are -// removed, top is moved down by move_len. -// 8. truncated below dest and shifted up. -// 9. shifted up -// 10. not changed +/// Move folds within the inclusive range "line1" to "line2" to after "dest" +/// require "line1" <= "line2" <= "dest" +/// +/// There are the following situations for the first fold at or below line1 - 1. +/// 1 2 3 4 +/// 1 2 3 4 +/// line1 2 3 4 +/// 2 3 4 5 6 7 +/// line2 3 4 5 6 7 +/// 3 4 6 7 8 9 +/// dest 4 7 8 9 +/// 4 7 8 10 +/// 4 7 8 10 +/// +/// In the following descriptions, "moved" means moving in the buffer, *and* in +/// the fold array. +/// Meanwhile, "shifted" just means moving in the buffer. +/// 1. not changed +/// 2. truncated above line1 +/// 3. length reduced by line2 - line1, folds starting between the end of 3 and +/// dest are truncated and shifted up +/// 4. internal folds moved (from [line1, line2] to dest) +/// 5. moved to dest. +/// 6. truncated below line2 and moved. +/// 7. length reduced by line2 - dest, folds starting between line2 and dest are +/// removed, top is moved down by move_len. +/// 8. truncated below dest and shifted up. +/// 9. shifted up +/// 10. not changed static void truncate_fold(win_T *const wp, fold_T *fp, linenr_T end) { // I want to stop *at here*, foldRemove() stops *above* top @@ -2826,7 +2753,7 @@ static void truncate_fold(win_T *const wp, fold_T *fp, linenr_T end) #define FOLD_END(fp) ((fp)->fd_top + (fp)->fd_len - 1) #define VALID_FOLD(fp, gap) \ ((gap)->ga_len > 0 && (fp) < ((fold_T *)(gap)->ga_data + (gap)->ga_len)) -#define FOLD_INDEX(fp, gap) ((size_t)(fp - ((fold_T *)(gap)->ga_data))) +#define FOLD_INDEX(fp, gap) ((size_t)((fp) - ((fold_T *)(gap)->ga_data))) void foldMoveRange(win_T *const wp, garray_T *gap, const linenr_T line1, const linenr_T line2, const linenr_T dest) { @@ -2929,13 +2856,11 @@ void foldMoveRange(win_T *const wp, garray_T *gap, const linenr_T line1, const l #undef FOLD_INDEX // foldMerge() {{{2 -/* - * Merge two adjacent folds (and the nested ones in them). - * This only works correctly when the folds are really adjacent! Thus "fp1" - * must end just above "fp2". - * The resulting fold is "fp1", nested folds are moved from "fp2" to "fp1". - * Fold entry "fp2" in "gap" is deleted. - */ +/// Merge two adjacent folds (and the nested ones in them). +/// This only works correctly when the folds are really adjacent! Thus "fp1" +/// must end just above "fp2". +/// The resulting fold is "fp1", nested folds are moved from "fp2" to "fp1". +/// Fold entry "fp2" in "gap" is deleted. static void foldMerge(win_T *const wp, fold_T *fp1, garray_T *gap, fold_T *fp2) { fold_T *fp3; @@ -2968,11 +2893,10 @@ static void foldMerge(win_T *const wp, fold_T *fp1, garray_T *gap, fold_T *fp2) } // foldlevelIndent() {{{2 -/* - * Low level function to get the foldlevel for the "indent" method. - * Doesn't use any caching. - * Returns a level of -1 if the foldlevel depends on surrounding lines. - */ +/// Low level function to get the foldlevel for the "indent" method. +/// Doesn't use any caching. +/// +/// @return a level of -1 if the foldlevel depends on surrounding lines. static void foldlevelIndent(fline_T *flp) { char_u *s; @@ -3000,10 +2924,8 @@ static void foldlevelIndent(fline_T *flp) } // foldlevelDiff() {{{2 -/* - * Low level function to get the foldlevel for the "diff" method. - * Doesn't use any caching. - */ +/// Low level function to get the foldlevel for the "diff" method. +/// Doesn't use any caching. static void foldlevelDiff(fline_T *flp) { if (diff_infold(flp->wp, flp->lnum + flp->off)) { @@ -3014,11 +2936,10 @@ static void foldlevelDiff(fline_T *flp) } // foldlevelExpr() {{{2 -/* - * Low level function to get the foldlevel for the "expr" method. - * Doesn't use any caching. - * Returns a level of -1 if the foldlevel depends on surrounding lines. - */ +/// Low level function to get the foldlevel for the "expr" method. +/// Doesn't use any caching. +/// +/// @return a level of -1 if the foldlevel depends on surrounding lines. static void foldlevelExpr(fline_T *flp) { win_T *win; @@ -3113,11 +3034,9 @@ static void foldlevelExpr(fline_T *flp) } // parseMarker() {{{2 -/* - * Parse 'foldmarker' and set "foldendmarker", "foldstartmarkerlen" and - * "foldendmarkerlen". - * Relies on the option value to have been checked for correctness already. - */ +/// Parse 'foldmarker' and set "foldendmarker", "foldstartmarkerlen" and +/// "foldendmarkerlen". +/// Relies on the option value to have been checked for correctness already. static void parseMarker(win_T *wp) { foldendmarker = vim_strchr(wp->w_p_fmr, ','); @@ -3126,15 +3045,13 @@ static void parseMarker(win_T *wp) } // foldlevelMarker() {{{2 -/* - * Low level function to get the foldlevel for the "marker" method. - * "foldendmarker", "foldstartmarkerlen" and "foldendmarkerlen" must have been - * set before calling this. - * Requires that flp->lvl is set to the fold level of the previous line! - * Careful: This means you can't call this function twice on the same line. - * Doesn't use any caching. - * Sets flp->start when a start marker was found. - */ +/// Low level function to get the foldlevel for the "marker" method. +/// "foldendmarker", "foldstartmarkerlen" and "foldendmarkerlen" must have been +/// set before calling this. +/// Requires that flp->lvl is set to the fold level of the previous line! +/// Careful: This means you can't call this function twice on the same line. +/// Doesn't use any caching. +/// Sets flp->start when a start marker was found. static void foldlevelMarker(fline_T *flp) { char_u *startmarker; @@ -3205,10 +3122,8 @@ static void foldlevelMarker(fline_T *flp) } // foldlevelSyntax() {{{2 -/* - * Low level function to get the foldlevel for the "syntax" method. - * Doesn't use any caching. - */ +/// Low level function to get the foldlevel for the "syntax" method. +/// Doesn't use any caching. static void foldlevelSyntax(fline_T *flp) { linenr_T lnum = flp->lnum + flp->off; @@ -3228,11 +3143,9 @@ static void foldlevelSyntax(fline_T *flp) // functions for storing the fold state in a View {{{1 // put_folds() {{{2 - -/* - * Write commands to "fd" to restore the manual folds in window "wp". - * Return FAIL if writing fails. - */ +/// Write commands to "fd" to restore the manual folds in window "wp". +/// +/// @return FAIL if writing fails. int put_folds(FILE *fd, win_T *wp) { if (foldmethodIsManual(wp)) { @@ -3252,10 +3165,9 @@ int put_folds(FILE *fd, win_T *wp) } // put_folds_recurse() {{{2 -/* - * Write commands to "fd" to recreate manually created folds. - * Returns FAIL when writing failed. - */ +/// Write commands to "fd" to recreate manually created folds. +/// +/// @return FAIL when writing failed. static int put_folds_recurse(FILE *fd, garray_T *gap, linenr_T off) { fold_T *fp = (fold_T *)gap->ga_data; @@ -3276,10 +3188,9 @@ static int put_folds_recurse(FILE *fd, garray_T *gap, linenr_T off) } // put_foldopen_recurse() {{{2 -/* - * Write commands to "fd" to open and close manually opened/closed folds. - * Returns FAIL when writing failed. - */ +/// Write commands to "fd" to open and close manually opened/closed folds. +/// +/// @return FAIL when writing failed. static int put_foldopen_recurse(FILE *fd, win_T *wp, garray_T *gap, linenr_T off) { int level; @@ -3325,10 +3236,9 @@ static int put_foldopen_recurse(FILE *fd, win_T *wp, garray_T *gap, linenr_T off } // put_fold_open_close() {{{2 -/* - * Write the open or close command to "fd". - * Returns FAIL when writing failed. - */ +/// Write the open or close command to "fd". +/// +/// @return FAIL when writing failed. static int put_fold_open_close(FILE *fd, fold_T *fp, linenr_T off) { if (fprintf(fd, "%" PRId64, (int64_t)(fp->fd_top + off)) < 0 diff --git a/src/nvim/fold.h b/src/nvim/fold.h index a96ea8a039..6b29214760 100644 --- a/src/nvim/fold.h +++ b/src/nvim/fold.h @@ -14,10 +14,10 @@ */ typedef struct foldinfo { linenr_T fi_lnum; // line number where fold starts - int fi_level; /* level of the fold; when this is zero the - other fields are invalid */ - int fi_low_level; /* lowest fold level that starts in the same - line */ + int fi_level; // level of the fold; when this is zero the + // other fields are invalid + int fi_low_level; // lowest fold level that starts in the same + // line long fi_lines; } foldinfo_T; diff --git a/src/nvim/generators/c_grammar.lua b/src/nvim/generators/c_grammar.lua index f35817c466..70a7be86b5 100644 --- a/src/nvim/generators/c_grammar.lua +++ b/src/nvim/generators/c_grammar.lua @@ -49,6 +49,7 @@ local c_proto = Ct( (fill * Cg((P('FUNC_API_REMOTE_IMPL') * Cc(true)), 'remote_impl') ^ -1) * (fill * Cg((P('FUNC_API_BRIDGE_IMPL') * Cc(true)), 'bridge_impl') ^ -1) * (fill * Cg((P('FUNC_API_COMPOSITOR_IMPL') * Cc(true)), 'compositor_impl') ^ -1) * + (fill * Cg((P('FUNC_API_CLIENT_IMPL') * Cc(true)), 'client_impl') ^ -1) * fill * P(';') ) 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_api_ui_events.lua b/src/nvim/generators/gen_api_ui_events.lua index 3cb117d8b5..5e70442dce 100644..100755 --- a/src/nvim/generators/gen_api_ui_events.lua +++ b/src/nvim/generators/gen_api_ui_events.lua @@ -3,13 +3,14 @@ local mpack = require('mpack') local nvimdir = arg[1] package.path = nvimdir .. '/?.lua;' .. package.path -assert(#arg == 7) +assert(#arg == 8) local input = io.open(arg[2], 'rb') local proto_output = io.open(arg[3], 'wb') local call_output = io.open(arg[4], 'wb') local remote_output = io.open(arg[5], 'wb') local bridge_output = io.open(arg[6], 'wb') local metadata_output = io.open(arg[7], 'wb') +local client_output = io.open(arg[8], 'wb') local c_grammar = require('generators.c_grammar') local events = c_grammar.grammar:match(input:read('*all')) @@ -50,6 +51,52 @@ local function write_arglist(output, ev, need_copy) end end +local function call_ui_event_method(output, ev) + output:write('void ui_client_event_'..ev.name..'(Array args)\n{\n') + + local hlattrs_args_count = 0 + if #ev.parameters > 0 then + output:write(' if (args.size < '..(#ev.parameters)) + for j = 1, #ev.parameters do + local kind = ev.parameters[j][1] + if kind ~= "Object" then + if kind == 'HlAttrs' then kind = 'Dictionary' end + output:write('\n || args.items['..(j-1)..'].type != kObjectType'..kind..'') + end + end + output:write(') {\n') + output:write(' ELOG("Error handling ui event \''..ev.name..'\'");\n') + output:write(' return;\n') + output:write(' }\n') + end + + for j = 1, #ev.parameters do + local param = ev.parameters[j] + local kind = param[1] + output:write(' '..kind..' arg_'..j..' = ') + if kind == 'HlAttrs' then + -- The first HlAttrs argument is rgb_attrs and second is cterm_attrs + output:write('ui_client_dict2hlattrs(args.items['..(j-1)..'].data.dictionary, '..(hlattrs_args_count == 0 and 'true' or 'false')..');\n') + hlattrs_args_count = hlattrs_args_count + 1 + elseif kind == 'Object' then + output:write('args.items['..(j-1)..'];\n') + else + output:write('args.items['..(j-1)..'].data.'..string.lower(kind)..';\n') + end + end + + output:write(' ui_call_'..ev.name..'(') + for j = 1, #ev.parameters do + output:write('arg_'..j) + if j ~= #ev.parameters then + output:write(', ') + end + end + output:write(');\n') + + output:write('}\n\n') +end + for i = 1, #events do local ev = events[i] assert(ev.return_type == 'void') @@ -160,12 +207,35 @@ for i = 1, #events do call_output:write(";\n") call_output:write("}\n\n") end + + if (not ev.remote_only) and (not ev.noexport) and (not ev.client_impl) then + call_ui_event_method(client_output, ev) + end end +-- Generate the map_init method for client handlers +client_output:write([[ +void ui_client_methods_table_init(void) +{ + +]]) + +for i = 1, #events do + local fn = events[i] + if (not fn.noexport) and ((not fn.remote_only) or fn.client_impl) then + client_output:write(' add_ui_client_event_handler('.. + '(String) {.data = "'..fn.name..'", '.. + '.size = sizeof("'..fn.name..'") - 1}, '.. + '(UIClientHandler) ui_client_event_'..fn.name..');\n') + end +end + +client_output:write('\n}\n\n') + proto_output:close() call_output:close() remote_output:close() -bridge_output:close() +client_output:close() -- don't expose internal attributes like "impl_name" in public metadata local exported_attributes = {'name', 'parameters', diff --git a/src/nvim/generators/gen_char_blob.lua b/src/nvim/generators/gen_char_blob.lua index a7dad50d48..11f6cbcc13 100644 --- a/src/nvim/generators/gen_char_blob.lua +++ b/src/nvim/generators/gen_char_blob.lua @@ -1,12 +1,26 @@ if arg[1] == '--help' then print('Usage:') - print(' '..arg[0]..' target source varname [source varname]...') + print(' '..arg[0]..' [-c] target source varname [source varname]...') print('') print('Generates C file with big uint8_t blob.') print('Blob will be stored in a static const array named varname.') os.exit() end +-- Recognized options: +-- -c compile Lua bytecode +local options = {} + +while true do + local opt = string.match(arg[1], "^-(%w)") + if not opt then + break + end + + options[opt] = true + table.remove(arg, 1) +end + assert(#arg >= 3 and (#arg - 1) % 2 == 0) local target_file = arg[1] or error('Need a target file') @@ -14,20 +28,39 @@ local target = io.open(target_file, 'w') target:write('#include <stdint.h>\n\n') -local varnames = {} +local index_items = {} + +local warn_on_missing_compiler = true +local modnames = {} for argi = 2, #arg, 2 do local source_file = arg[argi] - local varname = arg[argi + 1] - if varnames[varname] then - error(string.format("varname %q is already specified for file %q", varname, varnames[varname])) + local modname = arg[argi + 1] + if modnames[modname] then + error(string.format("modname %q is already specified for file %q", modname, modnames[modname])) end - varnames[varname] = source_file - - local source = io.open(source_file, 'r') - or error(string.format("source_file %q doesn't exist", source_file)) + modnames[modname] = source_file + local varname = string.gsub(modname,'%.','_dot_').."_module" target:write(('static const uint8_t %s[] = {\n'):format(varname)) + local output + if options.c then + local luac = os.getenv("LUAC_PRG") + if luac and luac ~= "" then + output = io.popen(luac:format(source_file), "r"):read("*a") + elseif warn_on_missing_compiler then + print("LUAC_PRG is missing, embedding raw source") + warn_on_missing_compiler = false + end + end + + if not output then + local source = io.open(source_file, "r") + or error(string.format("source_file %q doesn't exist", source_file)) + output = source:read("*a") + source:close() + end + local num_bytes = 0 local MAX_NUM_BYTES = 15 -- 78 / 5: maximum number of bytes on one line target:write(' ') @@ -41,19 +74,20 @@ for argi = 2, #arg, 2 do end end - for line in source:lines() do - for i = 1, string.len(line) do - local byte = line:byte(i) - assert(byte ~= 0) - target:write(string.format(' %3u,', byte)) - increase_num_bytes() - end - target:write(string.format(' %3u,', string.byte('\n', 1))) + for i = 1, string.len(output) do + local byte = output:byte(i) + target:write(string.format(' %3u,', byte)) increase_num_bytes() end - target:write(' 0};\n') - source:close() + target:write(' 0};\n') + if modname ~= "_" then + table.insert(index_items, ' { "'..modname..'", '..varname..', sizeof '..varname..' },\n\n') + end end +target:write('static ModuleDef builtin_modules[] = {\n') +target:write(table.concat(index_items)) +target:write('};\n') + target:close() 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 2f4b59837a..85479b220a 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" @@ -29,6 +30,7 @@ #include "nvim/func_attr.h" #include "nvim/garray.h" #include "nvim/getchar.h" +#include "nvim/input.h" #include "nvim/keymap.h" #include "nvim/lua/executor.h" #include "nvim/main.h" @@ -36,7 +38,6 @@ #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/message.h" -#include "nvim/misc1.h" #include "nvim/move.h" #include "nvim/normal.h" #include "nvim/ops.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; @@ -458,6 +459,15 @@ void flush_buffers(flush_buffers_T flush_typeahead) } } +/// flush map and typeahead buffers and give a warning for an error +void beep_flush(void) +{ + if (emsg_silent == 0) { + flush_buffers(FLUSH_MINIMAL); + vim_beep(BO_ERROR); + } +} + /* * The previous contents of the redo buffer is kept in old_redobuffer. * This is used for the CTRL-O <.> command in insert mode. @@ -514,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) { @@ -526,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. @@ -574,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) { @@ -595,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); @@ -616,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) { @@ -638,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); @@ -655,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; @@ -714,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; @@ -842,7 +842,10 @@ static void init_typebuf(void) void init_default_mappings(void) { add_map((char_u *)"Y y$", NORMAL, true); - add_map((char_u *)"<C-L> <Cmd>nohlsearch<Bar>diffupdate<CR><C-L>", NORMAL, true); + + // Use normal! <C-L> to prevent inserting raw <C-L> when using i_<C-O> + // See https://github.com/neovim/neovim/issues/17473 + add_map((char_u *)"<C-L> <Cmd>nohlsearch<Bar>diffupdate<Bar>normal! <C-L><CR>", NORMAL, true); add_map((char_u *)"<C-U> <C-G>u<C-U>", INSERT, true); add_map((char_u *)"<C-W> <C-G>u<C-W>", INSERT, true); } @@ -852,7 +855,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. @@ -982,36 +985,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 @@ -1171,6 +1172,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. @@ -1240,7 +1253,14 @@ static int old_mod_mask; // mod_mask for ungotten character static int old_mouse_grid; // mouse_grid related to old_char static int old_mouse_row; // mouse_row related to old_char static int old_mouse_col; // mouse_col related to old_char +static int old_KeyStuffed; // whether old_char was stuffed +static bool can_get_old_char(void) +{ + // If the old character was not stuffed and characters have been added to + // the stuff buffer, need to first get the stuffed characters instead. + return old_char != -1 && (old_KeyStuffed || stuff_empty()); +} /* * Save all three kinds of typeahead, so that the user must type at a prompt. @@ -1417,15 +1437,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; @@ -1443,7 +1461,7 @@ int vgetc(void) * If a character was put back with vungetc, it was already processed. * Return it directly. */ - if (old_char != -1) { + if (can_get_old_char()) { c = old_char; old_char = -1; mod_mask = old_mod_mask; @@ -1451,8 +1469,15 @@ int vgetc(void) mouse_row = old_mouse_row; mouse_col = old_mouse_col; } else { - mod_mask = 0x0; - last_recorded_len = 0; + // number of characters recorded from the last vgetc() call + static size_t last_vgetc_recorded_len = 0; + + mod_mask = 0; + + // last_recorded_len can be larger than last_vgetc_recorded_len + // if peeking records more + last_recorded_len -= last_vgetc_recorded_len; + for (;;) { // this is done twice if there are modifiers bool did_inc = false; if (mod_mask) { // no mapping after modifier has been read @@ -1562,20 +1587,31 @@ 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--; c = utf_ptr2char(buf); } + // A modifier was not used for a mapping, apply it to ASCII + // keys. Shift would already have been applied. + if (mod_mask & MOD_MASK_CTRL) { + if ((c >= '`' && c <= 0x7f) || (c >= '@' && c <= '_')) { + c &= 0x1f; + mod_mask &= ~MOD_MASK_CTRL; + if (c == 0) { + c = K_ZERO; + } + } else if (c == '6') { + // CTRL-6 is equivalent to CTRL-^ + c = 0x1e; + mod_mask &= ~MOD_MASK_CTRL; + } + } + // If mappings are enabled (i.e., not Ctrl-v) and the user directly typed // something with a meta- or alt- modifier that was not mapped, interpret // <M-x> as <Esc>x rather than as an unbound meta keypress. #8213 @@ -1583,13 +1619,16 @@ 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; } break; } + + last_vgetc_recorded_len = last_recorded_len; } /* @@ -1644,7 +1683,7 @@ int plain_vgetc(void) */ int vpeekc(void) { - if (old_char != -1) { + if (can_get_old_char()) { return old_char; } return vgetorpeek(false); @@ -1680,7 +1719,365 @@ int char_avail(void) return retval != NUL; } -// unget one character (can only be done once!) +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_T; + +/// Handle mappings in the typeahead buffer. +/// - When something was mapped, return map_result_retry for recursive mappings. +/// - When nothing mapped and typeahead has a character: return map_result_get. +/// - When there is no match yet, return map_result_nomatch, need to get more +/// typeahead. +static int handle_mapping(int *keylenp, bool *timedout, int *mapdepth) +{ + mapblock_T *mp = NULL; + mapblock_T *mp2; + mapblock_T *mp_match; + int mp_match_len = 0; + int max_mlen = 0; + int tb_c1; + int mlen; + int nolmaplen; + int keylen = *keylenp; + int i; + int local_State = get_real_state(); + bool is_plug_map = false; + + // If typehead starts with <Plug> then remap, even for a "noremap" mapping. + 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. + // + // Don't look for mappings if: + // - no_mapping set: mapping disabled (e.g. for CTRL-V) + // - maphash_valid not set: no mappings present. + // - typebuf.tb_buf[typebuf.tb_off] should not be remapped + // - in insert or cmdline mode and 'paste' option set + // - waiting for "hit return to continue" and CR or SPACE typed + // - waiting for a char with --more-- + // - in Ctrl-X mode, and we get a valid char for that mode + 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 || is_plug_map + || (p_remap + && !(typebuf.tb_noremap[typebuf.tb_off] & (RM_NONE|RM_ABBR)))) + && !(p_paste && (State & (INSERT + CMDLINE))) + && !(State == HITRETURN && (tb_c1 == CAR || tb_c1 == ' ')) + && State != ASKMORE + && State != CONFIRM + && !((ctrl_x_mode_not_default() && vim_is_ctrl_x_key(tb_c1)) + || ((compl_cont_status & CONT_LOCAL) + && (tb_c1 == Ctrl_N || tb_c1 == Ctrl_P)))) { + if (tb_c1 == K_SPECIAL) { + nolmaplen = 2; + } else { + LANGMAP_ADJUST(tb_c1, !(State & (CMDLINE | INSERT)) && get_real_state() != SELECTMODE); + nolmaplen = 0; + } + // First try buffer-local mappings. + mp = curbuf->b_maphash[MAP_HASH(local_State, tb_c1)]; + mp2 = maphash[MAP_HASH(local_State, tb_c1)]; + if (mp == NULL) { + // There are no buffer-local mappings. + mp = mp2; + mp2 = NULL; + } + // Loop until a partly matching mapping is found or all (local) + // mappings have been checked. + // The longest full match is remembered in "mp_match". + // A full match is only accepted if there is no partly match, so "aa" + // and "aaa" can both be mapped. + mp_match = NULL; + mp_match_len = 0; + for (; mp != NULL; mp->m_next == NULL ? (mp = mp2, mp2 = NULL) : (mp = mp->m_next)) { + // Only consider an entry if the first character matches and it is + // for the current state. + // Skip ":lmap" mappings if keys were mapped. + if (mp->m_keys[0] == tb_c1 && (mp->m_mode & local_State) + && ((mp->m_mode & LANGMAP) == 0 || typebuf.tb_maplen == 0)) { + int nomap = nolmaplen; + int c2; + // find the match length of this mapping + for (mlen = 1; mlen < typebuf.tb_len; mlen++) { + c2 = typebuf.tb_buf[typebuf.tb_off + mlen]; + if (nomap > 0) { + nomap--; + } else if (c2 == K_SPECIAL) { + nomap = 2; + } else { + LANGMAP_ADJUST(c2, true); + } + if (mp->m_keys[mlen] != c2) { + break; + } + } + + // Don't allow mapping the first byte(s) of a multi-byte char. + // Happens when mapping <M-a> and then changing 'encoding'. + // Beware that 0x80 is escaped. + char_u *p1 = mp->m_keys; + char_u *p2 = (char_u *)mb_unescape((const char **)&p1); + + if (p2 != NULL && MB_BYTE2LEN(tb_c1) > utfc_ptr2len(p2)) { + mlen = 0; + } + + // Check an entry whether it matches. + // - Full match: mlen == keylen + // - Partly match: mlen == typebuf.tb_len + keylen = mp->m_keylen; + if (mlen == keylen || (mlen == typebuf.tb_len && typebuf.tb_len < keylen)) { + char_u *s; + int n; + + // If only script-local mappings are allowed, check if the + // mapping starts with K_SNR. + s = typebuf.tb_noremap + typebuf.tb_off; + if (*s == RM_SCRIPT + && (mp->m_keys[0] != K_SPECIAL + || mp->m_keys[1] != KS_EXTRA + || mp->m_keys[2] != KE_SNR)) { + continue; + } + + // If one of the typed keys cannot be remapped, skip the entry. + for (n = mlen; --n >= 0;) { + if (*s++ & (RM_NONE|RM_ABBR)) { + break; + } + } + if (!is_plug_map && n >= 0) { + continue; + } + + if (keylen > typebuf.tb_len) { + if (!*timedout && !(mp_match != NULL && mp_match->m_nowait)) { + // break at a partly match + keylen = KEYLEN_PART_MAP; + break; + } + } else if (keylen > mp_match_len + || (keylen == mp_match_len + && mp_match != NULL + && (mp_match->m_mode & LANGMAP) == 0 + && (mp->m_mode & LANGMAP) != 0)) { + // found a longer match + mp_match = mp; + mp_match_len = keylen; + } + } else { + // No match; may have to check for termcode at next character. + if (max_mlen < mlen) { + max_mlen = mlen; + } + } + } + } + + // If no partly match found, use the longest full match. + if (keylen != KEYLEN_PART_MAP) { + mp = mp_match; + keylen = mp_match_len; + } + } + + // Check for match with 'pastetoggle' + if (*p_pt != NUL && mp == NULL && (State & (INSERT|NORMAL))) { + bool match = typebuf_match_len(p_pt, &mlen); + if (match) { + // write chars to script file(s) + if (mlen > typebuf.tb_maplen) { + gotchars(typebuf.tb_buf + typebuf.tb_off + typebuf.tb_maplen, + (size_t)(mlen - typebuf.tb_maplen)); + } + + del_typebuf(mlen, 0); // remove the chars + set_option_value("paste", !p_paste, NULL, 0); + if (!(State & INSERT)) { + msg_col = 0; + msg_row = Rows - 1; + msg_clr_eos(); // clear ruler + } + status_redraw_all(); + redraw_statuslines(); + showmode(); + setcursor(); + *keylenp = keylen; + return map_result_retry; + } + // Need more chars for partly match. + if (mlen == typebuf.tb_len) { + keylen = KEYLEN_PART_KEY; + } else if (max_mlen < mlen) { + // no match, may have to check for termcode at next character + max_mlen = mlen + 1; + } + } + + if ((mp == NULL || max_mlen >= mp_match_len) && keylen != KEYLEN_PART_MAP) { + // No matching mapping found or found a non-matching mapping that + // matches at least what the matching mapping matched + keylen = 0; + (void)keylen; // suppress clang/dead assignment + // If there was no mapping, use the character from the typeahead + // buffer right here. Otherwise, use the mapping (loop around). + if (mp == NULL) { + *keylenp = keylen; + return map_result_get; // get character from typeahead + } else { + keylen = mp_match_len; + } + } + + // complete match + if (keylen >= 0 && keylen <= typebuf.tb_len) { + char_u *map_str = NULL; + + // Write chars to script file(s). + // Note: :lmap mappings are written *after* being applied. #5658 + if (keylen > typebuf.tb_maplen && (mp->m_mode & LANGMAP) == 0) { + gotchars(typebuf.tb_buf + typebuf.tb_off + typebuf.tb_maplen, + (size_t)(keylen - typebuf.tb_maplen)); + } + + cmd_silent = (typebuf.tb_silent > 0); + del_typebuf(keylen, 0); // remove the mapped keys + + // Put the replacement string in front of mapstr. + // The depth check catches ":map x y" and ":map y x". + if (++*mapdepth >= p_mmd) { + emsg(_("E223: recursive mapping")); + if (State & CMDLINE) { + redrawcmdline(); + } else { + setcursor(); + } + flush_buffers(FLUSH_MINIMAL); + *mapdepth = 0; // for next one + *keylenp = keylen; + return map_result_fail; + } + + // In Select mode and a Visual mode mapping is used: Switch to Visual + // mode temporarily. Append K_SELECT to switch back to Select mode. + if (VIsual_active && VIsual_select && (mp->m_mode & VISUAL)) { + VIsual_select = false; + (void)ins_typebuf(K_SELECT_STRING, REMAP_NONE, 0, true, false); + } + + // Copy the values from *mp that are used, because evaluating the + // expression may invoke a function that redefines the mapping, thereby + // making *mp invalid. + char save_m_expr = mp->m_expr; + int save_m_noremap = mp->m_noremap; + char 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) { + 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); + 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 { + map_str = mp->m_str; + } + + // Insert the 'to' part in the typebuf.tb_buf. + // If 'from' field is the same as the start of the 'to' field, don't + // remap the first character (but do allow abbreviations). + // If m_noremap is set, don't remap the whole 'to' part. + if (map_str == NULL) { + i = FAIL; + } else { + int noremap; + + // If this is a LANGMAP mapping, then we didn't record the keys + // at the start of the function and have to record them now. + if (keylen > typebuf.tb_maplen && (mp->m_mode & LANGMAP) != 0) { + gotchars(map_str, STRLEN(map_str)); + } + + if (save_m_noremap != REMAP_YES) { + noremap = save_m_noremap; + } else if (STRNCMP(map_str, save_m_keys != NULL ? save_m_keys : mp->m_keys, + (size_t)keylen) != 0) { + noremap = REMAP_YES; + } else { + noremap = REMAP_SKIP; + } + i = ins_typebuf(map_str, noremap, 0, true, cmd_silent || save_m_silent); + if (save_m_expr) { + xfree(map_str); + } + } + xfree(save_m_keys); + xfree(save_m_str); + *keylenp = keylen; + if (i == FAIL) { + return map_result_fail; + } + return map_result_retry; + } + + *keylenp = keylen; + return map_result_nomatch; +} + +/// unget one character (can only be done once!) +/// If the character was stuffed, vgetc() will get it next time it is called. +/// Otherwise vgetc() will only get it when the stuff buffer is empty. void vungetc(int c) { old_char = c; @@ -1688,6 +2085,21 @@ void vungetc(int c) old_mouse_grid = mouse_grid; old_mouse_row = mouse_row; old_mouse_col = mouse_col; + old_KeyStuffed = KeyStuffed; +} + +/// When peeking and not getting a character, reg_executing cannot be cleared +/// yet, so set a flag to clear it later. +void check_end_reg_executing(bool advance) +{ + if (reg_executing != 0 && (typebuf.tb_maplen == 0 || pending_end_reg_executing)) { + if (advance) { + reg_executing = 0; + pending_end_reg_executing = false; + } else { + pending_end_reg_executing = true; + } + } } /// Gets a byte: @@ -1711,48 +2123,31 @@ 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; - int keylen; - char_u *s; - mapblock_T *mp; - mapblock_T *mp2; - mapblock_T *mp_match; - int mp_match_len = 0; - bool timedout = false; // waited for more than 1 second - // for mapping to complete + bool timedout = false; // waited for more than 1 second for mapping to complete int mapdepth = 0; // check for recursive mapping bool mode_deleted = false; // set when mode has been deleted - int local_State; - int mlen; - int max_mlen; - int i; int new_wcol, new_wrow; int n; - int nolmaplen; int old_wcol, old_wrow; int wait_tb_len; - /* - * This function doesn't work very well when called recursively. This may - * happen though, because of: - * 1. The call to add_to_showcmd(). char_avail() is then used to check if - * there is a character available, which calls this function. In that - * case we must return NUL, to indicate no character is available. - * 2. A GUI callback function writes to the screen, causing a - * wait_return(). - * Using ":normal" can also do this, but it saves the typeahead buffer, - * thus it should be OK. But don't get a key from the user then. - */ - if (vgetc_busy > 0 - && ex_normal_busy == 0) { + // This function doesn't work very well when called recursively. This may + // happen though, because of: + // 1. The call to add_to_showcmd(). char_avail() is then used to check if + // there is a character available, which calls this function. In that + // case we must return NUL, to indicate no character is available. + // 2. A GUI callback function writes to the screen, causing a + // wait_return(). + // Using ":normal" can also do this, but it saves the typeahead buffer, + // thus it should be OK. But don't get a key from the user then. + if (vgetc_busy > 0 && ex_normal_busy == 0) { return NUL; } - local_State = get_real_state(); - ++vgetc_busy; if (advance) { @@ -1761,13 +2156,9 @@ static int vgetorpeek(bool advance) init_typebuf(); start_stuff(); - if (advance && typebuf.tb_maplen == 0) { - reg_executing = 0; - } + check_end_reg_executing(advance); do { - /* - * get a character: 1. from the stuffbuffer - */ + // get a character: 1. from the stuffbuffer if (typeahead_char != 0) { c = typeahead_char; if (advance) { @@ -1784,30 +2175,28 @@ static int vgetorpeek(bool advance) KeyStuffed = true; } if (typebuf.tb_no_abbr_cnt == 0) { - typebuf.tb_no_abbr_cnt = 1; // no abbreviations now + typebuf.tb_no_abbr_cnt = 1; // no abbreviations now } } else { - /* - * Loop until we either find a matching mapped key, or we - * are sure that it is not a mapped key. - * If a mapped key sequence is found we go back to the start to - * try re-mapping. - */ + // Loop until we either find a matching mapped key, or we + // are sure that it is not a mapped key. + // If a mapped key sequence is found we go back to the start to + // try re-mapping. for (;;) { - /* - * os_breakcheck() is slow, don't use it too often when - * inside a mapping. But call it each time for typed - * characters. - */ + check_end_reg_executing(advance); + // os_breakcheck() is slow, don't use it too often when + // inside a mapping. But call it each time for typed + // characters. if (typebuf.tb_maplen) { line_breakcheck(); } else { - os_breakcheck(); // check for CTRL-C + os_breakcheck(); // check for CTRL-C } - keylen = 0; + int keylen = 0; if (got_int) { // flush all input c = inchar(typebuf.tb_buf, typebuf.tb_buflen - 1, 0L); + // If inchar() returns TRUE (script file was active) or we // are inside a mapping, get out of Insert mode. // Otherwise we behave like having gotten a CTRL-C. @@ -1831,361 +2220,49 @@ static int vgetorpeek(bool advance) break; } else if (typebuf.tb_len > 0) { - /* - * Check for a mappable key sequence. - * Walk through one maphash[] list until we find an - * entry that matches. - * - * Don't look for mappings if: - * - no_mapping set: mapping disabled (e.g. for CTRL-V) - * - maphash_valid not set: no mappings present. - * - typebuf.tb_buf[typebuf.tb_off] should not be remapped - * - in insert or cmdline mode and 'paste' option set - * - waiting for "hit return to continue" and CR or SPACE - * typed - * - waiting for a char with --more-- - * - in Ctrl-X mode, and we get a valid char for that mode - */ - mp = NULL; - max_mlen = 0; - c1 = typebuf.tb_buf[typebuf.tb_off]; - if (no_mapping == 0 && maphash_valid - && (no_zero_mapping == 0 || c1 != '0') - && (typebuf.tb_maplen == 0 - || (p_remap - && (typebuf.tb_noremap[typebuf.tb_off] - & (RM_NONE|RM_ABBR)) == 0)) - && !(p_paste && (State & (INSERT + CMDLINE))) - && !(State == HITRETURN && (c1 == CAR || c1 == ' ')) - && State != ASKMORE - && State != CONFIRM - && !((ctrl_x_mode_not_default() && vim_is_ctrl_x_key(c1)) - || ((compl_cont_status & CONT_LOCAL) - && (c1 == Ctrl_N - || c1 == Ctrl_P)))) { - if (c1 == K_SPECIAL) { - nolmaplen = 2; - } else { - LANGMAP_ADJUST(c1, (State & (CMDLINE | INSERT)) == 0 - && get_real_state() != SELECTMODE); - nolmaplen = 0; - } - // First try buffer-local mappings. - mp = curbuf->b_maphash[MAP_HASH(local_State, c1)]; - mp2 = maphash[MAP_HASH(local_State, c1)]; - if (mp == NULL) { - // There are no buffer-local mappings. - mp = mp2; - mp2 = NULL; - } - /* - * Loop until a partly matching mapping is found or - * all (local) mappings have been checked. - * The longest full match is remembered in "mp_match". - * A full match is only accepted if there is no partly - * match, so "aa" and "aaa" can both be mapped. - */ - mp_match = NULL; - mp_match_len = 0; - for (; mp != NULL; - mp->m_next == NULL ? (mp = mp2, mp2 = NULL) : - (mp = mp->m_next)) { - /* - * Only consider an entry if the first character - * matches and it is for the current state. - * Skip ":lmap" mappings if keys were mapped. - */ - if (mp->m_keys[0] == c1 - && (mp->m_mode & local_State) - && ((mp->m_mode & LANGMAP) == 0 - || typebuf.tb_maplen == 0)) { - int nomap = nolmaplen; - int c2; - // find the match length of this mapping - for (mlen = 1; mlen < typebuf.tb_len; mlen++) { - c2 = typebuf.tb_buf[typebuf.tb_off + mlen]; - if (nomap > 0) { - --nomap; - } else if (c2 == K_SPECIAL) { - nomap = 2; - } else { - LANGMAP_ADJUST(c2, TRUE); - } - if (mp->m_keys[mlen] != c2) { - break; - } - } + // Check for a mapping in "typebuf". + map_result_T result = (map_result_T)handle_mapping(&keylen, &timedout, &mapdepth); - /* Don't allow mapping the first byte(s) of a - * multi-byte char. Happens when mapping - * <M-a> and then changing 'encoding'. Beware - * that 0x80 is escaped. */ - char_u *p1 = mp->m_keys; - char_u *p2 = (char_u *)mb_unescape((const char **)&p1); - - if (p2 != NULL && MB_BYTE2LEN(c1) > utfc_ptr2len(p2)) { - mlen = 0; - } - - // Check an entry whether it matches. - // - Full match: mlen == keylen - // - Partly match: mlen == typebuf.tb_len - keylen = mp->m_keylen; - if (mlen == keylen - || (mlen == typebuf.tb_len - && typebuf.tb_len < keylen)) { - /* - * If only script-local mappings are - * allowed, check if the mapping starts - * with K_SNR. - */ - s = typebuf.tb_noremap + typebuf.tb_off; - if (*s == RM_SCRIPT - && (mp->m_keys[0] != K_SPECIAL - || mp->m_keys[1] != KS_EXTRA - || mp->m_keys[2] - != KE_SNR)) { - continue; - } - /* - * If one of the typed keys cannot be - * remapped, skip the entry. - */ - for (n = mlen; --n >= 0;) { - if (*s++ & (RM_NONE|RM_ABBR)) { - break; - } - } - if (n >= 0) { - continue; - } - - if (keylen > typebuf.tb_len) { - if (!timedout && !(mp_match != NULL - && mp_match->m_nowait)) { - // break at a partly match - keylen = KEYLEN_PART_MAP; - break; - } - } else if (keylen > mp_match_len - || (keylen == mp_match_len - && mp_match != NULL - && (mp_match->m_mode & LANGMAP) == 0 - && (mp->m_mode & LANGMAP) != 0)) { - // found a longer match - mp_match = mp; - mp_match_len = keylen; - } - } else { - // No match; may have to check for termcode at next character. - if (max_mlen < mlen) { - max_mlen = mlen; - } - } - } - } - - /* If no partly match found, use the longest full - * match. */ - if (keylen != KEYLEN_PART_MAP) { - mp = mp_match; - keylen = mp_match_len; - } - } - - if (*p_pt != NUL && mp == NULL && (State & (INSERT|NORMAL))) { - bool match = typebuf_match_len(p_pt, &mlen); - if (match) { - // write chars to script file(s) - if (mlen > typebuf.tb_maplen) { - gotchars(typebuf.tb_buf + typebuf.tb_off + typebuf.tb_maplen, - (size_t)(mlen - typebuf.tb_maplen)); - } - - del_typebuf(mlen, 0); // Remove the chars. - set_option_value("paste", !p_paste, NULL, 0); - if (!(State & INSERT)) { - msg_col = 0; - msg_row = Rows - 1; - msg_clr_eos(); // clear ruler - } - status_redraw_all(); - redraw_statuslines(); - showmode(); - setcursor(); - continue; - } - // Need more chars for partly match. - if (mlen == typebuf.tb_len) { - keylen = KEYLEN_PART_KEY; - } else if (max_mlen < mlen) { - // no match, may have to check for termcode at - // next character - max_mlen = mlen + 1; - } + if (result == map_result_retry) { + // try mapping again + continue; } - if ((mp == NULL || max_mlen >= mp_match_len) - && keylen != KEYLEN_PART_MAP) { - // No matching mapping found or found a non-matching mapping that - // matches at least what the matching mapping matched - keylen = 0; - (void)keylen; // suppress clang/dead assignment - // If there was no mapping, use the character from the typeahead - // buffer right here. Otherwise, use the mapping (loop around). - if (mp == NULL) { - // get a character: 2. from the typeahead buffer - c = typebuf.tb_buf[typebuf.tb_off] & 255; - if (advance) { // remove chars from tb_buf - cmd_silent = (typebuf.tb_silent > 0); - if (typebuf.tb_maplen > 0) { - KeyTyped = false; - } else { - KeyTyped = true; - // write char to script file(s) - gotchars(typebuf.tb_buf + typebuf.tb_off, 1); - } - KeyNoremap = typebuf.tb_noremap[typebuf.tb_off]; - del_typebuf(1, 0); - } - break; // got character, break for loop - } else { - keylen = mp_match_len; - } + if (result == map_result_fail) { + // failed, use the outer loop + c = -1; + break; } - // complete match - if (keylen >= 0 && keylen <= typebuf.tb_len) { - int save_m_expr; - int save_m_noremap; - int save_m_silent; - - // Write chars to script file(s) - // Note: :lmap mappings are written *after* being applied. #5658 - if (keylen > typebuf.tb_maplen && (mp->m_mode & LANGMAP) == 0) { - gotchars(typebuf.tb_buf + typebuf.tb_off + typebuf.tb_maplen, - (size_t)(keylen - typebuf.tb_maplen)); - } - - cmd_silent = (typebuf.tb_silent > 0); - del_typebuf(keylen, 0); // remove the mapped keys - - /* - * Put the replacement string in front of mapstr. - * The depth check catches ":map x y" and ":map y x". - */ - if (++mapdepth >= p_mmd) { - emsg(_("E223: recursive mapping")); - if (State & CMDLINE) { - redrawcmdline(); - } else { - setcursor(); - } - flush_buffers(FLUSH_MINIMAL); - mapdepth = 0; // for next one - c = -1; - break; - } - - /* - * In Select mode and a Visual mode mapping is used: - * Switch to Visual mode temporarily. Append K_SELECT - * to switch back to Select mode. - */ - if (VIsual_active && VIsual_select - && (mp->m_mode & VISUAL)) { - VIsual_select = false; - (void)ins_typebuf(K_SELECT_STRING, REMAP_NONE, 0, true, false); - } - - /* Copy the values from *mp that are used, because - * evaluating the expression may invoke a function - * that redefines the mapping, thereby making *mp - * invalid. */ - save_m_expr = mp->m_expr; - save_m_noremap = mp->m_noremap; - 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 - - /* - * 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 bool save_may_garbage_collect = may_garbage_collect; - - vgetc_busy = 0; - may_garbage_collect = false; - - save_m_keys = vim_strsave(mp->m_keys); - save_m_str = vim_strsave(mp->m_str); - s = eval_map_expr(save_m_str, NUL); - vgetc_busy = save_vgetc_busy; - may_garbage_collect = save_may_garbage_collect; - } else { - s = mp->m_str; - } - - /* - * Insert the 'to' part in the typebuf.tb_buf. - * If 'from' field is the same as the start of the - * 'to' field, don't remap the first character (but do - * allow abbreviations). - * If m_noremap is set, don't remap the whole 'to' - * part. - */ - if (s == NULL) { - i = FAIL; - } else { - int noremap; - - // If this is a LANGMAP mapping, then we didn't record the keys - // at the start of the function and have to record them now. - if (keylen > typebuf.tb_maplen && (mp->m_mode & LANGMAP) != 0) { - gotchars(s, STRLEN(s)); - } - - if (save_m_noremap != REMAP_YES) { - noremap = save_m_noremap; - } else if ( - STRNCMP(s, save_m_keys != NULL - ? save_m_keys : mp->m_keys, - (size_t)keylen) - != 0) { - noremap = REMAP_YES; + if (result == map_result_get) { + // get a character: 2. from the typeahead buffer + c = typebuf.tb_buf[typebuf.tb_off] & 255; + if (advance) { // remove chars from tb_buf + cmd_silent = (typebuf.tb_silent > 0); + if (typebuf.tb_maplen > 0) { + KeyTyped = false; } else { - noremap = REMAP_SKIP; + KeyTyped = true; + // write char to script file(s) + gotchars(typebuf.tb_buf + typebuf.tb_off, 1); } - i = ins_typebuf(s, noremap, - 0, TRUE, cmd_silent || save_m_silent); - if (save_m_expr) { - xfree(s); - } - } - xfree(save_m_keys); - xfree(save_m_str); - if (i == FAIL) { - c = -1; - break; + KeyNoremap = typebuf.tb_noremap[typebuf.tb_off]; + del_typebuf(1, 0); } - continue; + break; // got character, break the for loop } + + // not enough characters, get more } - /* - * get a character: 3. from the user - handle <Esc> in Insert mode - */ - /* - * special case: if we get an <ESC> in insert mode and there - * are no more characters at once, we pretend to go out of - * insert mode. This prevents the one second delay after - * typing an <ESC>. If we get something after all, we may - * have to redisplay the mode. That the cursor is in the wrong - * place does not matter. - */ + // get a character: 3. from the user - handle <Esc> in Insert mode + + // special case: if we get an <ESC> in insert mode and there + // are no more characters at once, we pretend to go out of + // insert mode. This prevents the one second delay after + // typing an <ESC>. If we get something after all, we may + // have to redisplay the mode. That the cursor is in the wrong + // place does not matter. c = 0; new_wcol = curwin->w_wcol; new_wrow = curwin->w_wrow; @@ -2196,10 +2273,8 @@ static int vgetorpeek(bool advance) && ex_normal_busy == 0 && typebuf.tb_maplen == 0 && (State & INSERT) - && (p_timeout - || (keylen == KEYLEN_PART_KEY && p_ttimeout)) - && (c = inchar(typebuf.tb_buf + typebuf.tb_off + typebuf.tb_len, - 3, 25L)) == 0) { + && (p_timeout || (keylen == KEYLEN_PART_KEY && p_ttimeout)) + && (c = inchar(typebuf.tb_buf + typebuf.tb_off + typebuf.tb_len, 3, 25L)) == 0) { colnr_T col = 0, vcol; char_u *ptr; @@ -2215,11 +2290,9 @@ static int vgetorpeek(bool advance) if (curwin->w_cursor.col != 0) { if (curwin->w_wcol > 0) { if (did_ai) { - /* - * We are expecting to truncate the trailing - * white-space, so find the last non-white - * character -- webb - */ + // We are expecting to truncate the trailing + // white-space, so find the last non-white + // character -- webb col = vcol = curwin->w_wcol = 0; ptr = get_cursor_line_ptr(); while (col < curwin->w_cursor.col) { @@ -2233,7 +2306,7 @@ static int vgetorpeek(bool advance) + curwin->w_wcol / curwin->w_width_inner; curwin->w_wcol %= curwin->w_width_inner; curwin->w_wcol += curwin_col_off(); - col = 0; // no correction needed + col = 0; // no correction needed } else { --curwin->w_wcol; col = curwin->w_cursor.col - 1; @@ -2261,7 +2334,7 @@ static int vgetorpeek(bool advance) curwin->w_wrow = old_wrow; } if (c < 0) { - continue; // end of input script reached + continue; // end of input script reached } // Allow mapping for just typed characters. When we get here c @@ -2296,20 +2369,21 @@ static int vgetorpeek(bool advance) // cmdline window. if (p_im && (State & INSERT)) { c = Ctrl_L; - } else if (exmode_active) { - c = '\n'; } else if ((State & CMDLINE) || (cmdwin_type > 0 && tc == ESC)) { c = Ctrl_C; } else { c = ESC; } tc = c; + + // no chars to block abbreviations for + typebuf.tb_no_abbr_cnt = 0; + break; } - /* - * get a character: 3. from the user - update display - */ + // get a character: 3. from the user - update display + // In insert mode a screen update is skipped when characters // are still available. But when those available characters // are part of a mapping, and we are going to do a blocking @@ -2320,26 +2394,21 @@ static int vgetorpeek(bool advance) if (((State & INSERT) != 0 || p_lz) && (State & CMDLINE) == 0 && advance && must_redraw != 0 && !need_wait_return) { update_screen(0); - setcursor(); // put cursor back where it belongs + setcursor(); // put cursor back where it belongs } - /* - * If we have a partial match (and are going to wait for more - * input from the user), show the partially matched characters - * to the user with showcmd. - */ - i = 0; + // If we have a partial match (and are going to wait for more + // input from the user), show the partially matched characters + // to the user with showcmd. + int showcmd_idx = 0; c1 = 0; if (typebuf.tb_len > 0 && advance && !exmode_active) { - if (((State & (NORMAL | INSERT)) || State == LANGMAP) - && State != HITRETURN) { + if (((State & (NORMAL | INSERT)) || State == LANGMAP) && State != HITRETURN) { // this looks nice when typing a dead character map if (State & INSERT - && ptr2cells(typebuf.tb_buf + typebuf.tb_off - + typebuf.tb_len - 1) == 1) { - edit_putchar(typebuf.tb_buf[typebuf.tb_off + typebuf.tb_len - 1], - false); - setcursor(); // put cursor back where it belongs + && ptr2cells(typebuf.tb_buf + typebuf.tb_off + typebuf.tb_len - 1) == 1) { + edit_putchar(typebuf.tb_buf[typebuf.tb_off + typebuf.tb_len - 1], false); + setcursor(); // put cursor back where it belongs c1 = 1; } // need to use the col and row from above here @@ -2349,11 +2418,10 @@ static int vgetorpeek(bool advance) curwin->w_wrow = new_wrow; push_showcmd(); if (typebuf.tb_len > SHOWCMD_COLS) { - i = typebuf.tb_len - SHOWCMD_COLS; + showcmd_idx = typebuf.tb_len - SHOWCMD_COLS; } - while (i < typebuf.tb_len) { - (void)add_to_showcmd(typebuf.tb_buf[typebuf.tb_off - + i++]); + while (showcmd_idx < typebuf.tb_len) { + (void)add_to_showcmd(typebuf.tb_buf[typebuf.tb_off + showcmd_idx++]); } curwin->w_wcol = old_wcol; curwin->w_wrow = old_wrow; @@ -2369,9 +2437,7 @@ static int vgetorpeek(bool advance) } } - /* - * get a character: 3. from the user - get it - */ + // get a character: 3. from the user - get it if (typebuf.tb_len == 0) { // timedout may have been set while waiting for a mapping // that has a <Nop> RHS. @@ -2381,8 +2447,7 @@ static int vgetorpeek(bool advance) long wait_time = 0; if (advance) { - if (typebuf.tb_len == 0 - || !(p_timeout || (p_ttimeout && keylen == KEYLEN_PART_KEY))) { + if (typebuf.tb_len == 0 || !(p_timeout || (p_ttimeout && keylen == KEYLEN_PART_KEY))) { // blocking wait wait_time = -1L; } else if (keylen == KEYLEN_PART_KEY && p_ttm >= 0) { @@ -2397,7 +2462,7 @@ static int vgetorpeek(bool advance) typebuf.tb_buflen - typebuf.tb_off - typebuf.tb_len - 1, wait_time); - if (i != 0) { + if (showcmd_idx != 0) { pop_showcmd(); } if (c1 == 1) { @@ -2407,47 +2472,45 @@ static int vgetorpeek(bool advance) if (State & CMDLINE) { unputcmdline(); } else { - setcursor(); // put cursor back where it belongs + setcursor(); // put cursor back where it belongs } } if (c < 0) { - continue; // end of input script reached + continue; // end of input script reached } - if (c == NUL) { // no character available + if (c == NUL) { // no character available if (!advance) { break; } - if (wait_tb_len > 0) { // timed out + if (wait_tb_len > 0) { // timed out timedout = true; continue; } - } else { // allow mapping for just typed characters + } else { // allow mapping for just typed characters while (typebuf.tb_buf[typebuf.tb_off + typebuf.tb_len] != NUL) { typebuf.tb_noremap[typebuf.tb_off + typebuf.tb_len++] = RM_YES; } } - } // for (;;) - } // if (!character from stuffbuf) + } // for (;;) + } // if (!character from stuffbuf) // if advance is false don't loop on NULs } while (c < 0 || (advance && c == NUL)); - /* - * The "INSERT" message is taken care of here: - * if we return an ESC to exit insert mode, the message is deleted - * if we don't return an ESC but deleted the message before, redisplay it - */ + // The "INSERT" message is taken care of here: + // if we return an ESC to exit insert mode, the message is deleted + // if we don't return an ESC but deleted the message before, redisplay it if (advance && p_smd && msg_silent == 0 && (State & INSERT)) { if (c == ESC && !mode_deleted && !no_mapping && mode_displayed) { if (typebuf.tb_len && !KeyTyped) { - redraw_cmdline = true; // delete mode later + redraw_cmdline = true; // delete mode later } else { unshowmode(false); } } else if (c != ESC && mode_deleted) { if (typebuf.tb_len && !KeyTyped) { - redraw_cmdline = true; // show mode later + redraw_cmdline = true; // show mode later } else { showmode(); } @@ -2474,7 +2537,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!!!! /// @@ -2492,7 +2555,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. @@ -2547,7 +2610,7 @@ int inchar(char_u *buf, int maxlen, long wait_time) // Don't use buf[] here, closescript() may have freed typebuf.tb_buf[] // and buf may be pointing inside typebuf.tb_buf[]. if (got_int) { -#define DUM_LEN MAXMAPLEN * 3 + 3 +#define DUM_LEN (MAXMAPLEN * 3 + 3) char_u dum[DUM_LEN + 1]; for (;;) { @@ -2591,7 +2654,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; @@ -2602,9 +2665,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 @@ -2640,11 +2702,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) + const size_t orig_rhs_len, LuaRef rhs_lua, int cpo_flags, + MapArguments *mapargs) { char_u *lhs_buf = NULL; char_u *rhs_buf = NULL; @@ -2660,22 +2724,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); @@ -2787,7 +2863,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); @@ -2849,7 +2925,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) { @@ -3039,10 +3115,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; @@ -3050,6 +3130,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; } } @@ -3118,6 +3202,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; @@ -3126,6 +3211,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) { @@ -3222,8 +3312,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); } @@ -3414,7 +3506,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; } @@ -3459,19 +3552,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 } @@ -3554,8 +3657,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; } } @@ -3840,9 +3942,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 @@ -3888,7 +3990,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); @@ -3901,7 +4003,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; } @@ -3931,22 +4033,21 @@ 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 *save_cmd; + char_u *p = NULL; + char_u *expr = NULL; 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); - - save_cmd = save_cmdline_alloc(); + // 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); + } // Forbid changing text or using ":normal" to avoid most of the bad side // effects. Also restore the cursor position. @@ -3956,31 +4057,41 @@ 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; msg_col = save_msg_col; 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: @@ -3995,7 +4106,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); } @@ -4005,11 +4116,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; @@ -4017,10 +4126,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++; } @@ -4071,11 +4176,14 @@ 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) { + && p[2] == KE_SNR) { break; } } @@ -4260,7 +4368,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) { @@ -4353,10 +4461,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(); @@ -4397,7 +4506,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; } } } @@ -4582,3 +4692,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..9b8605f1df 100644 --- a/src/nvim/getchar.h +++ b/src/nvim/getchar.h @@ -50,17 +50,19 @@ 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 +#define KEYLEN_PART_KEY (-1) // keylen value for incomplete key-code +#define KEYLEN_PART_MAP (-2) // keylen value for incomplete mapping /// Maximum number of streams to read script from enum { NSCRIPT = 15, }; diff --git a/src/nvim/globals.h b/src/nvim/globals.h index 9c065c09f4..2b85b6a208 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -4,6 +4,7 @@ #include <inttypes.h> #include <stdbool.h> +#include "nvim/ascii.h" #include "nvim/event/loop.h" #include "nvim/ex_eval.h" #include "nvim/iconv.h" @@ -27,7 +28,7 @@ #endif #ifndef FILETYPE_FILE -# define FILETYPE_FILE "filetype.vim" +# define FILETYPE_FILE "filetype.lua filetype.vim" #endif #ifndef FTPLUGIN_FILE @@ -126,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 @@ -201,7 +202,6 @@ EXTERN bool msg_did_scroll INIT(= false); EXTERN char_u *keep_msg INIT(= NULL); // msg to be shown after redraw EXTERN int keep_msg_attr INIT(= 0); // highlight attr for keep_msg -EXTERN bool keep_msg_more INIT(= false); // keep_msg was set by msgmore() EXTERN bool need_fileinfo INIT(= false); // do fileinfo() after redraw EXTERN int msg_scroll INIT(= false); // msg_start() will scroll EXTERN bool msg_didout INIT(= false); // msg_outstr() was used in line @@ -326,22 +326,25 @@ 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 }); // ID of the current channel making a client API call EXTERN uint64_t current_channel_id INIT(= 0); +// ID of the client channel. Used by ui client +EXTERN uint64_t ui_client_channel_id INIT(= 0); + EXTERN bool did_source_packages INIT(= false); // Scope information for the code that indirectly triggered the current @@ -358,6 +361,11 @@ EXTERN int provider_call_nesting INIT(= 0); EXTERN int t_colors INIT(= 256); // int value of T_CCO +// Flags to indicate an additional string for highlight name completion. +EXTERN int include_none INIT(= 0); // when 1 include "None" +EXTERN int include_default INIT(= 0); // when 1 include "default" +EXTERN int include_link INIT(= 0); // when 2 include "link" and "clear" + // When highlight_match is true, highlight a match, starting at the cursor // position. Search_match_lines is the number of lines after the match (0 for // a match within one line), search_match_endcol the column number of the @@ -422,7 +430,7 @@ EXTERN win_T *lastwin; // last window EXTERN win_T *prevwin INIT(= NULL); // previous window #define ONE_WINDOW (firstwin == lastwin) #define FOR_ALL_FRAMES(frp, first_frame) \ - for (frp = first_frame; frp != NULL; frp = frp->fr_next) // NOLINT + for ((frp) = first_frame; (frp) != NULL; (frp) = (frp)->fr_next) // NOLINT // When using this macro "break" only breaks out of the inner loop. Use "goto" // to break out of the tabpage loop. @@ -445,14 +453,15 @@ EXTERN int aucmd_win_used INIT(= false); // aucmd_win is being used EXTERN frame_T *topframe; // top of the window frame tree // Tab pages are alternative topframes. "first_tabpage" points to the first -// one in the list, "curtab" is the current one. +// one in the list, "curtab" is the current one. "lastused_tabpage" is the +// last used one. EXTERN tabpage_T *first_tabpage; -EXTERN tabpage_T *lastused_tabpage; EXTERN tabpage_T *curtab; +EXTERN tabpage_T *lastused_tabpage; EXTERN bool redraw_tabline INIT(= false); // need to redraw tabline // Iterates over all tabs in the tab list -#define FOR_ALL_TABS(tp) for (tabpage_T *tp = first_tabpage; tp != NULL; tp = tp->tp_next) +#define FOR_ALL_TABS(tp) for (tabpage_T *(tp) = first_tabpage; (tp) != NULL; (tp) = (tp)->tp_next) // All buffers are linked in a list. 'firstbuf' points to the first entry, // 'lastbuf' to the last entry and 'curbuf' to the currently active buffer. @@ -468,7 +477,7 @@ EXTERN buf_T *curbuf INIT(= NULL); // currently active buffer // Iterate through all the signs placed in a buffer #define FOR_ALL_SIGNS_IN_BUF(buf, sign) \ - for (sign = buf->b_signlist; sign != NULL; sign = sign->se_next) // NOLINT + for ((sign) = (buf)->b_signlist; (sign) != NULL; (sign) = (sign)->se_next) // NOLINT // List of files being edited (global argument list). curwin->w_alist points @@ -524,6 +533,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. @@ -533,6 +544,11 @@ EXTERN int VIsual_mode INIT(= 'v'); /// true when redoing Visual. EXTERN int redo_VIsual_busy INIT(= false); +// The Visual area is remembered for reselection. +EXTERN int resel_VIsual_mode INIT(= NUL); // 'v', 'V', or Ctrl-V +EXTERN linenr_T resel_VIsual_line_count; // number of lines +EXTERN colnr_T resel_VIsual_vcol; // nr of cols or end col + /// When pasting text with the middle mouse button in visual mode with /// restart_edit set, remember where it started so we can set Insstart. EXTERN pos_T where_paste_started; @@ -602,7 +618,7 @@ EXTERN int inhibit_delete_count INIT(= 0); #define DBCS_CHT 950 // taiwan #define DBCS_CHTU 9950 // euc-tw #define DBCS_2BYTE 1 // 2byte- -#define DBCS_DEBUG -1 +#define DBCS_DEBUG (-1) /// Encoding used when 'fencs' is set to "default" EXTERN char_u *fenc_default INIT(= NULL); @@ -627,6 +643,9 @@ EXTERN bool ex_no_reprint INIT(=false); // No need to print after z or p. EXTERN int reg_recording INIT(= 0); // register for recording or zero EXTERN int reg_executing INIT(= 0); // register being executed or zero +// Flag set when peeking a character and found the end of executed register +EXTERN bool pending_end_reg_executing INIT(= false); +EXTERN int reg_recorded INIT(= 0); // last recorded register or zero EXTERN int no_mapping INIT(= false); // currently no mapping allowed EXTERN int no_zero_mapping INIT(= 0); // mapping zero not allowed @@ -661,11 +680,8 @@ EXTERN bool cmd_silent INIT(= false); // don't echo the command line #define SEA_QUIT 2 // quit editing the file #define SEA_RECOVER 3 // recover the file -EXTERN int swap_exists_action INIT(= SEA_NONE); -// For dialog when swap file already -// exists. -EXTERN bool swap_exists_did_quit INIT(= false); -// Selected "quit" at the dialog. +EXTERN int swap_exists_action INIT(= SEA_NONE); ///< For dialog when swap file already exists. +EXTERN bool swap_exists_did_quit INIT(= false); ///< Selected "quit" at the dialog. EXTERN char_u IObuff[IOSIZE]; ///< Buffer for sprintf, I/O, etc. EXTERN char_u NameBuff[MAXPATHL]; ///< Buffer for expanding file names @@ -718,15 +734,16 @@ EXTERN reg_extmatch_T *re_extmatch_in INIT(= NULL); // Set by vim_regexec() to store \z\(...\) matches EXTERN reg_extmatch_T *re_extmatch_out INIT(= NULL); -EXTERN bool did_outofmem_msg INIT(= false); -// set after out of memory msg -EXTERN bool did_swapwrite_msg INIT(= false); -// set after swap write error msg -EXTERN int global_busy INIT(= 0); // set when :global is executing -EXTERN bool listcmd_busy INIT(= false); // set when :argdo, :windo or - // :bufdo is executing -EXTERN bool need_start_insertmode INIT(= false); -// start insert mode soon +EXTERN bool did_outofmem_msg INIT(= false); ///< set after out of memory msg +EXTERN bool did_swapwrite_msg INIT(= false); ///< set after swap write error msg +EXTERN int global_busy INIT(= 0); ///< set when :global is executing +EXTERN bool listcmd_busy INIT(= false); ///< set when :argdo, :windo or :bufdo is executing +EXTERN bool need_start_insertmode INIT(= false); ///< start insert mode soon + +#define MODE_MAX_LENGTH 4 // max mode length returned in get_mode() + // including the final NUL character + +EXTERN char last_mode[MODE_MAX_LENGTH] INIT(= "n"); EXTERN char_u *last_cmdline INIT(= NULL); // last command line (for ":) EXTERN char_u *repeat_cmdline INIT(= NULL); // command line for "." EXTERN char_u *new_last_cmdline INIT(= NULL); // new value for last_cmdline @@ -746,8 +763,7 @@ EXTERN bool g_tag_at_cursor INIT(= false); // whether the tag command comes EXTERN int replace_offset INIT(= 0); // offset for replace_push() -EXTERN char_u *escape_chars INIT(= (char_u *)" \t\\\"|"); -// need backslash in cmd line +EXTERN char_u *escape_chars INIT(= (char_u *)" \t\\\"|"); // need backslash in cmd line EXTERN int keep_help_flag INIT(= false); // doing :ta from help file @@ -789,6 +805,8 @@ extern char_u *compiled_sys; // directory is not a local directory, globaldir is NULL. EXTERN char_u *globaldir INIT(= NULL); +EXTERN char *last_chdir_reason INIT(= NULL); + // Whether 'keymodel' contains "stopsel" and "startsel". EXTERN bool km_stopsel INIT(= false); EXTERN bool km_startsel INIT(= false); @@ -968,6 +986,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")); @@ -988,6 +1007,12 @@ 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")); EXTERN char bot_top_msg[] INIT(= N_("search hit BOTTOM, continuing at TOP")); diff --git a/src/nvim/hardcopy.c b/src/nvim/hardcopy.c index 32b471efb0..575b239f5a 100644 --- a/src/nvim/hardcopy.c +++ b/src/nvim/hardcopy.c @@ -22,6 +22,7 @@ #include "nvim/fileio.h" #include "nvim/garray.h" #include "nvim/hardcopy.h" +#include "nvim/highlight_group.h" #include "nvim/mbyte.h" #include "nvim/memline.h" #include "nvim/memory.h" @@ -386,30 +387,46 @@ 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->underlineline = (highlight_has_attr(hl_id, HL_UNDERLINELINE, modec) != NULL); pattr->undercurl = (highlight_has_attr(hl_id, HL_UNDERCURL, modec) != NULL); + pattr->underdot = (highlight_has_attr(hl_id, HL_UNDERDOT, modec) != NULL); + pattr->underdash = (highlight_has_attr(hl_id, HL_UNDERDASH, 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; @@ -986,8 +1003,7 @@ static colnr_T hardcopy_line(prt_settings_T *psettings, int page_line, prt_pos_T #define PRT_PS_DEFAULT_DPI (72) // Default user space resolution #define PRT_PS_DEFAULT_FONTSIZE (10) -#define PRT_MEDIASIZE_LEN (sizeof(prt_mediasize) / \ - sizeof(struct prt_mediasize_S)) +#define PRT_MEDIASIZE_LEN ARRAY_SIZE(prt_mediasize) static struct prt_mediasize_S prt_mediasize[] = { @@ -1254,7 +1270,7 @@ static struct prt_dsc_comment_S prt_dsc_table[] = * Variables for the output PostScript file. */ static FILE *prt_ps_fd; -static int prt_file_error; +static bool prt_file_error; static char_u *prt_ps_file_name = NULL; /* @@ -1330,7 +1346,7 @@ static void prt_write_file_raw_len(char_u *buffer, size_t bytes) if (!prt_file_error && fwrite(buffer, sizeof(char_u), bytes, prt_ps_fd) != bytes) { emsg(_("E455: Error writing to PostScript output file")); - prt_file_error = TRUE; + prt_file_error = true; } } @@ -1982,7 +1998,7 @@ void mch_print_cleanup(void) if (prt_ps_fd != NULL) { fclose(prt_ps_fd); prt_ps_fd = NULL; - prt_file_error = FALSE; + prt_file_error = false; } if (prt_ps_file_name != NULL) { XFREE_CLEAR(prt_ps_file_name); @@ -2204,7 +2220,7 @@ int mch_print_init(prt_settings_T *psettings, char_u *jobname, int forceit) // Check encoding and character set are compatible if ((p_mbenc->needs_charset & p_mbchar->has_charset) == 0) { emsg(_("E673: Incompatible multi-byte encoding and character set.")); - return FALSE; + return false; } // Add charset name if not empty @@ -2216,7 +2232,7 @@ int mch_print_init(prt_settings_T *psettings, char_u *jobname, int forceit) // Add custom CMap character set name if (*p_pmcs == NUL) { emsg(_("E674: printmbcharset cannot be empty with multi-byte encoding.")); - return FALSE; + return false; } STRLCPY(prt_cmap, p_pmcs, sizeof(prt_cmap) - 2); STRCAT(prt_cmap, "-"); @@ -2232,7 +2248,7 @@ int mch_print_init(prt_settings_T *psettings, char_u *jobname, int forceit) if (!mbfont_opts[OPT_MBFONT_REGULAR].present) { emsg(_("E675: No default font specified for multi-byte printing.")); - return FALSE; + return false; } // Derive CID font names with fallbacks if not defined @@ -2426,12 +2442,12 @@ int mch_print_init(prt_settings_T *psettings, char_u *jobname, int forceit) prt_need_bgcol = false; prt_need_underline = false; - prt_file_error = FALSE; + prt_file_error = false; return OK; } -static int prt_add_resource(struct prt_ps_resource_S *resource) +static bool prt_add_resource(struct prt_ps_resource_S *resource) { FILE *fd_resource; char_u resource_buffer[512]; @@ -2440,7 +2456,7 @@ static int prt_add_resource(struct prt_ps_resource_S *resource) fd_resource = os_fopen((char *)resource->filename, READBIN); if (fd_resource == NULL) { semsg(_("E456: Can't open file \"%s\""), resource->filename); - return FALSE; + return false; } switch (resource->type) { case PRT_RESOURCE_TYPE_PROCSET: @@ -2450,7 +2466,7 @@ static int prt_add_resource(struct prt_ps_resource_S *resource) (char *)resource->title); break; default: - return FALSE; + return false; } prt_dsc_textline("BeginDocument", (char *)resource->filename); @@ -2462,7 +2478,7 @@ static int prt_add_resource(struct prt_ps_resource_S *resource) semsg(_("E457: Can't read PostScript resource file \"%s\""), resource->filename); fclose(fd_resource); - return FALSE; + return false; } if (bytes_read == 0) { break; @@ -2470,7 +2486,7 @@ static int prt_add_resource(struct prt_ps_resource_S *resource) prt_write_file_raw_len(resource_buffer, bytes_read); if (prt_file_error) { fclose(fd_resource); - return FALSE; + return false; } } fclose(fd_resource); @@ -2479,10 +2495,10 @@ static int prt_add_resource(struct prt_ps_resource_S *resource) prt_dsc_noarg("EndResource"); - return TRUE; + return true; } -int mch_print_begin(prt_settings_T *psettings) +bool mch_print_begin(prt_settings_T *psettings) { int bbox[4]; double left; @@ -2496,7 +2512,6 @@ int mch_print_begin(prt_settings_T *psettings) char_u *p; struct prt_ps_resource_S res_cidfont; struct prt_ps_resource_S res_cmap; - int retval = FALSE; /* * PS DSC Header comments - no PS code! @@ -2568,25 +2583,25 @@ int mch_print_begin(prt_settings_T *psettings) // Search for external resources VIM supplies if (!prt_find_resource("prolog", &res_prolog)) { emsg(_("E456: Can't find PostScript resource file \"prolog.ps\"")); - return FALSE; + return false; } if (!prt_open_resource(&res_prolog)) { - return FALSE; + return false; } if (!prt_check_resource(&res_prolog, PRT_PROLOG_VERSION)) { - return FALSE; + return false; } if (prt_out_mbyte) { // Look for required version of multi-byte printing procset if (!prt_find_resource("cidfont", &res_cidfont)) { emsg(_("E456: Can't find PostScript resource file \"cidfont.ps\"")); - return FALSE; + return false; } if (!prt_open_resource(&res_cidfont)) { - return FALSE; + return false; } if (!prt_check_resource(&res_cidfont, PRT_CID_PROLOG_VERSION)) { - return FALSE; + return false; } } @@ -2611,12 +2626,12 @@ int mch_print_begin(prt_settings_T *psettings) if (!prt_find_resource(p_encoding, &res_encoding)) { semsg(_("E456: Can't find PostScript resource file \"%s.ps\""), p_encoding); - return FALSE; + return false; } } } if (!prt_open_resource(&res_encoding)) { - return FALSE; + return false; } // For the moment there are no checks on encoding resource files to // perform @@ -2630,10 +2645,10 @@ int mch_print_begin(prt_settings_T *psettings) if (!prt_find_resource(prt_ascii_encoding, &res_encoding)) { semsg(_("E456: Can't find PostScript resource file \"%s.ps\""), prt_ascii_encoding); - return FALSE; + return false; } if (!prt_open_resource(&res_encoding)) { - return FALSE; + return false; } // For the moment there are no checks on encoding resource files to // perform @@ -2656,10 +2671,10 @@ int mch_print_begin(prt_settings_T *psettings) if (!prt_find_resource(prt_cmap, &res_cmap)) { semsg(_("E456: Can't find PostScript resource file \"%s.ps\""), prt_cmap); - return FALSE; + return false; } if (!prt_open_resource(&res_cmap)) { - return FALSE; + return false; } } @@ -2737,7 +2752,7 @@ int mch_print_begin(prt_settings_T *psettings) // There will be only one Roman font encoding to be included in the PS // file. if (!prt_add_resource(&res_encoding)) { - return FALSE; + return false; } } @@ -2847,9 +2862,7 @@ int mch_print_begin(prt_settings_T *psettings) prt_dsc_noarg("EndSetup"); // Fail if any problems writing out to the PS file - retval = !prt_file_error; - - return retval; + return !prt_file_error; } void mch_print_end(prt_settings_T *psettings) diff --git a/src/nvim/hardcopy.h b/src/nvim/hardcopy.h index eba769d342..16119c5d2d 100644 --- a/src/nvim/hardcopy.h +++ b/src/nvim/hardcopy.h @@ -18,6 +18,9 @@ typedef struct { TriState italic; TriState underline; int undercurl; + int underlineline; + int underdot; + int underdash; } prt_text_attr_T; /* diff --git a/src/nvim/highlight.c b/src/nvim/highlight.c index 3050ca02de..7d91f38d56 100644 --- a/src/nvim/highlight.c +++ b/src/nvim/highlight.c @@ -5,15 +5,16 @@ #include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" +#include "nvim/decoration_provider.h" #include "nvim/highlight.h" #include "nvim/highlight_defs.h" +#include "nvim/highlight_group.h" #include "nvim/lua/executor.h" #include "nvim/map.h" #include "nvim/message.h" #include "nvim/option.h" #include "nvim/popupmnu.h" #include "nvim/screen.h" -#include "nvim/syntax.h" #include "nvim/ui.h" #include "nvim/vim.h" @@ -144,13 +145,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 +199,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); @@ -327,7 +328,7 @@ void update_window_hl(win_T *wp, bool invalid) wp->w_hl_attr_normal); } - for (int hlf = 0; hlf < (int)HLF_COUNT; hlf++) { + for (int hlf = 0; hlf < HLF_COUNT; hlf++) { int attr; if (wp->w_hl_ids[hlf] != 0) { attr = hl_get_ui_attr(hlf, wp->w_hl_ids[hlf], false); @@ -558,7 +559,7 @@ int hl_blend_attrs(int back_attr, int front_attr, bool *through) cattrs = battrs; cattrs.rgb_fg_color = rgb_blend(ratio, battrs.rgb_fg_color, fattrs.rgb_bg_color); - if (cattrs.rgb_ae_attr & (HL_UNDERLINE|HL_UNDERCURL)) { + if (cattrs.rgb_ae_attr & (HL_ANY_UNDERLINE)) { cattrs.rgb_sp_color = rgb_blend(ratio, battrs.rgb_sp_color, fattrs.rgb_bg_color); } else { @@ -576,7 +577,7 @@ int hl_blend_attrs(int back_attr, int front_attr, bool *through) } cattrs.rgb_fg_color = rgb_blend(ratio/2, battrs.rgb_fg_color, fattrs.rgb_fg_color); - if (cattrs.rgb_ae_attr & (HL_UNDERLINE|HL_UNDERCURL)) { + if (cattrs.rgb_ae_attr & (HL_ANY_UNDERLINE)) { cattrs.rgb_sp_color = rgb_blend(ratio/2, battrs.rgb_bg_color, fattrs.rgb_sp_color); } else { @@ -743,10 +744,23 @@ Dictionary hlattrs2dict(HlAttrs ae, bool use_rgb) PUT(hl, "underline", BOOLEAN_OBJ(true)); } + if (mask & HL_UNDERLINELINE) { + PUT(hl, "underlineline", BOOLEAN_OBJ(true)); + } + if (mask & HL_UNDERCURL) { PUT(hl, "undercurl", BOOLEAN_OBJ(true)); } + if (mask & HL_UNDERDOT) { + PUT(hl, "underdot", BOOLEAN_OBJ(true)); + } + + if (mask & HL_UNDERDASH) { + PUT(hl, "underdash", BOOLEAN_OBJ(true)); + } + + if (mask & HL_ITALIC) { PUT(hl, "italic", BOOLEAN_OBJ(true)); } @@ -796,112 +810,123 @@ 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; \ + } + + 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, underlineline, , HL_UNDERLINELINE); + CHECK_FLAG(dict, mask, undercurl, , HL_UNDERCURL); + CHECK_FLAG(dict, mask, underdot, , HL_UNDERDOT); + CHECK_FLAG(dict, mask, underdash, , HL_UNDERDASH); + 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; + } - // 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; - } - } + 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'"); } + } - 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; - } + // 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; } - 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"); - } + 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; } + } + if (HAS_KEY(dict->ctermbg)) { + ctermbg = object_to_color(dict->ctermbg, "ctermbg", false, err); if (ERROR_SET(err)) { - return hlattrs; // error set, caller should not use retval + return hlattrs; } } @@ -914,19 +939,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/highlight_defs.h b/src/nvim/highlight_defs.h index d4d53c4126..e0ee649013 100644 --- a/src/nvim/highlight_defs.h +++ b/src/nvim/highlight_defs.h @@ -24,6 +24,10 @@ typedef enum { HL_FG_INDEXED = 0x0200, HL_DEFAULT = 0x0400, HL_GLOBAL = 0x0800, + HL_UNDERLINELINE = 0x1000, + HL_UNDERDOT = 0x2000, + HL_UNDERDASH = 0x4000, + HL_ANY_UNDERLINE = HL_UNDERLINE | HL_UNDERLINELINE | HL_UNDERCURL | HL_UNDERDOT | HL_UNDERDASH, } HlAttrFlags; /// Stores a complete highlighting entry, including colors and attributes @@ -65,10 +69,13 @@ typedef enum { HLF_LNA, // LineNrAbove HLF_LNB, // LineNrBelow HLF_CLN, // current line number when 'cursorline' is set + HLF_CLS, // current line sign column + HLF_CLF, // current line fold HLF_R, // return to continue message and yes/no questions HLF_S, // status lines HLF_SNC, // status lines of not-current windows - HLF_C, // column to separate vertically split windows + HLF_C, // window split separators + HLF_VSP, // VertSplit HLF_T, // Titles for output from ":set all", ":autocmd" etc. HLF_V, // Visual mode HLF_VNC, // Visual mode, autoselecting and not clipboard owner @@ -122,13 +129,16 @@ EXTERN const char *hlf_names[] INIT(= { [HLF_LNA] = "LineNrAbove", [HLF_LNB] = "LineNrBelow", [HLF_CLN] = "CursorLineNr", + [HLF_CLS] = "CursorLineSign", + [HLF_CLF] = "CursorLineFold", [HLF_R] = "Question", [HLF_S] = "StatusLine", [HLF_SNC] = "StatusLineNC", - [HLF_C] = "VertSplit", + [HLF_C] = "WinSeparator", [HLF_T] = "Title", [HLF_V] = "Visual", [HLF_VNC] = "VisualNC", + [HLF_VSP] = "VertSplit", [HLF_W] = "WarningMsg", [HLF_WM] = "WildMenu", [HLF_FL] = "Folded", diff --git a/src/nvim/highlight_group.c b/src/nvim/highlight_group.c new file mode 100644 index 0000000000..d448f3a646 --- /dev/null +++ b/src/nvim/highlight_group.c @@ -0,0 +1,2799 @@ +// 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 + +// highlight_group.c: code for managing highlight groups + +#include <stdbool.h> +#include "nvim/autocmd.h" +#include "nvim/api/private/helpers.h" +#include "nvim/charset.h" +#include "nvim/cursor_shape.h" +#include "nvim/fold.h" +#include "nvim/highlight.h" +#include "nvim/highlight_group.h" +#include "nvim/lua/executor.h" +#include "nvim/match.h" +#include "nvim/option.h" +#include "nvim/runtime.h" +#include "nvim/screen.h" + +/// \addtogroup SG_SET +/// @{ +#define SG_CTERM 2 // cterm has been set +#define SG_GUI 4 // gui has been set +#define SG_LINK 8 // link has been set +/// @} + +#define MAX_SYN_NAME 200 + +// builtin |highlight-groups| +static garray_T highlight_ga = GA_EMPTY_INIT_VALUE; + +Map(cstr_t, int) highlight_unames = MAP_INIT; + +/// The "term", "cterm" and "gui" arguments can be any combination of the +/// following names, separated by commas (but no spaces!). +static char *(hl_name_table[]) = + { "bold", "standout", "underline", "underlineline", "undercurl", "underdot", + "underdash", "italic", "reverse", "inverse", "strikethrough", "nocombine", "NONE" }; +static int hl_attr_table[] = + { HL_BOLD, HL_STANDOUT, HL_UNDERLINE, HL_UNDERLINELINE, HL_UNDERCURL, HL_UNDERDOT, HL_UNDERDASH, + HL_ITALIC, HL_INVERSE, HL_INVERSE, HL_STRIKETHROUGH, HL_NOCOMBINE, 0 }; + +/// Structure that stores information about a highlight group. +/// The ID of a highlight group is also called group ID. It is the index in +/// the highlight_ga array PLUS ONE. +typedef struct { + char_u *sg_name; ///< highlight group name + char *sg_name_u; ///< uppercase of sg_name + bool sg_cleared; ///< "hi clear" was used + int sg_attr; ///< Screen attr @see ATTR_ENTRY + int sg_link; ///< link to this highlight group ID + int sg_deflink; ///< default link; restored in highlight_clear() + int sg_set; ///< combination of flags in \ref SG_SET + sctx_T sg_deflink_sctx; ///< script where the default link was set + sctx_T sg_script_ctx; ///< script in which the group was last set + // for terminal UIs + int sg_cterm; ///< "cterm=" highlighting attr + ///< (combination of \ref HlAttrFlags) + int sg_cterm_fg; ///< terminal fg color number + 1 + int sg_cterm_bg; ///< terminal bg color number + 1 + bool sg_cterm_bold; ///< bold attr was set for light color + // for RGB UIs + int sg_gui; ///< "gui=" highlighting attributes + ///< (combination of \ref HlAttrFlags) + RgbValue sg_rgb_fg; ///< RGB foreground color + RgbValue sg_rgb_bg; ///< RGB background color + RgbValue sg_rgb_sp; ///< RGB special color + char *sg_rgb_fg_name; ///< RGB foreground color name + char *sg_rgb_bg_name; ///< RGB background color name + char *sg_rgb_sp_name; ///< RGB special color name + + int sg_blend; ///< blend level (0-100 inclusive), -1 if unset +} HlGroup; + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "highlight_group.c.generated.h" +#endif + +static inline HlGroup *HL_TABLE(void) +{ + return ((HlGroup *)((highlight_ga.ga_data))); +} + +// The default highlight groups. These are compiled-in for fast startup and +// they still work when the runtime files can't be found. +// +// When making changes here, also change runtime/colors/default.vim! + +static const char *highlight_init_both[] = { + "Conceal ctermbg=DarkGrey ctermfg=LightGrey guibg=DarkGrey guifg=LightGrey", + "Cursor guibg=fg guifg=bg", + "lCursor guibg=fg guifg=bg", + "DiffText cterm=bold ctermbg=Red gui=bold guibg=Red", + "ErrorMsg ctermbg=DarkRed ctermfg=White guibg=Red guifg=White", + "IncSearch cterm=reverse gui=reverse", + "ModeMsg cterm=bold gui=bold", + "NonText ctermfg=Blue gui=bold guifg=Blue", + "Normal cterm=NONE gui=NONE", + "PmenuSbar ctermbg=Grey guibg=Grey", + "StatusLine cterm=reverse,bold gui=reverse,bold", + "StatusLineNC cterm=reverse gui=reverse", + "TabLineFill cterm=reverse gui=reverse", + "TabLineSel cterm=bold gui=bold", + "TermCursor cterm=reverse gui=reverse", + "VertSplit cterm=reverse gui=reverse", + "WildMenu ctermbg=Yellow ctermfg=Black guibg=Yellow guifg=Black", + "default link WinSeparator VertSplit", + "default link EndOfBuffer NonText", + "default link LineNrAbove LineNr", + "default link LineNrBelow LineNr", + "default link QuickFixLine Search", + "default link CursorLineSign SignColumn", + "default link CursorLineFold FoldColumn", + "default link Substitute Search", + "default link Whitespace NonText", + "default link MsgSeparator StatusLine", + "default link NormalFloat Pmenu", + "default link FloatBorder WinSeparator", + "default FloatShadow blend=80 guibg=Black", + "default FloatShadowThrough blend=100 guibg=Black", + "RedrawDebugNormal cterm=reverse gui=reverse", + "RedrawDebugClear ctermbg=Yellow guibg=Yellow", + "RedrawDebugComposed ctermbg=Green guibg=Green", + "RedrawDebugRecompose ctermbg=Red guibg=Red", + "Error term=reverse cterm=NONE ctermfg=White ctermbg=Red gui=NONE guifg=White guibg=Red", + "Todo term=standout cterm=NONE ctermfg=Black ctermbg=Yellow gui=NONE guifg=Blue guibg=Yellow", + "default link String Constant", + "default link Character Constant", + "default link Number Constant", + "default link Boolean Constant", + "default link Float Number", + "default link Function Identifier", + "default link Conditional Statement", + "default link Repeat Statement", + "default link Label Statement", + "default link Operator Statement", + "default link Keyword Statement", + "default link Exception Statement", + "default link Include PreProc", + "default link Define PreProc", + "default link Macro PreProc", + "default link PreCondit PreProc", + "default link StorageClass Type", + "default link Structure Type", + "default link Typedef Type", + "default link Tag Special", + "default link SpecialChar Special", + "default link Delimiter Special", + "default link SpecialComment Special", + "default link Debug Special", + "default DiagnosticError ctermfg=1 guifg=Red", + "default DiagnosticWarn ctermfg=3 guifg=Orange", + "default DiagnosticInfo ctermfg=4 guifg=LightBlue", + "default DiagnosticHint ctermfg=7 guifg=LightGrey", + "default DiagnosticUnderlineError cterm=underline gui=underline guisp=Red", + "default DiagnosticUnderlineWarn cterm=underline gui=underline guisp=Orange", + "default DiagnosticUnderlineInfo cterm=underline gui=underline guisp=LightBlue", + "default DiagnosticUnderlineHint cterm=underline gui=underline guisp=LightGrey", + "default link DiagnosticVirtualTextError DiagnosticError", + "default link DiagnosticVirtualTextWarn DiagnosticWarn", + "default link DiagnosticVirtualTextInfo DiagnosticInfo", + "default link DiagnosticVirtualTextHint DiagnosticHint", + "default link DiagnosticFloatingError DiagnosticError", + "default link DiagnosticFloatingWarn DiagnosticWarn", + "default link DiagnosticFloatingInfo DiagnosticInfo", + "default link DiagnosticFloatingHint DiagnosticHint", + "default link DiagnosticSignError DiagnosticError", + "default link DiagnosticSignWarn DiagnosticWarn", + "default link DiagnosticSignInfo DiagnosticInfo", + "default link DiagnosticSignHint DiagnosticHint", + NULL +}; + +// Default colors only used with a light background. +static const char *highlight_init_light[] = { + "ColorColumn ctermbg=LightRed guibg=LightRed", + "CursorColumn ctermbg=LightGrey guibg=Grey90", + "CursorLine cterm=underline guibg=Grey90", + "CursorLineNr cterm=underline ctermfg=Brown gui=bold guifg=Brown", + "DiffAdd ctermbg=LightBlue guibg=LightBlue", + "DiffChange ctermbg=LightMagenta guibg=LightMagenta", + "DiffDelete ctermfg=Blue ctermbg=LightCyan gui=bold guifg=Blue guibg=LightCyan", + "Directory ctermfg=DarkBlue guifg=Blue", + "FoldColumn ctermbg=Grey ctermfg=DarkBlue guibg=Grey guifg=DarkBlue", + "Folded ctermbg=Grey ctermfg=DarkBlue guibg=LightGrey guifg=DarkBlue", + "LineNr ctermfg=Brown guifg=Brown", + "MatchParen ctermbg=Cyan guibg=Cyan", + "MoreMsg ctermfg=DarkGreen gui=bold guifg=SeaGreen", + "Pmenu ctermbg=LightMagenta ctermfg=Black guibg=LightMagenta", + "PmenuSel ctermbg=LightGrey ctermfg=Black guibg=Grey", + "PmenuThumb ctermbg=Black guibg=Black", + "Question ctermfg=DarkGreen gui=bold guifg=SeaGreen", + "Search ctermbg=Yellow ctermfg=NONE guibg=Yellow guifg=NONE", + "SignColumn ctermbg=Grey ctermfg=DarkBlue guibg=Grey guifg=DarkBlue", + "SpecialKey ctermfg=DarkBlue guifg=Blue", + "SpellBad ctermbg=LightRed guisp=Red gui=undercurl", + "SpellCap ctermbg=LightBlue guisp=Blue gui=undercurl", + "SpellLocal ctermbg=Cyan guisp=DarkCyan gui=undercurl", + "SpellRare ctermbg=LightMagenta guisp=Magenta gui=undercurl", + "TabLine cterm=underline ctermfg=black ctermbg=LightGrey gui=underline guibg=LightGrey", + "Title ctermfg=DarkMagenta gui=bold guifg=Magenta", + "Visual guibg=LightGrey", + "WarningMsg ctermfg=DarkRed guifg=Red", + "Comment term=bold cterm=NONE ctermfg=DarkBlue ctermbg=NONE gui=NONE guifg=Blue guibg=NONE", + "Constant term=underline cterm=NONE ctermfg=DarkRed ctermbg=NONE gui=NONE guifg=Magenta guibg=NONE", + "Special term=bold cterm=NONE ctermfg=DarkMagenta ctermbg=NONE gui=NONE guifg=#6a5acd guibg=NONE", + "Identifier term=underline cterm=NONE ctermfg=DarkCyan ctermbg=NONE gui=NONE guifg=DarkCyan guibg=NONE", + "Statement term=bold cterm=NONE ctermfg=Brown ctermbg=NONE gui=bold guifg=Brown guibg=NONE", + "PreProc term=underline cterm=NONE ctermfg=DarkMagenta ctermbg=NONE gui=NONE guifg=#6a0dad guibg=NONE", + "Type term=underline cterm=NONE ctermfg=DarkGreen ctermbg=NONE gui=bold guifg=SeaGreen guibg=NONE", + "Underlined term=underline cterm=underline ctermfg=DarkMagenta gui=underline guifg=SlateBlue", + "Ignore term=NONE cterm=NONE ctermfg=white ctermbg=NONE gui=NONE guifg=bg guibg=NONE", + NULL +}; + +// Default colors only used with a dark background. +static const char *highlight_init_dark[] = { + "ColorColumn ctermbg=DarkRed guibg=DarkRed", + "CursorColumn ctermbg=DarkGrey guibg=Grey40", + "CursorLine cterm=underline guibg=Grey40", + "CursorLineNr cterm=underline ctermfg=Yellow gui=bold guifg=Yellow", + "DiffAdd ctermbg=DarkBlue guibg=DarkBlue", + "DiffChange ctermbg=DarkMagenta guibg=DarkMagenta", + "DiffDelete ctermfg=Blue ctermbg=DarkCyan gui=bold guifg=Blue guibg=DarkCyan", + "Directory ctermfg=LightCyan guifg=Cyan", + "FoldColumn ctermbg=DarkGrey ctermfg=Cyan guibg=Grey guifg=Cyan", + "Folded ctermbg=DarkGrey ctermfg=Cyan guibg=DarkGrey guifg=Cyan", + "LineNr ctermfg=Yellow guifg=Yellow", + "MatchParen ctermbg=DarkCyan guibg=DarkCyan", + "MoreMsg ctermfg=LightGreen gui=bold guifg=SeaGreen", + "Pmenu ctermbg=Magenta ctermfg=Black guibg=Magenta", + "PmenuSel ctermbg=Black ctermfg=DarkGrey guibg=DarkGrey", + "PmenuThumb ctermbg=White guibg=White", + "Question ctermfg=LightGreen gui=bold guifg=Green", + "Search ctermbg=Yellow ctermfg=Black guibg=Yellow guifg=Black", + "SignColumn ctermbg=DarkGrey ctermfg=Cyan guibg=Grey guifg=Cyan", + "SpecialKey ctermfg=LightBlue guifg=Cyan", + "SpellBad ctermbg=Red guisp=Red gui=undercurl", + "SpellCap ctermbg=Blue guisp=Blue gui=undercurl", + "SpellLocal ctermbg=Cyan guisp=Cyan gui=undercurl", + "SpellRare ctermbg=Magenta guisp=Magenta gui=undercurl", + "TabLine cterm=underline ctermfg=white ctermbg=DarkGrey gui=underline guibg=DarkGrey", + "Title ctermfg=LightMagenta gui=bold guifg=Magenta", + "Visual guibg=DarkGrey", + "WarningMsg ctermfg=LightRed guifg=Red", + "Comment term=bold cterm=NONE ctermfg=Cyan ctermbg=NONE gui=NONE guifg=#80a0ff guibg=NONE", + "Constant term=underline cterm=NONE ctermfg=Magenta ctermbg=NONE gui=NONE guifg=#ffa0a0 guibg=NONE", + "Special term=bold cterm=NONE ctermfg=LightRed ctermbg=NONE gui=NONE guifg=Orange guibg=NONE", + "Identifier term=underline cterm=bold ctermfg=Cyan ctermbg=NONE gui=NONE guifg=#40ffff guibg=NONE", + "Statement term=bold cterm=NONE ctermfg=Yellow ctermbg=NONE gui=bold guifg=#ffff60 guibg=NONE", + "PreProc term=underline cterm=NONE ctermfg=LightBlue ctermbg=NONE gui=NONE guifg=#ff80ff guibg=NONE", + "Type term=underline cterm=NONE ctermfg=LightGreen ctermbg=NONE gui=bold guifg=#60ff60 guibg=NONE", + "Underlined term=underline cterm=underline ctermfg=LightBlue gui=underline guifg=#80a0ff", + "Ignore term=NONE cterm=NONE ctermfg=black ctermbg=NONE gui=NONE guifg=bg guibg=NONE", + NULL +}; + +const char *const highlight_init_cmdline[] = { + // XXX When modifying a list modify it in both valid and invalid halves. + // TODO(ZyX-I): merge valid and invalid groups via a macros. + + // NvimInternalError should appear only when highlighter has a bug. + "NvimInternalError ctermfg=Red ctermbg=Red guifg=Red guibg=Red", + + // Highlight groups (links) used by parser: + + "default link NvimAssignment Operator", + "default link NvimPlainAssignment NvimAssignment", + "default link NvimAugmentedAssignment NvimAssignment", + "default link NvimAssignmentWithAddition NvimAugmentedAssignment", + "default link NvimAssignmentWithSubtraction NvimAugmentedAssignment", + "default link NvimAssignmentWithConcatenation NvimAugmentedAssignment", + + "default link NvimOperator Operator", + + "default link NvimUnaryOperator NvimOperator", + "default link NvimUnaryPlus NvimUnaryOperator", + "default link NvimUnaryMinus NvimUnaryOperator", + "default link NvimNot NvimUnaryOperator", + + "default link NvimBinaryOperator NvimOperator", + "default link NvimComparison NvimBinaryOperator", + "default link NvimComparisonModifier NvimComparison", + "default link NvimBinaryPlus NvimBinaryOperator", + "default link NvimBinaryMinus NvimBinaryOperator", + "default link NvimConcat NvimBinaryOperator", + "default link NvimConcatOrSubscript NvimConcat", + "default link NvimOr NvimBinaryOperator", + "default link NvimAnd NvimBinaryOperator", + "default link NvimMultiplication NvimBinaryOperator", + "default link NvimDivision NvimBinaryOperator", + "default link NvimMod NvimBinaryOperator", + + "default link NvimTernary NvimOperator", + "default link NvimTernaryColon NvimTernary", + + "default link NvimParenthesis Delimiter", + "default link NvimLambda NvimParenthesis", + "default link NvimNestingParenthesis NvimParenthesis", + "default link NvimCallingParenthesis NvimParenthesis", + + "default link NvimSubscript NvimParenthesis", + "default link NvimSubscriptBracket NvimSubscript", + "default link NvimSubscriptColon NvimSubscript", + "default link NvimCurly NvimSubscript", + + "default link NvimContainer NvimParenthesis", + "default link NvimDict NvimContainer", + "default link NvimList NvimContainer", + + "default link NvimIdentifier Identifier", + "default link NvimIdentifierScope NvimIdentifier", + "default link NvimIdentifierScopeDelimiter NvimIdentifier", + "default link NvimIdentifierName NvimIdentifier", + "default link NvimIdentifierKey NvimIdentifier", + + "default link NvimColon Delimiter", + "default link NvimComma Delimiter", + "default link NvimArrow Delimiter", + + "default link NvimRegister SpecialChar", + "default link NvimNumber Number", + "default link NvimFloat NvimNumber", + "default link NvimNumberPrefix Type", + + "default link NvimOptionSigil Type", + "default link NvimOptionName NvimIdentifier", + "default link NvimOptionScope NvimIdentifierScope", + "default link NvimOptionScopeDelimiter NvimIdentifierScopeDelimiter", + + "default link NvimEnvironmentSigil NvimOptionSigil", + "default link NvimEnvironmentName NvimIdentifier", + + "default link NvimString String", + "default link NvimStringBody NvimString", + "default link NvimStringQuote NvimString", + "default link NvimStringSpecial SpecialChar", + + "default link NvimSingleQuote NvimStringQuote", + "default link NvimSingleQuotedBody NvimStringBody", + "default link NvimSingleQuotedQuote NvimStringSpecial", + + "default link NvimDoubleQuote NvimStringQuote", + "default link NvimDoubleQuotedBody NvimStringBody", + "default link NvimDoubleQuotedEscape NvimStringSpecial", + + "default link NvimFigureBrace NvimInternalError", + "default link NvimSingleQuotedUnknownEscape NvimInternalError", + + "default link NvimSpacing Normal", + + // NvimInvalid groups: + + "default link NvimInvalidSingleQuotedUnknownEscape NvimInternalError", + + "default link NvimInvalid Error", + + "default link NvimInvalidAssignment NvimInvalid", + "default link NvimInvalidPlainAssignment NvimInvalidAssignment", + "default link NvimInvalidAugmentedAssignment NvimInvalidAssignment", + "default link NvimInvalidAssignmentWithAddition NvimInvalidAugmentedAssignment", + "default link NvimInvalidAssignmentWithSubtraction NvimInvalidAugmentedAssignment", + "default link NvimInvalidAssignmentWithConcatenation NvimInvalidAugmentedAssignment", + + "default link NvimInvalidOperator NvimInvalid", + + "default link NvimInvalidUnaryOperator NvimInvalidOperator", + "default link NvimInvalidUnaryPlus NvimInvalidUnaryOperator", + "default link NvimInvalidUnaryMinus NvimInvalidUnaryOperator", + "default link NvimInvalidNot NvimInvalidUnaryOperator", + + "default link NvimInvalidBinaryOperator NvimInvalidOperator", + "default link NvimInvalidComparison NvimInvalidBinaryOperator", + "default link NvimInvalidComparisonModifier NvimInvalidComparison", + "default link NvimInvalidBinaryPlus NvimInvalidBinaryOperator", + "default link NvimInvalidBinaryMinus NvimInvalidBinaryOperator", + "default link NvimInvalidConcat NvimInvalidBinaryOperator", + "default link NvimInvalidConcatOrSubscript NvimInvalidConcat", + "default link NvimInvalidOr NvimInvalidBinaryOperator", + "default link NvimInvalidAnd NvimInvalidBinaryOperator", + "default link NvimInvalidMultiplication NvimInvalidBinaryOperator", + "default link NvimInvalidDivision NvimInvalidBinaryOperator", + "default link NvimInvalidMod NvimInvalidBinaryOperator", + + "default link NvimInvalidTernary NvimInvalidOperator", + "default link NvimInvalidTernaryColon NvimInvalidTernary", + + "default link NvimInvalidDelimiter NvimInvalid", + + "default link NvimInvalidParenthesis NvimInvalidDelimiter", + "default link NvimInvalidLambda NvimInvalidParenthesis", + "default link NvimInvalidNestingParenthesis NvimInvalidParenthesis", + "default link NvimInvalidCallingParenthesis NvimInvalidParenthesis", + + "default link NvimInvalidSubscript NvimInvalidParenthesis", + "default link NvimInvalidSubscriptBracket NvimInvalidSubscript", + "default link NvimInvalidSubscriptColon NvimInvalidSubscript", + "default link NvimInvalidCurly NvimInvalidSubscript", + + "default link NvimInvalidContainer NvimInvalidParenthesis", + "default link NvimInvalidDict NvimInvalidContainer", + "default link NvimInvalidList NvimInvalidContainer", + + "default link NvimInvalidValue NvimInvalid", + + "default link NvimInvalidIdentifier NvimInvalidValue", + "default link NvimInvalidIdentifierScope NvimInvalidIdentifier", + "default link NvimInvalidIdentifierScopeDelimiter NvimInvalidIdentifier", + "default link NvimInvalidIdentifierName NvimInvalidIdentifier", + "default link NvimInvalidIdentifierKey NvimInvalidIdentifier", + + "default link NvimInvalidColon NvimInvalidDelimiter", + "default link NvimInvalidComma NvimInvalidDelimiter", + "default link NvimInvalidArrow NvimInvalidDelimiter", + + "default link NvimInvalidRegister NvimInvalidValue", + "default link NvimInvalidNumber NvimInvalidValue", + "default link NvimInvalidFloat NvimInvalidNumber", + "default link NvimInvalidNumberPrefix NvimInvalidNumber", + + "default link NvimInvalidOptionSigil NvimInvalidIdentifier", + "default link NvimInvalidOptionName NvimInvalidIdentifier", + "default link NvimInvalidOptionScope NvimInvalidIdentifierScope", + "default link NvimInvalidOptionScopeDelimiter NvimInvalidIdentifierScopeDelimiter", + + "default link NvimInvalidEnvironmentSigil NvimInvalidOptionSigil", + "default link NvimInvalidEnvironmentName NvimInvalidIdentifier", + + // Invalid string bodies and specials are still highlighted as valid ones to + // minimize the red area. + "default link NvimInvalidString NvimInvalidValue", + "default link NvimInvalidStringBody NvimStringBody", + "default link NvimInvalidStringQuote NvimInvalidString", + "default link NvimInvalidStringSpecial NvimStringSpecial", + + "default link NvimInvalidSingleQuote NvimInvalidStringQuote", + "default link NvimInvalidSingleQuotedBody NvimInvalidStringBody", + "default link NvimInvalidSingleQuotedQuote NvimInvalidStringSpecial", + + "default link NvimInvalidDoubleQuote NvimInvalidStringQuote", + "default link NvimInvalidDoubleQuotedBody NvimInvalidStringBody", + "default link NvimInvalidDoubleQuotedEscape NvimInvalidStringSpecial", + "default link NvimInvalidDoubleQuotedUnknownEscape NvimInvalidValue", + + "default link NvimInvalidFigureBrace NvimInvalidDelimiter", + + "default link NvimInvalidSpacing ErrorMsg", + + // Not actually invalid, but we highlight user that he is doing something + // wrong. + "default link NvimDoubleQuotedUnknownEscape NvimInvalidValue", + NULL, +}; + +/// Returns the number of highlight groups. +int highlight_num_groups(void) +{ + return highlight_ga.ga_len; +} + +/// Returns the name of a highlight group. +char_u *highlight_group_name(int id) +{ + return HL_TABLE()[id].sg_name; +} + +/// Returns the ID of the link to a highlight group. +int highlight_link_id(int id) +{ + return HL_TABLE()[id].sg_link; +} + +/// Create default links for Nvim* highlight groups used for cmdline coloring +void syn_init_cmdline_highlight(bool reset, bool init) +{ + for (size_t i = 0; highlight_init_cmdline[i] != NULL; i++) { + do_highlight(highlight_init_cmdline[i], reset, init); + } +} + +/// Load colors from a file if "g:colors_name" is set, otherwise load builtin +/// colors +/// +/// @param both include groups where 'bg' doesn't matter +/// @param reset clear groups first +void init_highlight(bool both, bool reset) +{ + static int had_both = false; + + // Try finding the color scheme file. Used when a color file was loaded + // and 'background' or 't_Co' is changed. + char_u *p = get_var_value("g:colors_name"); + if (p != NULL) { + // Value of g:colors_name could be freed in load_colors() and make + // p invalid, so copy it. + char_u *copy_p = vim_strsave(p); + bool okay = load_colors(copy_p); + xfree(copy_p); + if (okay) { + return; + } + } + + // Didn't use a color file, use the compiled-in colors. + if (both) { + had_both = true; + const char *const *const pp = highlight_init_both; + for (size_t i = 0; pp[i] != NULL; i++) { + do_highlight(pp[i], reset, true); + } + } else if (!had_both) { + // Don't do anything before the call with both == true from main(). + // Not everything has been setup then, and that call will overrule + // everything anyway. + return; + } + + const char *const *const pp = ((*p_bg == 'l') + ? highlight_init_light + : highlight_init_dark); + for (size_t i = 0; pp[i] != NULL; i++) { + do_highlight(pp[i], reset, true); + } + + // Reverse looks ugly, but grey may not work for 8 colors. Thus let it + // depend on the number of colors available. + // With 8 colors brown is equal to yellow, need to use black for Search fg + // to avoid Statement highlighted text disappears. + // Clear the attributes, needed when changing the t_Co value. + if (t_colors > 8) { + do_highlight((*p_bg == 'l' + ? "Visual cterm=NONE ctermbg=LightGrey" + : "Visual cterm=NONE ctermbg=DarkGrey"), false, true); + } else { + do_highlight("Visual cterm=reverse ctermbg=NONE", false, true); + if (*p_bg == 'l') { + do_highlight("Search ctermfg=black", false, true); + } + } + + syn_init_cmdline_highlight(false, false); +} + +/// Load color file "name". +/// Return OK for success, FAIL for failure. +int load_colors(char_u *name) +{ + char_u *buf; + int retval = FAIL; + static bool recursive = false; + + // When being called recursively, this is probably because setting + // 'background' caused the highlighting to be reloaded. This means it is + // working, thus we should return OK. + if (recursive) { + return OK; + } + + recursive = true; + size_t buflen = STRLEN(name) + 12; + buf = xmalloc(buflen); + apply_autocmds(EVENT_COLORSCHEMEPRE, name, curbuf->b_fname, false, curbuf); + snprintf((char *)buf, buflen, "colors/%s.vim", name); + retval = source_runtime((char *)buf, DIP_START + DIP_OPT); + if (retval == FAIL) { + snprintf((char *)buf, buflen, "colors/%s.lua", name); + retval = source_runtime((char *)buf, DIP_START + DIP_OPT); + } + xfree(buf); + apply_autocmds(EVENT_COLORSCHEME, name, curbuf->b_fname, false, curbuf); + + recursive = false; + + return retval; +} + +static char *(color_names[28]) = { + "Black", "DarkBlue", "DarkGreen", "DarkCyan", + "DarkRed", "DarkMagenta", "Brown", "DarkYellow", + "Gray", "Grey", "LightGray", "LightGrey", + "DarkGray", "DarkGrey", + "Blue", "LightBlue", "Green", "LightGreen", + "Cyan", "LightCyan", "Red", "LightRed", "Magenta", + "LightMagenta", "Yellow", "LightYellow", "White", "NONE" +}; +// indices: +// 0, 1, 2, 3, +// 4, 5, 6, 7, +// 8, 9, 10, 11, +// 12, 13, +// 14, 15, 16, 17, +// 18, 19, 20, 21, 22, +// 23, 24, 25, 26, 27 +static int color_numbers_16[28] = { 0, 1, 2, 3, + 4, 5, 6, 6, + 7, 7, 7, 7, + 8, 8, + 9, 9, 10, 10, + 11, 11, 12, 12, 13, + 13, 14, 14, 15, -1 }; +// for xterm with 88 colors... +static int color_numbers_88[28] = { 0, 4, 2, 6, + 1, 5, 32, 72, + 84, 84, 7, 7, + 82, 82, + 12, 43, 10, 61, + 14, 63, 9, 74, 13, + 75, 11, 78, 15, -1 }; +// for xterm with 256 colors... +static int color_numbers_256[28] = { 0, 4, 2, 6, + 1, 5, 130, 3, + 248, 248, 7, 7, + 242, 242, + 12, 81, 10, 121, + 14, 159, 9, 224, 13, + 225, 11, 229, 15, -1 }; +// for terminals with less than 16 colors... +static int color_numbers_8[28] = { 0, 4, 2, 6, + 1, 5, 3, 3, + 7, 7, 7, 7, + 0+8, 0+8, + 4+8, 4+8, 2+8, 2+8, + 6+8, 6+8, 1+8, 1+8, 5+8, + 5+8, 3+8, 3+8, 7+8, -1 }; + +// Lookup the "cterm" value to be used for color with index "idx" in +// color_names[]. +// "boldp" will be set to TRUE or FALSE for a foreground color when using 8 +// colors, otherwise it will be unchanged. +int lookup_color(const int idx, const bool foreground, TriState *const boldp) +{ + int color = color_numbers_16[idx]; + + // Use the _16 table to check if it's a valid color name. + if (color < 0) { + return -1; + } + + if (t_colors == 8) { + // t_Co is 8: use the 8 colors table + color = color_numbers_8[idx]; + if (foreground) { + // set/reset bold attribute to get light foreground + // colors (on some terminals, e.g. "linux") + if (color & 8) { + *boldp = kTrue; + } else { + *boldp = kFalse; + } + } + color &= 7; // truncate to 8 colors + } else if (t_colors == 16) { + color = color_numbers_8[idx]; + } else if (t_colors == 88) { + color = color_numbers_88[idx]; + } else if (t_colors >= 256) { + color = color_numbers_256[idx]; + } + 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 +/// +/// When using ":highlight clear" this is called recursively for each group with +/// forceit and init being both true. +/// +/// @param[in] line Command arguments. +/// @param[in] forceit True when bang is given, allows to link group even if +/// it has its own settings. +/// @param[in] init True when initializing. +void do_highlight(const char *line, const bool forceit, const bool init) + FUNC_ATTR_NONNULL_ALL +{ + const char *name_end; + const char *linep; + const char *key_start; + const char *arg_start; + int off; + int len; + int attr; + int id; + int idx; + HlGroup item_before; + bool did_change = false; + bool dodefault = false; + bool doclear = false; + bool dolink = false; + bool error = false; + int color; + bool is_normal_group = false; // "Normal" group + bool did_highlight_changed = false; + + // If no argument, list current highlighting. + if (ends_excmd((uint8_t)(*line))) { + for (int i = 1; i <= highlight_ga.ga_len && !got_int; i++) { + // TODO(brammool): only call when the group has attributes set + highlight_list_one(i); + } + return; + } + + // Isolate the name. + name_end = (const char *)skiptowhite((const char_u *)line); + linep = (const char *)skipwhite((const char_u *)name_end); + + // Check for "default" argument. + if (strncmp(line, "default", (size_t)(name_end - line)) == 0) { + dodefault = true; + line = linep; + name_end = (const char *)skiptowhite((const char_u *)line); + linep = (const char *)skipwhite((const char_u *)name_end); + } + + // Check for "clear" or "link" argument. + if (strncmp(line, "clear", (size_t)(name_end - line)) == 0) { + doclear = true; + } else if (strncmp(line, "link", (size_t)(name_end - line)) == 0) { + dolink = true; + } + + // ":highlight {group-name}": list highlighting for one group. + if (!doclear && !dolink && ends_excmd((uint8_t)(*linep))) { + id = syn_name2id_len((const char_u *)line, (size_t)(name_end - line)); + if (id == 0) { + semsg(_("E411: highlight group not found: %s"), line); + } else { + highlight_list_one(id); + } + return; + } + + // Handle ":highlight link {from} {to}" command. + if (dolink) { + const char *from_start = linep; + const char *from_end; + const char *to_start; + const char *to_end; + int from_id; + int to_id; + HlGroup *hlgroup = NULL; + + from_end = (const char *)skiptowhite((const char_u *)from_start); + to_start = (const char *)skipwhite((const char_u *)from_end); + to_end = (const char *)skiptowhite((const char_u *)to_start); + + if (ends_excmd((uint8_t)(*from_start)) + || ends_excmd((uint8_t)(*to_start))) { + semsg(_("E412: Not enough arguments: \":highlight link %s\""), + from_start); + return; + } + + if (!ends_excmd(*skipwhite((const char_u *)to_end))) { + semsg(_("E413: Too many arguments: \":highlight link %s\""), from_start); + return; + } + + from_id = syn_check_group(from_start, (size_t)(from_end - from_start)); + if (strncmp(to_start, "NONE", 4) == 0) { + to_id = 0; + } else { + to_id = syn_check_group(to_start, (size_t)(to_end - to_start)); + } + + if (from_id > 0) { + hlgroup = &HL_TABLE()[from_id - 1]; + if (dodefault && (forceit || hlgroup->sg_deflink == 0)) { + hlgroup->sg_deflink = to_id; + hlgroup->sg_deflink_sctx = current_sctx; + hlgroup->sg_deflink_sctx.sc_lnum += sourcing_lnum; + nlua_set_sctx(&hlgroup->sg_deflink_sctx); + } + } + + if (from_id > 0 && (!init || hlgroup->sg_set == 0)) { + // Don't allow a link when there already is some highlighting + // for the group, unless '!' is used + if (to_id > 0 && !forceit && !init + && hl_has_settings(from_id - 1, dodefault)) { + if (sourcing_name == NULL && !dodefault) { + emsg(_("E414: group has settings, highlight link ignored")); + } + } else if (hlgroup->sg_link != to_id + || hlgroup->sg_script_ctx.sc_sid != current_sctx.sc_sid + || hlgroup->sg_cleared) { + if (!init) { + hlgroup->sg_set |= SG_LINK; + } + 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); + + // Only call highlight changed() once after multiple changes + need_highlight_changed = true; + } + } + + return; + } + + if (doclear) { + // ":highlight clear [group]" command. + line = linep; + if (ends_excmd((uint8_t)(*line))) { + do_unlet(S_LEN("colors_name"), true); + restore_cterm_colors(); + + // Clear all default highlight groups and load the defaults. + for (int j = 0; j < highlight_ga.ga_len; j++) { + highlight_clear(j); + } + init_highlight(true, true); + highlight_changed(); + redraw_all_later(NOT_VALID); + return; + } + name_end = (const char *)skiptowhite((const char_u *)line); + linep = (const char *)skipwhite((const char_u *)name_end); + } + + // Find the group name in the table. If it does not exist yet, add it. + id = syn_check_group(line, (size_t)(name_end - line)); + if (id == 0) { // Failed (out of memory). + return; + } + idx = id - 1; // Index is ID minus one. + + // Return if "default" was used and the group already has settings + if (dodefault && hl_has_settings(idx, true)) { + return; + } + + // Make a copy so we can check if any attribute actually changed + item_before = HL_TABLE()[idx]; + is_normal_group = (STRCMP(HL_TABLE()[idx].sg_name_u, "NORMAL") == 0); + + // Clear the highlighting for ":hi clear {group}" and ":hi clear". + if (doclear || (forceit && init)) { + highlight_clear(idx); + if (!doclear) { + HL_TABLE()[idx].sg_set = 0; + } + } + + char *key = NULL; + char *arg = NULL; + if (!doclear) { + while (!ends_excmd((uint8_t)(*linep))) { + key_start = linep; + if (*linep == '=') { + semsg(_("E415: unexpected equal sign: %s"), key_start); + error = true; + break; + } + + // Isolate the key ("term", "ctermfg", "ctermbg", "font", "guifg", + // "guibg" or "guisp"). + while (*linep && !ascii_iswhite(*linep) && *linep != '=') { + linep++; + } + xfree(key); + key = (char *)vim_strnsave_up((const char_u *)key_start, + (size_t)(linep - key_start)); + linep = (const char *)skipwhite((const char_u *)linep); + + if (strcmp(key, "NONE") == 0) { + if (!init || HL_TABLE()[idx].sg_set == 0) { + if (!init) { + HL_TABLE()[idx].sg_set |= SG_CTERM+SG_GUI; + } + highlight_clear(idx); + } + continue; + } + + // Check for the equal sign. + if (*linep != '=') { + semsg(_("E416: missing equal sign: %s"), key_start); + error = true; + break; + } + linep++; + + // Isolate the argument. + linep = (const char *)skipwhite((const char_u *)linep); + if (*linep == '\'') { // guifg='color name' + arg_start = ++linep; + linep = strchr(linep, '\''); + if (linep == NULL) { + semsg(_(e_invarg2), key_start); + error = true; + break; + } + } else { + arg_start = linep; + linep = (const char *)skiptowhite((const char_u *)linep); + } + if (linep == arg_start) { + semsg(_("E417: missing argument: %s"), key_start); + error = true; + break; + } + xfree(arg); + arg = xstrndup(arg_start, (size_t)(linep - arg_start)); + + if (*linep == '\'') { + linep++; + } + + // Store the argument. + if (strcmp(key, "TERM") == 0 + || strcmp(key, "CTERM") == 0 + || strcmp(key, "GUI") == 0) { + attr = 0; + off = 0; + int i; + while (arg[off] != NUL) { + for (i = ARRAY_SIZE(hl_attr_table); --i >= 0;) { + len = (int)STRLEN(hl_name_table[i]); + if (STRNICMP(arg + off, hl_name_table[i], len) == 0) { + attr |= hl_attr_table[i]; + off += len; + break; + } + } + if (i < 0) { + semsg(_("E418: Illegal value: %s"), arg); + error = true; + break; + } + if (arg[off] == ',') { // Another one follows. + off++; + } + } + if (error) { + break; + } + if (*key == 'C') { + if (!init || !(HL_TABLE()[idx].sg_set & SG_CTERM)) { + if (!init) { + HL_TABLE()[idx].sg_set |= SG_CTERM; + } + HL_TABLE()[idx].sg_cterm = attr; + HL_TABLE()[idx].sg_cterm_bold = false; + } + } else if (*key == 'G') { + if (!init || !(HL_TABLE()[idx].sg_set & SG_GUI)) { + if (!init) { + HL_TABLE()[idx].sg_set |= SG_GUI; + } + HL_TABLE()[idx].sg_gui = attr; + } + } + } else if (STRCMP(key, "FONT") == 0) { + // in non-GUI fonts are simply ignored + } else if (STRCMP(key, "CTERMFG") == 0 || STRCMP(key, "CTERMBG") == 0) { + if (!init || !(HL_TABLE()[idx].sg_set & SG_CTERM)) { + if (!init) { + HL_TABLE()[idx].sg_set |= SG_CTERM; + } + + // When setting the foreground color, and previously the "bold" + // flag was set for a light color, reset it now + if (key[5] == 'F' && HL_TABLE()[idx].sg_cterm_bold) { + HL_TABLE()[idx].sg_cterm &= ~HL_BOLD; + HL_TABLE()[idx].sg_cterm_bold = false; + } + + if (ascii_isdigit(*arg)) { + color = atoi(arg); + } else if (STRICMP(arg, "fg") == 0) { + if (cterm_normal_fg_color) { + color = cterm_normal_fg_color - 1; + } else { + emsg(_("E419: FG color unknown")); + error = true; + break; + } + } else if (STRICMP(arg, "bg") == 0) { + if (cterm_normal_bg_color > 0) { + color = cterm_normal_bg_color - 1; + } else { + emsg(_("E420: BG color unknown")); + error = true; + break; + } + } else { + // Reduce calls to STRICMP a bit, it can be slow. + off = TOUPPER_ASC(*arg); + int i; + for (i = ARRAY_SIZE(color_names); --i >= 0;) { + if (off == color_names[i][0] + && STRICMP(arg + 1, color_names[i] + 1) == 0) { + break; + } + } + if (i < 0) { + semsg(_("E421: Color name or number not recognized: %s"), + key_start); + error = true; + break; + } + + TriState bold = kNone; + color = lookup_color(i, key[5] == 'F', &bold); + + // set/reset bold attribute to get light foreground + // colors (on some terminals, e.g. "linux") + if (bold == kTrue) { + HL_TABLE()[idx].sg_cterm |= HL_BOLD; + HL_TABLE()[idx].sg_cterm_bold = true; + } else if (bold == kFalse) { + HL_TABLE()[idx].sg_cterm &= ~HL_BOLD; + } + } + // Add one to the argument, to avoid zero. Zero is used for + // "NONE", then "color" is -1. + if (key[5] == 'F') { + HL_TABLE()[idx].sg_cterm_fg = color + 1; + if (is_normal_group) { + cterm_normal_fg_color = color + 1; + } + } else { + HL_TABLE()[idx].sg_cterm_bg = color + 1; + if (is_normal_group) { + cterm_normal_bg_color = color + 1; + if (!ui_rgb_attached()) { + if (color >= 0) { + int dark = -1; + + if (t_colors < 16) { + dark = (color == 0 || color == 4); + } else if (color < 16) { + // Limit the heuristic to the standard 16 colors + dark = (color < 7 || color == 8); + } + // Set the 'background' option if the value is + // wrong. + if (dark != -1 + && dark != (*p_bg == 'd') + && !option_was_set("bg")) { + set_option_value("bg", 0L, (dark ? "dark" : "light"), 0); + reset_option_was_set("bg"); + } + } + } + } + } + } + } else if (strcmp(key, "GUIFG") == 0) { + char **namep = &HL_TABLE()[idx].sg_rgb_fg_name; + + if (!init || !(HL_TABLE()[idx].sg_set & SG_GUI)) { + if (!init) { + HL_TABLE()[idx].sg_set |= SG_GUI; + } + + if (*namep == NULL || STRCMP(*namep, arg) != 0) { + xfree(*namep); + if (strcmp(arg, "NONE") != 0) { + *namep = xstrdup(arg); + HL_TABLE()[idx].sg_rgb_fg = name_to_color(arg); + } else { + *namep = NULL; + HL_TABLE()[idx].sg_rgb_fg = -1; + } + did_change = true; + } + } + + if (is_normal_group) { + normal_fg = HL_TABLE()[idx].sg_rgb_fg; + } + } else if (STRCMP(key, "GUIBG") == 0) { + char **const namep = &HL_TABLE()[idx].sg_rgb_bg_name; + + if (!init || !(HL_TABLE()[idx].sg_set & SG_GUI)) { + if (!init) { + HL_TABLE()[idx].sg_set |= SG_GUI; + } + + if (*namep == NULL || STRCMP(*namep, arg) != 0) { + xfree(*namep); + if (STRCMP(arg, "NONE") != 0) { + *namep = xstrdup(arg); + HL_TABLE()[idx].sg_rgb_bg = name_to_color(arg); + } else { + *namep = NULL; + HL_TABLE()[idx].sg_rgb_bg = -1; + } + did_change = true; + } + } + + if (is_normal_group) { + normal_bg = HL_TABLE()[idx].sg_rgb_bg; + } + } else if (strcmp(key, "GUISP") == 0) { + char **const namep = &HL_TABLE()[idx].sg_rgb_sp_name; + + if (!init || !(HL_TABLE()[idx].sg_set & SG_GUI)) { + if (!init) { + HL_TABLE()[idx].sg_set |= SG_GUI; + } + + if (*namep == NULL || STRCMP(*namep, arg) != 0) { + xfree(*namep); + if (strcmp(arg, "NONE") != 0) { + *namep = xstrdup(arg); + HL_TABLE()[idx].sg_rgb_sp = name_to_color(arg); + } else { + *namep = NULL; + HL_TABLE()[idx].sg_rgb_sp = -1; + } + did_change = true; + } + } + + if (is_normal_group) { + normal_sp = HL_TABLE()[idx].sg_rgb_sp; + } + } else if (strcmp(key, "START") == 0 || strcmp(key, "STOP") == 0) { + // Ignored for now + } else if (strcmp(key, "BLEND") == 0) { + if (strcmp(arg, "NONE") != 0) { + HL_TABLE()[idx].sg_blend = (int)strtol(arg, NULL, 10); + } else { + HL_TABLE()[idx].sg_blend = -1; + } + } else { + semsg(_("E423: Illegal argument: %s"), key_start); + error = true; + break; + } + HL_TABLE()[idx].sg_cleared = false; + + // When highlighting has been given for a group, don't link it. + if (!init || !(HL_TABLE()[idx].sg_set & SG_LINK)) { + HL_TABLE()[idx].sg_link = 0; + } + + // Continue with next argument. + linep = (const char *)skipwhite((const char_u *)linep); + } + } + + // If there is an error, and it's a new entry, remove it from the table. + if (error && idx == highlight_ga.ga_len) { + syn_unadd_group(); + } else { + if (!error && is_normal_group) { + // Need to update all groups, because they might be using "bg" and/or + // "fg", which have been changed now. + highlight_attr_set_all(); + + if (!ui_has(kUILinegrid) && starting == 0) { + // Older UIs assume that we clear the screen after normal group is + // changed + ui_refresh(); + } else { + // TUI and newer UIs will repaint the screen themselves. NOT_VALID + // redraw below will still handle usages of guibg=fg etc. + ui_default_colors_set(); + } + did_highlight_changed = true; + redraw_all_later(NOT_VALID); + } else { + set_hl_attr(idx); + } + 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); + + // Only call highlight_changed() once, after a sequence of highlight + // commands, and only if an attribute actually changed + if ((did_change + || memcmp(&HL_TABLE()[idx], &item_before, sizeof(item_before)) != 0) + && !did_highlight_changed) { + // Do not trigger a redraw when highlighting is changed while + // redrawing. This may happen when evaluating 'statusline' changes the + // StatusLine group. + if (!updating_screen) { + redraw_all_later(NOT_VALID); + } + need_highlight_changed = true; + } +} + +#if defined(EXITFREE) +void free_highlight(void) +{ + for (int i = 0; i < highlight_ga.ga_len; i++) { + highlight_clear(i); + xfree(HL_TABLE()[i].sg_name); + xfree(HL_TABLE()[i].sg_name_u); + } + ga_clear(&highlight_ga); + map_destroy(cstr_t, int)(&highlight_unames); +} + +#endif + +/// Reset the cterm colors to what they were before Vim was started, if +/// possible. Otherwise reset them to zero. +void restore_cterm_colors(void) +{ + normal_fg = -1; + normal_bg = -1; + normal_sp = -1; + cterm_normal_fg_color = 0; + cterm_normal_bg_color = 0; +} + +/// @param check_link if true also check for an existing link. +/// +/// @return TRUE if highlight group "idx" has any settings. +static int hl_has_settings(int idx, bool check_link) +{ + return HL_TABLE()[idx].sg_cleared == 0 + && (HL_TABLE()[idx].sg_attr != 0 + || HL_TABLE()[idx].sg_cterm_fg != 0 + || HL_TABLE()[idx].sg_cterm_bg != 0 + || HL_TABLE()[idx].sg_rgb_fg_name != NULL + || HL_TABLE()[idx].sg_rgb_bg_name != NULL + || HL_TABLE()[idx].sg_rgb_sp_name != NULL + || (check_link && (HL_TABLE()[idx].sg_set & SG_LINK))); +} + +/// Clear highlighting for one group. +static void highlight_clear(int idx) +{ + HL_TABLE()[idx].sg_cleared = true; + + HL_TABLE()[idx].sg_attr = 0; + HL_TABLE()[idx].sg_cterm = 0; + HL_TABLE()[idx].sg_cterm_bold = false; + HL_TABLE()[idx].sg_cterm_fg = 0; + HL_TABLE()[idx].sg_cterm_bg = 0; + HL_TABLE()[idx].sg_gui = 0; + HL_TABLE()[idx].sg_rgb_fg = -1; + HL_TABLE()[idx].sg_rgb_bg = -1; + HL_TABLE()[idx].sg_rgb_sp = -1; + XFREE_CLEAR(HL_TABLE()[idx].sg_rgb_fg_name); + XFREE_CLEAR(HL_TABLE()[idx].sg_rgb_bg_name); + XFREE_CLEAR(HL_TABLE()[idx].sg_rgb_sp_name); + HL_TABLE()[idx].sg_blend = -1; + // Restore default link and context if they exist. Otherwise clears. + HL_TABLE()[idx].sg_link = HL_TABLE()[idx].sg_deflink; + // Since we set the default link, set the location to where the default + // link was set. + HL_TABLE()[idx].sg_script_ctx = HL_TABLE()[idx].sg_deflink_sctx; +} + +/// \addtogroup LIST_XXX +/// @{ +#define LIST_ATTR 1 +#define LIST_STRING 2 +#define LIST_INT 3 +/// @} + +static void highlight_list_one(const int id) +{ + const HlGroup *sgp = &HL_TABLE()[id - 1]; // index is ID minus one + bool didh = false; + + if (message_filtered(sgp->sg_name)) { + return; + } + + didh = highlight_list_arg(id, didh, LIST_ATTR, + sgp->sg_cterm, NULL, "cterm"); + didh = highlight_list_arg(id, didh, LIST_INT, + sgp->sg_cterm_fg, NULL, "ctermfg"); + didh = highlight_list_arg(id, didh, LIST_INT, + sgp->sg_cterm_bg, NULL, "ctermbg"); + + didh = highlight_list_arg(id, didh, LIST_ATTR, + sgp->sg_gui, NULL, "gui"); + didh = highlight_list_arg(id, didh, LIST_STRING, + 0, sgp->sg_rgb_fg_name, "guifg"); + didh = highlight_list_arg(id, didh, LIST_STRING, + 0, sgp->sg_rgb_bg_name, "guibg"); + didh = highlight_list_arg(id, didh, LIST_STRING, + 0, sgp->sg_rgb_sp_name, "guisp"); + + didh = highlight_list_arg(id, didh, LIST_INT, + sgp->sg_blend+1, NULL, "blend"); + + if (sgp->sg_link && !got_int) { + (void)syn_list_header(didh, 0, id, true); + didh = true; + msg_puts_attr("links to", HL_ATTR(HLF_D)); + msg_putchar(' '); + msg_outtrans(HL_TABLE()[HL_TABLE()[id - 1].sg_link - 1].sg_name); + } + + if (!didh) { + highlight_list_arg(id, didh, LIST_STRING, 0, "cleared", ""); + } + if (p_verbose > 0) { + last_set_msg(sgp->sg_script_ctx); + } +} + +Dictionary get_global_hl_defs(void) +{ + Dictionary rv = ARRAY_DICT_INIT; + for (int i = 1; i <= highlight_ga.ga_len && !got_int; i++) { + Dictionary attrs = ARRAY_DICT_INIT; + HlGroup *h = &HL_TABLE()[i - 1]; + if (h->sg_attr > 0) { + attrs = hlattrs2dict(syn_attr2entry(h->sg_attr), true); + } else if (h->sg_link > 0) { + const char *link = (const char *)HL_TABLE()[h->sg_link - 1].sg_name; + PUT(attrs, "link", STRING_OBJ(cstr_to_string(link))); + } + PUT(rv, (const char *)h->sg_name, DICTIONARY_OBJ(attrs)); + } + + return rv; +} + +/// Outputs a highlight when doing ":hi MyHighlight" +/// +/// @param type one of \ref LIST_XXX +/// @param iarg integer argument used if \p type == LIST_INT +/// @param sarg string used if \p type == LIST_STRING +static bool highlight_list_arg(const int id, bool didh, const int type, int iarg, char *const sarg, + const char *const name) +{ + char buf[100]; + + if (got_int) { + return false; + } + if (type == LIST_STRING ? (sarg != NULL) : (iarg != 0)) { + char *ts = buf; + if (type == LIST_INT) { + snprintf((char *)buf, sizeof(buf), "%d", iarg - 1); + } else if (type == LIST_STRING) { + ts = sarg; + } else { // type == LIST_ATTR + buf[0] = NUL; + for (int i = 0; hl_attr_table[i] != 0; i++) { + if (iarg & hl_attr_table[i]) { + if (buf[0] != NUL) { + xstrlcat(buf, ",", 100); + } + xstrlcat(buf, hl_name_table[i], 100); + iarg &= ~hl_attr_table[i]; // don't want "inverse" + } + } + } + + (void)syn_list_header(didh, (int)(vim_strsize((char_u *)ts) + (int)STRLEN(name) + + 1), id, false); + didh = true; + if (!got_int) { + if (*name != NUL) { + msg_puts_attr(name, HL_ATTR(HLF_D)); + msg_puts_attr("=", HL_ATTR(HLF_D)); + } + msg_outtrans((char_u *)ts); + } + } + return didh; +} + +/// Check whether highlight group has attribute +/// +/// @param[in] id Highlight group to check. +/// @param[in] flag Attribute to check. +/// @param[in] modec 'g' for GUI, 'c' for term. +/// +/// @return "1" if highlight group has attribute, NULL otherwise. +const char *highlight_has_attr(const int id, const int flag, const int modec) + FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE +{ + int attr; + + if (id <= 0 || id > highlight_ga.ga_len) { + return NULL; + } + + if (modec == 'g') { + attr = HL_TABLE()[id - 1].sg_gui; + } else { + attr = HL_TABLE()[id - 1].sg_cterm; + } + + return (attr & flag) ? "1" : NULL; +} + +/// Return color name of the given highlight group +/// +/// @param[in] id Highlight group to work with. +/// @param[in] what What to return: one of "font", "fg", "bg", "sp", "fg#", +/// "bg#" or "sp#". +/// @param[in] modec 'g' for GUI, 'c' for cterm and 't' for term. +/// +/// @return color name, possibly in a static buffer. Buffer will be overwritten +/// on next highlight_color() call. May return NULL. +const char *highlight_color(const int id, const char *const what, const int modec) + FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL +{ + static char name[20]; + int n; + bool fg = false; + bool sp = false; + bool font = false; + + if (id <= 0 || id > highlight_ga.ga_len) { + return NULL; + } + + if (TOLOWER_ASC(what[0]) == 'f' && TOLOWER_ASC(what[1]) == 'g') { + fg = true; + } else if (TOLOWER_ASC(what[0]) == 'f' && TOLOWER_ASC(what[1]) == 'o' + && TOLOWER_ASC(what[2]) == 'n' && TOLOWER_ASC(what[3]) == 't') { + font = true; + } else if (TOLOWER_ASC(what[0]) == 's' && TOLOWER_ASC(what[1]) == 'p') { + sp = true; + } else if (!(TOLOWER_ASC(what[0]) == 'b' && TOLOWER_ASC(what[1]) == 'g')) { + return NULL; + } + if (modec == 'g') { + if (what[2] == '#' && ui_rgb_attached()) { + if (fg) { + n = HL_TABLE()[id - 1].sg_rgb_fg; + } else if (sp) { + n = HL_TABLE()[id - 1].sg_rgb_sp; + } else { + n = HL_TABLE()[id - 1].sg_rgb_bg; + } + if (n < 0 || n > 0xffffff) { + return NULL; + } + snprintf(name, sizeof(name), "#%06x", n); + return name; + } + if (fg) { + return (const char *)HL_TABLE()[id - 1].sg_rgb_fg_name; + } + if (sp) { + return (const char *)HL_TABLE()[id - 1].sg_rgb_sp_name; + } + return (const char *)HL_TABLE()[id - 1].sg_rgb_bg_name; + } + if (font || sp) { + return NULL; + } + if (modec == 'c') { + if (fg) { + n = HL_TABLE()[id - 1].sg_cterm_fg - 1; + } else { + n = HL_TABLE()[id - 1].sg_cterm_bg - 1; + } + if (n < 0) { + return NULL; + } + snprintf(name, sizeof(name), "%d", n); + return name; + } + // term doesn't have color. + return NULL; +} + +/// Output the syntax list header. +/// +/// @param did_header did header already +/// @param outlen length of string that comes +/// @param id highlight group id +/// @param force_newline always start a new line +/// @return true when started a new line. +bool syn_list_header(const bool did_header, const int outlen, const int id, + bool force_newline) +{ + int endcol = 19; + bool newline = true; + int name_col = 0; + bool adjust = true; + + if (!did_header) { + msg_putchar('\n'); + if (got_int) { + return true; + } + msg_outtrans(HL_TABLE()[id - 1].sg_name); + name_col = msg_col; + endcol = 15; + } else if ((ui_has(kUIMessages) || msg_silent) && !force_newline) { + msg_putchar(' '); + adjust = false; + } else if (msg_col + outlen + 1 >= Columns || force_newline) { + msg_putchar('\n'); + if (got_int) { + return true; + } + } else { + if (msg_col >= endcol) { // wrap around is like starting a new line + newline = false; + } + } + + if (adjust) { + if (msg_col >= endcol) { + // output at least one space + endcol = msg_col + 1; + } + + msg_advance(endcol); + } + + // Show "xxx" with the attributes. + if (!did_header) { + if (endcol == Columns - 1 && endcol <= name_col) { + msg_putchar(' '); + } + msg_puts_attr("xxx", syn_id2attr(id)); + msg_putchar(' '); + } + + return newline; +} + +/// Set the attribute numbers for a highlight group. +/// Called after one of the attributes has changed. +/// @param idx corrected highlight index +static void set_hl_attr(int idx) +{ + HlAttrs at_en = HLATTRS_INIT; + HlGroup *sgp = HL_TABLE() + idx; + + at_en.cterm_ae_attr = (int16_t)sgp->sg_cterm; + at_en.cterm_fg_color = sgp->sg_cterm_fg; + at_en.cterm_bg_color = sgp->sg_cterm_bg; + at_en.rgb_ae_attr = (int16_t)sgp->sg_gui; + // FIXME(tarruda): The "unset value" for rgb is -1, but since hlgroup is + // initialized with 0(by garray functions), check for sg_rgb_{f,b}g_name + // before setting attr_entry->{f,g}g_color to a other than -1 + at_en.rgb_fg_color = sgp->sg_rgb_fg_name ? sgp->sg_rgb_fg : -1; + at_en.rgb_bg_color = sgp->sg_rgb_bg_name ? sgp->sg_rgb_bg : -1; + at_en.rgb_sp_color = sgp->sg_rgb_sp_name ? sgp->sg_rgb_sp : -1; + at_en.hl_blend = sgp->sg_blend; + + sgp->sg_attr = hl_get_syn_attr(0, idx+1, at_en); + + // a cursor style uses this syn_id, make sure its attribute is updated. + if (cursor_mode_uses_syn_id(idx+1)) { + ui_mode_info_set(); + } +} + +int syn_name2id(const char *name) + FUNC_ATTR_NONNULL_ALL +{ + return syn_name2id_len((char_u *)name, STRLEN(name)); +} + +/// Lookup a highlight group name and return its ID. +/// +/// @param highlight name e.g. 'Cursor', 'Normal' +/// @return the highlight id, else 0 if \p name does not exist +int syn_name2id_len(const char_u *name, size_t len) + FUNC_ATTR_NONNULL_ALL +{ + char name_u[MAX_SYN_NAME + 1]; + + if (len == 0 || len > MAX_SYN_NAME) { + return 0; + } + + // Avoid using stricmp() too much, it's slow on some systems */ + // Avoid alloc()/free(), these are slow too. + memcpy(name_u, name, len); + name_u[len] = '\0'; + vim_strup((char_u *)name_u); + + // map_get(..., int) returns 0 when no key is present, which is + // the expected value for missing highlight group. + return map_get(cstr_t, int)(&highlight_unames, name_u); +} + +/// Lookup a highlight group name and return its attributes. +/// Return zero if not found. +int syn_name2attr(const char_u *name) + FUNC_ATTR_NONNULL_ALL +{ + int id = syn_name2id((char *)name); + + if (id != 0) { + return syn_id2attr(id); + } + return 0; +} + +/// Return TRUE if highlight group "name" exists. +int highlight_exists(const char *name) +{ + return syn_name2id(name) > 0; +} + +/// Return the name of highlight group "id". +/// When not a valid ID return an empty string. +char_u *syn_id2name(int id) +{ + if (id <= 0 || id > highlight_ga.ga_len) { + return (char_u *)""; + } + return HL_TABLE()[id - 1].sg_name; +} + +/// Find highlight group name in the table and return its ID. +/// If it doesn't exist yet, a new entry is created. +/// +/// @param pp Highlight group name +/// @param len length of \p pp +/// +/// @return 0 for failure else the id of the group +int syn_check_group(const char *name, size_t len) +{ + if (len > MAX_SYN_NAME) { + emsg(_(e_highlight_group_name_too_long)); + return 0; + } + int id = syn_name2id_len((char_u *)name, len); + if (id == 0) { // doesn't exist yet + return syn_add_group(vim_strnsave((char_u *)name, len)); + } + return id; +} + +/// Add new highlight group and return its ID. +/// +/// @param name must be an allocated string, it will be consumed. +/// @return 0 for failure, else the allocated group id +/// @see syn_check_group syn_unadd_group +static int syn_add_group(char_u *name) +{ + char_u *p; + + // Check that the name is ASCII letters, digits and underscore. + for (p = name; *p != NUL; p++) { + if (!vim_isprintc(*p)) { + emsg(_("E669: Unprintable character in group name")); + xfree(name); + return 0; + } else if (!ASCII_ISALNUM(*p) && *p != '_') { + // This is an error, but since there previously was no check only give a warning. + msg_source(HL_ATTR(HLF_W)); + msg(_("W18: Invalid character in group name")); + break; + } + } + + // First call for this growarray: init growing array. + if (highlight_ga.ga_data == NULL) { + highlight_ga.ga_itemsize = sizeof(HlGroup); + ga_set_growsize(&highlight_ga, 10); + } + + if (highlight_ga.ga_len >= MAX_HL_ID) { + emsg(_("E849: Too many highlight and syntax groups")); + xfree(name); + return 0; + } + + char *const name_up = (char *)vim_strsave_up(name); + + // Append another syntax_highlight entry. + HlGroup *hlgp = GA_APPEND_VIA_PTR(HlGroup, &highlight_ga); + memset(hlgp, 0, sizeof(*hlgp)); + hlgp->sg_name = name; + hlgp->sg_rgb_bg = -1; + hlgp->sg_rgb_fg = -1; + hlgp->sg_rgb_sp = -1; + hlgp->sg_blend = -1; + hlgp->sg_name_u = name_up; + + int id = highlight_ga.ga_len; // ID is index plus one + + map_put(cstr_t, int)(&highlight_unames, name_up, id); + + return id; +} + +/// When, just after calling syn_add_group(), an error is discovered, this +/// function deletes the new name. +static void syn_unadd_group(void) +{ + highlight_ga.ga_len--; + HlGroup *item = &HL_TABLE()[highlight_ga.ga_len]; + map_del(cstr_t, int)(&highlight_unames, item->sg_name_u); + xfree(item->sg_name); + xfree(item->sg_name_u); +} + +/// Translate a group ID to highlight attributes. +/// @see syn_attr2entry +int syn_id2attr(int hl_id) +{ + hl_id = syn_get_final_id(hl_id); + HlGroup *sgp = &HL_TABLE()[hl_id - 1]; // index is ID minus one + + int attr = ns_get_hl(-1, hl_id, false, sgp->sg_set); + if (attr >= 0) { + return attr; + } + return sgp->sg_attr; +} + +/// Translate a group ID to the final group ID (following links). +int syn_get_final_id(int hl_id) +{ + int count; + + if (hl_id > highlight_ga.ga_len || hl_id < 1) { + return 0; // Can be called from eval!! + } + + // Follow links until there is no more. + // Look out for loops! Break after 100 links. + for (count = 100; --count >= 0;) { + HlGroup *sgp = &HL_TABLE()[hl_id - 1]; // index is ID minus one + + // ACHTUNG: when using "tmp" attribute (no link) the function might be + // called twice. it needs be smart enough to remember attr only to + // syn_id2attr time + int check = ns_get_hl(-1, hl_id, true, sgp->sg_set); + if (check == 0) { + return hl_id; // how dare! it broke the link! + } else if (check > 0) { + hl_id = check; + continue; + } + + + if (sgp->sg_link == 0 || sgp->sg_link > highlight_ga.ga_len) { + break; + } + hl_id = sgp->sg_link; + } + + return hl_id; +} + +/// Refresh the color attributes of all highlight groups. +void highlight_attr_set_all(void) +{ + for (int idx = 0; idx < highlight_ga.ga_len; idx++) { + HlGroup *sgp = &HL_TABLE()[idx]; + if (sgp->sg_rgb_bg_name != NULL) { + sgp->sg_rgb_bg = name_to_color(sgp->sg_rgb_bg_name); + } + if (sgp->sg_rgb_fg_name != NULL) { + sgp->sg_rgb_fg = name_to_color(sgp->sg_rgb_fg_name); + } + if (sgp->sg_rgb_sp_name != NULL) { + sgp->sg_rgb_sp = name_to_color(sgp->sg_rgb_sp_name); + } + set_hl_attr(idx); + } +} + +// Apply difference between User[1-9] and HLF_S to HLF_SNC. +static void combine_stl_hlt(int id, int id_S, int id_alt, int hlcnt, int i, int hlf, int *table) + FUNC_ATTR_NONNULL_ALL +{ + HlGroup *const hlt = HL_TABLE(); + + if (id_alt == 0) { + memset(&hlt[hlcnt + i], 0, sizeof(HlGroup)); + hlt[hlcnt + i].sg_cterm = highlight_attr[hlf]; + hlt[hlcnt + i].sg_gui = highlight_attr[hlf]; + } else { + memmove(&hlt[hlcnt + i], &hlt[id_alt - 1], sizeof(HlGroup)); + } + hlt[hlcnt + i].sg_link = 0; + + hlt[hlcnt + i].sg_cterm ^= hlt[id - 1].sg_cterm ^ hlt[id_S - 1].sg_cterm; + if (hlt[id - 1].sg_cterm_fg != hlt[id_S - 1].sg_cterm_fg) { + hlt[hlcnt + i].sg_cterm_fg = hlt[id - 1].sg_cterm_fg; + } + if (hlt[id - 1].sg_cterm_bg != hlt[id_S - 1].sg_cterm_bg) { + hlt[hlcnt + i].sg_cterm_bg = hlt[id - 1].sg_cterm_bg; + } + hlt[hlcnt + i].sg_gui ^= hlt[id - 1].sg_gui ^ hlt[id_S - 1].sg_gui; + if (hlt[id - 1].sg_rgb_fg != hlt[id_S - 1].sg_rgb_fg) { + hlt[hlcnt + i].sg_rgb_fg = hlt[id - 1].sg_rgb_fg; + } + if (hlt[id - 1].sg_rgb_bg != hlt[id_S - 1].sg_rgb_bg) { + hlt[hlcnt + i].sg_rgb_bg = hlt[id - 1].sg_rgb_bg; + } + if (hlt[id - 1].sg_rgb_sp != hlt[id_S - 1].sg_rgb_sp) { + hlt[hlcnt + i].sg_rgb_sp = hlt[id - 1].sg_rgb_sp; + } + highlight_ga.ga_len = hlcnt + i + 1; + set_hl_attr(hlcnt + i); // At long last we can apply + table[i] = syn_id2attr(hlcnt + i + 1); +} + +/// Translate highlight groups into attributes in highlight_attr[] and set up +/// the user highlights User1..9. A set of corresponding highlights to use on +/// top of HLF_SNC is computed. Called only when nvim starts and upon first +/// screen redraw after any :highlight command. +void highlight_changed(void) +{ + int id; + char userhl[30]; // use 30 to avoid compiler warning + int id_S = -1; + int id_SNC = 0; + int hlcnt; + + need_highlight_changed = false; + + /// Translate builtin highlight groups into attributes for quick lookup. + for (int hlf = 0; hlf < HLF_COUNT; hlf++) { + id = syn_check_group(hlf_names[hlf], STRLEN(hlf_names[hlf])); + if (id == 0) { + abort(); + } + int final_id = syn_get_final_id(id); + if (hlf == HLF_SNC) { + id_SNC = final_id; + } else if (hlf == HLF_S) { + id_S = final_id; + } + + highlight_attr[hlf] = hl_get_ui_attr(hlf, final_id, + hlf == HLF_INACTIVE); + + if (highlight_attr[hlf] != highlight_attr_last[hlf]) { + if (hlf == HLF_MSG) { + clear_cmdline = true; + } + ui_call_hl_group_set(cstr_as_string((char *)hlf_names[hlf]), + highlight_attr[hlf]); + highlight_attr_last[hlf] = highlight_attr[hlf]; + } + } + + // + // Setup the user highlights + // + // Temporarily utilize 10 more hl entries: + // 9 for User1-User9 combined with StatusLineNC + // 1 for StatusLine default + // Must to be in there simultaneously in case of table overflows in + // get_attr_entry() + ga_grow(&highlight_ga, 10); + hlcnt = highlight_ga.ga_len; + if (id_S == -1) { + // Make sure id_S is always valid to simplify code below. Use the last entry + memset(&HL_TABLE()[hlcnt + 9], 0, sizeof(HlGroup)); + id_S = hlcnt + 10; + } + for (int i = 0; i < 9; i++) { + snprintf(userhl, sizeof(userhl), "User%d", i + 1); + id = syn_name2id(userhl); + if (id == 0) { + highlight_user[i] = 0; + highlight_stlnc[i] = 0; + } else { + highlight_user[i] = syn_id2attr(id); + combine_stl_hlt(id, id_S, id_SNC, hlcnt, i, HLF_SNC, highlight_stlnc); + } + } + highlight_ga.ga_len = hlcnt; +} + +/// Handle command line completion for :highlight command. +void set_context_in_highlight_cmd(expand_T *xp, const char *arg) +{ + // Default: expand group names. + xp->xp_context = EXPAND_HIGHLIGHT; + xp->xp_pattern = (char_u *)arg; + include_link = 2; + include_default = 1; + + // (part of) subcommand already typed + if (*arg != NUL) { + const char *p = (const char *)skiptowhite((const char_u *)arg); + if (*p != NUL) { // Past "default" or group name. + include_default = 0; + if (strncmp("default", arg, (unsigned)(p - arg)) == 0) { + arg = (const char *)skipwhite((const char_u *)p); + xp->xp_pattern = (char_u *)arg; + p = (const char *)skiptowhite((const char_u *)arg); + } + if (*p != NUL) { // past group name + include_link = 0; + if (arg[1] == 'i' && arg[0] == 'N') { + highlight_list(); + } + if (strncmp("link", arg, (unsigned)(p - arg)) == 0 + || strncmp("clear", arg, (unsigned)(p - arg)) == 0) { + xp->xp_pattern = skipwhite((const char_u *)p); + p = (const char *)skiptowhite(xp->xp_pattern); + if (*p != NUL) { // Past first group name. + xp->xp_pattern = skipwhite((const char_u *)p); + p = (const char *)skiptowhite(xp->xp_pattern); + } + } + if (*p != NUL) { // Past group name(s). + xp->xp_context = EXPAND_NOTHING; + } + } + } + } +} + +/// List highlighting matches in a nice way. +static void highlight_list(void) +{ + int i; + + for (i = 10; --i >= 0;) { + highlight_list_two(i, HL_ATTR(HLF_D)); + } + for (i = 40; --i >= 0;) { + highlight_list_two(99, 0); + } +} + +static void highlight_list_two(int cnt, int attr) +{ + msg_puts_attr(&("N \bI \b! \b"[cnt / 11]), attr); + msg_clr_eos(); + ui_flush(); + os_delay(cnt == 99 ? 40L : (uint64_t)cnt * 50L, false); +} + +/// Function given to ExpandGeneric() to obtain the list of group names. +const char *get_highlight_name(expand_T *const xp, int idx) + FUNC_ATTR_WARN_UNUSED_RESULT +{ + return get_highlight_name_ext(xp, idx, true); +} + +/// Obtain a highlight group name. +/// +/// @param skip_cleared if true don't return a cleared entry. +const char *get_highlight_name_ext(expand_T *xp, int idx, bool skip_cleared) + FUNC_ATTR_WARN_UNUSED_RESULT +{ + if (idx < 0) { + return NULL; + } + + // Items are never removed from the table, skip the ones that were cleared. + if (skip_cleared && idx < highlight_ga.ga_len && HL_TABLE()[idx].sg_cleared) { + return ""; + } + + if (idx == highlight_ga.ga_len && include_none != 0) { + return "none"; + } else if (idx == highlight_ga.ga_len + include_none + && include_default != 0) { + return "default"; + } else if (idx == highlight_ga.ga_len + include_none + include_default + && include_link != 0) { + return "link"; + } else if (idx == highlight_ga.ga_len + include_none + include_default + 1 + && include_link != 0) { + return "clear"; + } else if (idx >= highlight_ga.ga_len) { + return NULL; + } + return (const char *)HL_TABLE()[idx].sg_name; +} + +color_name_table_T color_name_table[] = { + // Colors from rgb.txt + { "AliceBlue", RGB_(0xf0, 0xf8, 0xff) }, + { "AntiqueWhite", RGB_(0xfa, 0xeb, 0xd7) }, + { "AntiqueWhite1", RGB_(0xff, 0xef, 0xdb) }, + { "AntiqueWhite2", RGB_(0xee, 0xdf, 0xcc) }, + { "AntiqueWhite3", RGB_(0xcd, 0xc0, 0xb0) }, + { "AntiqueWhite4", RGB_(0x8b, 0x83, 0x78) }, + { "Aqua", RGB_(0x00, 0xff, 0xff) }, + { "Aquamarine", RGB_(0x7f, 0xff, 0xd4) }, + { "Aquamarine1", RGB_(0x7f, 0xff, 0xd4) }, + { "Aquamarine2", RGB_(0x76, 0xee, 0xc6) }, + { "Aquamarine3", RGB_(0x66, 0xcd, 0xaa) }, + { "Aquamarine4", RGB_(0x45, 0x8b, 0x74) }, + { "Azure", RGB_(0xf0, 0xff, 0xff) }, + { "Azure1", RGB_(0xf0, 0xff, 0xff) }, + { "Azure2", RGB_(0xe0, 0xee, 0xee) }, + { "Azure3", RGB_(0xc1, 0xcd, 0xcd) }, + { "Azure4", RGB_(0x83, 0x8b, 0x8b) }, + { "Beige", RGB_(0xf5, 0xf5, 0xdc) }, + { "Bisque", RGB_(0xff, 0xe4, 0xc4) }, + { "Bisque1", RGB_(0xff, 0xe4, 0xc4) }, + { "Bisque2", RGB_(0xee, 0xd5, 0xb7) }, + { "Bisque3", RGB_(0xcd, 0xb7, 0x9e) }, + { "Bisque4", RGB_(0x8b, 0x7d, 0x6b) }, + { "Black", RGB_(0x00, 0x00, 0x00) }, + { "BlanchedAlmond", RGB_(0xff, 0xeb, 0xcd) }, + { "Blue", RGB_(0x00, 0x00, 0xff) }, + { "Blue1", RGB_(0x0, 0x0, 0xff) }, + { "Blue2", RGB_(0x0, 0x0, 0xee) }, + { "Blue3", RGB_(0x0, 0x0, 0xcd) }, + { "Blue4", RGB_(0x0, 0x0, 0x8b) }, + { "BlueViolet", RGB_(0x8a, 0x2b, 0xe2) }, + { "Brown", RGB_(0xa5, 0x2a, 0x2a) }, + { "Brown1", RGB_(0xff, 0x40, 0x40) }, + { "Brown2", RGB_(0xee, 0x3b, 0x3b) }, + { "Brown3", RGB_(0xcd, 0x33, 0x33) }, + { "Brown4", RGB_(0x8b, 0x23, 0x23) }, + { "BurlyWood", RGB_(0xde, 0xb8, 0x87) }, + { "Burlywood1", RGB_(0xff, 0xd3, 0x9b) }, + { "Burlywood2", RGB_(0xee, 0xc5, 0x91) }, + { "Burlywood3", RGB_(0xcd, 0xaa, 0x7d) }, + { "Burlywood4", RGB_(0x8b, 0x73, 0x55) }, + { "CadetBlue", RGB_(0x5f, 0x9e, 0xa0) }, + { "CadetBlue1", RGB_(0x98, 0xf5, 0xff) }, + { "CadetBlue2", RGB_(0x8e, 0xe5, 0xee) }, + { "CadetBlue3", RGB_(0x7a, 0xc5, 0xcd) }, + { "CadetBlue4", RGB_(0x53, 0x86, 0x8b) }, + { "ChartReuse", RGB_(0x7f, 0xff, 0x00) }, + { "Chartreuse1", RGB_(0x7f, 0xff, 0x0) }, + { "Chartreuse2", RGB_(0x76, 0xee, 0x0) }, + { "Chartreuse3", RGB_(0x66, 0xcd, 0x0) }, + { "Chartreuse4", RGB_(0x45, 0x8b, 0x0) }, + { "Chocolate", RGB_(0xd2, 0x69, 0x1e) }, + { "Chocolate1", RGB_(0xff, 0x7f, 0x24) }, + { "Chocolate2", RGB_(0xee, 0x76, 0x21) }, + { "Chocolate3", RGB_(0xcd, 0x66, 0x1d) }, + { "Chocolate4", RGB_(0x8b, 0x45, 0x13) }, + { "Coral", RGB_(0xff, 0x7f, 0x50) }, + { "Coral1", RGB_(0xff, 0x72, 0x56) }, + { "Coral2", RGB_(0xee, 0x6a, 0x50) }, + { "Coral3", RGB_(0xcd, 0x5b, 0x45) }, + { "Coral4", RGB_(0x8b, 0x3e, 0x2f) }, + { "CornFlowerBlue", RGB_(0x64, 0x95, 0xed) }, + { "Cornsilk", RGB_(0xff, 0xf8, 0xdc) }, + { "Cornsilk1", RGB_(0xff, 0xf8, 0xdc) }, + { "Cornsilk2", RGB_(0xee, 0xe8, 0xcd) }, + { "Cornsilk3", RGB_(0xcd, 0xc8, 0xb1) }, + { "Cornsilk4", RGB_(0x8b, 0x88, 0x78) }, + { "Crimson", RGB_(0xdc, 0x14, 0x3c) }, + { "Cyan", RGB_(0x00, 0xff, 0xff) }, + { "Cyan1", RGB_(0x0, 0xff, 0xff) }, + { "Cyan2", RGB_(0x0, 0xee, 0xee) }, + { "Cyan3", RGB_(0x0, 0xcd, 0xcd) }, + { "Cyan4", RGB_(0x0, 0x8b, 0x8b) }, + { "DarkBlue", RGB_(0x00, 0x00, 0x8b) }, + { "DarkCyan", RGB_(0x00, 0x8b, 0x8b) }, + { "DarkGoldenRod", RGB_(0xb8, 0x86, 0x0b) }, + { "DarkGoldenrod1", RGB_(0xff, 0xb9, 0xf) }, + { "DarkGoldenrod2", RGB_(0xee, 0xad, 0xe) }, + { "DarkGoldenrod3", RGB_(0xcd, 0x95, 0xc) }, + { "DarkGoldenrod4", RGB_(0x8b, 0x65, 0x8) }, + { "DarkGray", RGB_(0xa9, 0xa9, 0xa9) }, + { "DarkGreen", RGB_(0x00, 0x64, 0x00) }, + { "DarkGrey", RGB_(0xa9, 0xa9, 0xa9) }, + { "DarkKhaki", RGB_(0xbd, 0xb7, 0x6b) }, + { "DarkMagenta", RGB_(0x8b, 0x00, 0x8b) }, + { "DarkOliveGreen", RGB_(0x55, 0x6b, 0x2f) }, + { "DarkOliveGreen1", RGB_(0xca, 0xff, 0x70) }, + { "DarkOliveGreen2", RGB_(0xbc, 0xee, 0x68) }, + { "DarkOliveGreen3", RGB_(0xa2, 0xcd, 0x5a) }, + { "DarkOliveGreen4", RGB_(0x6e, 0x8b, 0x3d) }, + { "DarkOrange", RGB_(0xff, 0x8c, 0x00) }, + { "DarkOrange1", RGB_(0xff, 0x7f, 0x0) }, + { "DarkOrange2", RGB_(0xee, 0x76, 0x0) }, + { "DarkOrange3", RGB_(0xcd, 0x66, 0x0) }, + { "DarkOrange4", RGB_(0x8b, 0x45, 0x0) }, + { "DarkOrchid", RGB_(0x99, 0x32, 0xcc) }, + { "DarkOrchid1", RGB_(0xbf, 0x3e, 0xff) }, + { "DarkOrchid2", RGB_(0xb2, 0x3a, 0xee) }, + { "DarkOrchid3", RGB_(0x9a, 0x32, 0xcd) }, + { "DarkOrchid4", RGB_(0x68, 0x22, 0x8b) }, + { "DarkRed", RGB_(0x8b, 0x00, 0x00) }, + { "DarkSalmon", RGB_(0xe9, 0x96, 0x7a) }, + { "DarkSeaGreen", RGB_(0x8f, 0xbc, 0x8f) }, + { "DarkSeaGreen1", RGB_(0xc1, 0xff, 0xc1) }, + { "DarkSeaGreen2", RGB_(0xb4, 0xee, 0xb4) }, + { "DarkSeaGreen3", RGB_(0x9b, 0xcd, 0x9b) }, + { "DarkSeaGreen4", RGB_(0x69, 0x8b, 0x69) }, + { "DarkSlateBlue", RGB_(0x48, 0x3d, 0x8b) }, + { "DarkSlateGray", RGB_(0x2f, 0x4f, 0x4f) }, + { "DarkSlateGray1", RGB_(0x97, 0xff, 0xff) }, + { "DarkSlateGray2", RGB_(0x8d, 0xee, 0xee) }, + { "DarkSlateGray3", RGB_(0x79, 0xcd, 0xcd) }, + { "DarkSlateGray4", RGB_(0x52, 0x8b, 0x8b) }, + { "DarkSlateGrey", RGB_(0x2f, 0x4f, 0x4f) }, + { "DarkTurquoise", RGB_(0x00, 0xce, 0xd1) }, + { "DarkViolet", RGB_(0x94, 0x00, 0xd3) }, + { "DarkYellow", RGB_(0xbb, 0xbb, 0x00) }, + { "DeepPink", RGB_(0xff, 0x14, 0x93) }, + { "DeepPink1", RGB_(0xff, 0x14, 0x93) }, + { "DeepPink2", RGB_(0xee, 0x12, 0x89) }, + { "DeepPink3", RGB_(0xcd, 0x10, 0x76) }, + { "DeepPink4", RGB_(0x8b, 0xa, 0x50) }, + { "DeepSkyBlue", RGB_(0x00, 0xbf, 0xff) }, + { "DeepSkyBlue1", RGB_(0x0, 0xbf, 0xff) }, + { "DeepSkyBlue2", RGB_(0x0, 0xb2, 0xee) }, + { "DeepSkyBlue3", RGB_(0x0, 0x9a, 0xcd) }, + { "DeepSkyBlue4", RGB_(0x0, 0x68, 0x8b) }, + { "DimGray", RGB_(0x69, 0x69, 0x69) }, + { "DimGrey", RGB_(0x69, 0x69, 0x69) }, + { "DodgerBlue", RGB_(0x1e, 0x90, 0xff) }, + { "DodgerBlue1", RGB_(0x1e, 0x90, 0xff) }, + { "DodgerBlue2", RGB_(0x1c, 0x86, 0xee) }, + { "DodgerBlue3", RGB_(0x18, 0x74, 0xcd) }, + { "DodgerBlue4", RGB_(0x10, 0x4e, 0x8b) }, + { "Firebrick", RGB_(0xb2, 0x22, 0x22) }, + { "Firebrick1", RGB_(0xff, 0x30, 0x30) }, + { "Firebrick2", RGB_(0xee, 0x2c, 0x2c) }, + { "Firebrick3", RGB_(0xcd, 0x26, 0x26) }, + { "Firebrick4", RGB_(0x8b, 0x1a, 0x1a) }, + { "FloralWhite", RGB_(0xff, 0xfa, 0xf0) }, + { "ForestGreen", RGB_(0x22, 0x8b, 0x22) }, + { "Fuchsia", RGB_(0xff, 0x00, 0xff) }, + { "Gainsboro", RGB_(0xdc, 0xdc, 0xdc) }, + { "GhostWhite", RGB_(0xf8, 0xf8, 0xff) }, + { "Gold", RGB_(0xff, 0xd7, 0x00) }, + { "Gold1", RGB_(0xff, 0xd7, 0x0) }, + { "Gold2", RGB_(0xee, 0xc9, 0x0) }, + { "Gold3", RGB_(0xcd, 0xad, 0x0) }, + { "Gold4", RGB_(0x8b, 0x75, 0x0) }, + { "GoldenRod", RGB_(0xda, 0xa5, 0x20) }, + { "Goldenrod1", RGB_(0xff, 0xc1, 0x25) }, + { "Goldenrod2", RGB_(0xee, 0xb4, 0x22) }, + { "Goldenrod3", RGB_(0xcd, 0x9b, 0x1d) }, + { "Goldenrod4", RGB_(0x8b, 0x69, 0x14) }, + { "Gray", RGB_(0x80, 0x80, 0x80) }, + { "Gray0", RGB_(0x0, 0x0, 0x0) }, + { "Gray1", RGB_(0x3, 0x3, 0x3) }, + { "Gray10", RGB_(0x1a, 0x1a, 0x1a) }, + { "Gray100", RGB_(0xff, 0xff, 0xff) }, + { "Gray11", RGB_(0x1c, 0x1c, 0x1c) }, + { "Gray12", RGB_(0x1f, 0x1f, 0x1f) }, + { "Gray13", RGB_(0x21, 0x21, 0x21) }, + { "Gray14", RGB_(0x24, 0x24, 0x24) }, + { "Gray15", RGB_(0x26, 0x26, 0x26) }, + { "Gray16", RGB_(0x29, 0x29, 0x29) }, + { "Gray17", RGB_(0x2b, 0x2b, 0x2b) }, + { "Gray18", RGB_(0x2e, 0x2e, 0x2e) }, + { "Gray19", RGB_(0x30, 0x30, 0x30) }, + { "Gray2", RGB_(0x5, 0x5, 0x5) }, + { "Gray20", RGB_(0x33, 0x33, 0x33) }, + { "Gray21", RGB_(0x36, 0x36, 0x36) }, + { "Gray22", RGB_(0x38, 0x38, 0x38) }, + { "Gray23", RGB_(0x3b, 0x3b, 0x3b) }, + { "Gray24", RGB_(0x3d, 0x3d, 0x3d) }, + { "Gray25", RGB_(0x40, 0x40, 0x40) }, + { "Gray26", RGB_(0x42, 0x42, 0x42) }, + { "Gray27", RGB_(0x45, 0x45, 0x45) }, + { "Gray28", RGB_(0x47, 0x47, 0x47) }, + { "Gray29", RGB_(0x4a, 0x4a, 0x4a) }, + { "Gray3", RGB_(0x8, 0x8, 0x8) }, + { "Gray30", RGB_(0x4d, 0x4d, 0x4d) }, + { "Gray31", RGB_(0x4f, 0x4f, 0x4f) }, + { "Gray32", RGB_(0x52, 0x52, 0x52) }, + { "Gray33", RGB_(0x54, 0x54, 0x54) }, + { "Gray34", RGB_(0x57, 0x57, 0x57) }, + { "Gray35", RGB_(0x59, 0x59, 0x59) }, + { "Gray36", RGB_(0x5c, 0x5c, 0x5c) }, + { "Gray37", RGB_(0x5e, 0x5e, 0x5e) }, + { "Gray38", RGB_(0x61, 0x61, 0x61) }, + { "Gray39", RGB_(0x63, 0x63, 0x63) }, + { "Gray4", RGB_(0xa, 0xa, 0xa) }, + { "Gray40", RGB_(0x66, 0x66, 0x66) }, + { "Gray41", RGB_(0x69, 0x69, 0x69) }, + { "Gray42", RGB_(0x6b, 0x6b, 0x6b) }, + { "Gray43", RGB_(0x6e, 0x6e, 0x6e) }, + { "Gray44", RGB_(0x70, 0x70, 0x70) }, + { "Gray45", RGB_(0x73, 0x73, 0x73) }, + { "Gray46", RGB_(0x75, 0x75, 0x75) }, + { "Gray47", RGB_(0x78, 0x78, 0x78) }, + { "Gray48", RGB_(0x7a, 0x7a, 0x7a) }, + { "Gray49", RGB_(0x7d, 0x7d, 0x7d) }, + { "Gray5", RGB_(0xd, 0xd, 0xd) }, + { "Gray50", RGB_(0x7f, 0x7f, 0x7f) }, + { "Gray51", RGB_(0x82, 0x82, 0x82) }, + { "Gray52", RGB_(0x85, 0x85, 0x85) }, + { "Gray53", RGB_(0x87, 0x87, 0x87) }, + { "Gray54", RGB_(0x8a, 0x8a, 0x8a) }, + { "Gray55", RGB_(0x8c, 0x8c, 0x8c) }, + { "Gray56", RGB_(0x8f, 0x8f, 0x8f) }, + { "Gray57", RGB_(0x91, 0x91, 0x91) }, + { "Gray58", RGB_(0x94, 0x94, 0x94) }, + { "Gray59", RGB_(0x96, 0x96, 0x96) }, + { "Gray6", RGB_(0xf, 0xf, 0xf) }, + { "Gray60", RGB_(0x99, 0x99, 0x99) }, + { "Gray61", RGB_(0x9c, 0x9c, 0x9c) }, + { "Gray62", RGB_(0x9e, 0x9e, 0x9e) }, + { "Gray63", RGB_(0xa1, 0xa1, 0xa1) }, + { "Gray64", RGB_(0xa3, 0xa3, 0xa3) }, + { "Gray65", RGB_(0xa6, 0xa6, 0xa6) }, + { "Gray66", RGB_(0xa8, 0xa8, 0xa8) }, + { "Gray67", RGB_(0xab, 0xab, 0xab) }, + { "Gray68", RGB_(0xad, 0xad, 0xad) }, + { "Gray69", RGB_(0xb0, 0xb0, 0xb0) }, + { "Gray7", RGB_(0x12, 0x12, 0x12) }, + { "Gray70", RGB_(0xb3, 0xb3, 0xb3) }, + { "Gray71", RGB_(0xb5, 0xb5, 0xb5) }, + { "Gray72", RGB_(0xb8, 0xb8, 0xb8) }, + { "Gray73", RGB_(0xba, 0xba, 0xba) }, + { "Gray74", RGB_(0xbd, 0xbd, 0xbd) }, + { "Gray75", RGB_(0xbf, 0xbf, 0xbf) }, + { "Gray76", RGB_(0xc2, 0xc2, 0xc2) }, + { "Gray77", RGB_(0xc4, 0xc4, 0xc4) }, + { "Gray78", RGB_(0xc7, 0xc7, 0xc7) }, + { "Gray79", RGB_(0xc9, 0xc9, 0xc9) }, + { "Gray8", RGB_(0x14, 0x14, 0x14) }, + { "Gray80", RGB_(0xcc, 0xcc, 0xcc) }, + { "Gray81", RGB_(0xcf, 0xcf, 0xcf) }, + { "Gray82", RGB_(0xd1, 0xd1, 0xd1) }, + { "Gray83", RGB_(0xd4, 0xd4, 0xd4) }, + { "Gray84", RGB_(0xd6, 0xd6, 0xd6) }, + { "Gray85", RGB_(0xd9, 0xd9, 0xd9) }, + { "Gray86", RGB_(0xdb, 0xdb, 0xdb) }, + { "Gray87", RGB_(0xde, 0xde, 0xde) }, + { "Gray88", RGB_(0xe0, 0xe0, 0xe0) }, + { "Gray89", RGB_(0xe3, 0xe3, 0xe3) }, + { "Gray9", RGB_(0x17, 0x17, 0x17) }, + { "Gray90", RGB_(0xe5, 0xe5, 0xe5) }, + { "Gray91", RGB_(0xe8, 0xe8, 0xe8) }, + { "Gray92", RGB_(0xeb, 0xeb, 0xeb) }, + { "Gray93", RGB_(0xed, 0xed, 0xed) }, + { "Gray94", RGB_(0xf0, 0xf0, 0xf0) }, + { "Gray95", RGB_(0xf2, 0xf2, 0xf2) }, + { "Gray96", RGB_(0xf5, 0xf5, 0xf5) }, + { "Gray97", RGB_(0xf7, 0xf7, 0xf7) }, + { "Gray98", RGB_(0xfa, 0xfa, 0xfa) }, + { "Gray99", RGB_(0xfc, 0xfc, 0xfc) }, + { "Green", RGB_(0x00, 0x80, 0x00) }, + { "Green1", RGB_(0x0, 0xff, 0x0) }, + { "Green2", RGB_(0x0, 0xee, 0x0) }, + { "Green3", RGB_(0x0, 0xcd, 0x0) }, + { "Green4", RGB_(0x0, 0x8b, 0x0) }, + { "GreenYellow", RGB_(0xad, 0xff, 0x2f) }, + { "Grey", RGB_(0x80, 0x80, 0x80) }, + { "Grey0", RGB_(0x0, 0x0, 0x0) }, + { "Grey1", RGB_(0x3, 0x3, 0x3) }, + { "Grey10", RGB_(0x1a, 0x1a, 0x1a) }, + { "Grey100", RGB_(0xff, 0xff, 0xff) }, + { "Grey11", RGB_(0x1c, 0x1c, 0x1c) }, + { "Grey12", RGB_(0x1f, 0x1f, 0x1f) }, + { "Grey13", RGB_(0x21, 0x21, 0x21) }, + { "Grey14", RGB_(0x24, 0x24, 0x24) }, + { "Grey15", RGB_(0x26, 0x26, 0x26) }, + { "Grey16", RGB_(0x29, 0x29, 0x29) }, + { "Grey17", RGB_(0x2b, 0x2b, 0x2b) }, + { "Grey18", RGB_(0x2e, 0x2e, 0x2e) }, + { "Grey19", RGB_(0x30, 0x30, 0x30) }, + { "Grey2", RGB_(0x5, 0x5, 0x5) }, + { "Grey20", RGB_(0x33, 0x33, 0x33) }, + { "Grey21", RGB_(0x36, 0x36, 0x36) }, + { "Grey22", RGB_(0x38, 0x38, 0x38) }, + { "Grey23", RGB_(0x3b, 0x3b, 0x3b) }, + { "Grey24", RGB_(0x3d, 0x3d, 0x3d) }, + { "Grey25", RGB_(0x40, 0x40, 0x40) }, + { "Grey26", RGB_(0x42, 0x42, 0x42) }, + { "Grey27", RGB_(0x45, 0x45, 0x45) }, + { "Grey28", RGB_(0x47, 0x47, 0x47) }, + { "Grey29", RGB_(0x4a, 0x4a, 0x4a) }, + { "Grey3", RGB_(0x8, 0x8, 0x8) }, + { "Grey30", RGB_(0x4d, 0x4d, 0x4d) }, + { "Grey31", RGB_(0x4f, 0x4f, 0x4f) }, + { "Grey32", RGB_(0x52, 0x52, 0x52) }, + { "Grey33", RGB_(0x54, 0x54, 0x54) }, + { "Grey34", RGB_(0x57, 0x57, 0x57) }, + { "Grey35", RGB_(0x59, 0x59, 0x59) }, + { "Grey36", RGB_(0x5c, 0x5c, 0x5c) }, + { "Grey37", RGB_(0x5e, 0x5e, 0x5e) }, + { "Grey38", RGB_(0x61, 0x61, 0x61) }, + { "Grey39", RGB_(0x63, 0x63, 0x63) }, + { "Grey4", RGB_(0xa, 0xa, 0xa) }, + { "Grey40", RGB_(0x66, 0x66, 0x66) }, + { "Grey41", RGB_(0x69, 0x69, 0x69) }, + { "Grey42", RGB_(0x6b, 0x6b, 0x6b) }, + { "Grey43", RGB_(0x6e, 0x6e, 0x6e) }, + { "Grey44", RGB_(0x70, 0x70, 0x70) }, + { "Grey45", RGB_(0x73, 0x73, 0x73) }, + { "Grey46", RGB_(0x75, 0x75, 0x75) }, + { "Grey47", RGB_(0x78, 0x78, 0x78) }, + { "Grey48", RGB_(0x7a, 0x7a, 0x7a) }, + { "Grey49", RGB_(0x7d, 0x7d, 0x7d) }, + { "Grey5", RGB_(0xd, 0xd, 0xd) }, + { "Grey50", RGB_(0x7f, 0x7f, 0x7f) }, + { "Grey51", RGB_(0x82, 0x82, 0x82) }, + { "Grey52", RGB_(0x85, 0x85, 0x85) }, + { "Grey53", RGB_(0x87, 0x87, 0x87) }, + { "Grey54", RGB_(0x8a, 0x8a, 0x8a) }, + { "Grey55", RGB_(0x8c, 0x8c, 0x8c) }, + { "Grey56", RGB_(0x8f, 0x8f, 0x8f) }, + { "Grey57", RGB_(0x91, 0x91, 0x91) }, + { "Grey58", RGB_(0x94, 0x94, 0x94) }, + { "Grey59", RGB_(0x96, 0x96, 0x96) }, + { "Grey6", RGB_(0xf, 0xf, 0xf) }, + { "Grey60", RGB_(0x99, 0x99, 0x99) }, + { "Grey61", RGB_(0x9c, 0x9c, 0x9c) }, + { "Grey62", RGB_(0x9e, 0x9e, 0x9e) }, + { "Grey63", RGB_(0xa1, 0xa1, 0xa1) }, + { "Grey64", RGB_(0xa3, 0xa3, 0xa3) }, + { "Grey65", RGB_(0xa6, 0xa6, 0xa6) }, + { "Grey66", RGB_(0xa8, 0xa8, 0xa8) }, + { "Grey67", RGB_(0xab, 0xab, 0xab) }, + { "Grey68", RGB_(0xad, 0xad, 0xad) }, + { "Grey69", RGB_(0xb0, 0xb0, 0xb0) }, + { "Grey7", RGB_(0x12, 0x12, 0x12) }, + { "Grey70", RGB_(0xb3, 0xb3, 0xb3) }, + { "Grey71", RGB_(0xb5, 0xb5, 0xb5) }, + { "Grey72", RGB_(0xb8, 0xb8, 0xb8) }, + { "Grey73", RGB_(0xba, 0xba, 0xba) }, + { "Grey74", RGB_(0xbd, 0xbd, 0xbd) }, + { "Grey75", RGB_(0xbf, 0xbf, 0xbf) }, + { "Grey76", RGB_(0xc2, 0xc2, 0xc2) }, + { "Grey77", RGB_(0xc4, 0xc4, 0xc4) }, + { "Grey78", RGB_(0xc7, 0xc7, 0xc7) }, + { "Grey79", RGB_(0xc9, 0xc9, 0xc9) }, + { "Grey8", RGB_(0x14, 0x14, 0x14) }, + { "Grey80", RGB_(0xcc, 0xcc, 0xcc) }, + { "Grey81", RGB_(0xcf, 0xcf, 0xcf) }, + { "Grey82", RGB_(0xd1, 0xd1, 0xd1) }, + { "Grey83", RGB_(0xd4, 0xd4, 0xd4) }, + { "Grey84", RGB_(0xd6, 0xd6, 0xd6) }, + { "Grey85", RGB_(0xd9, 0xd9, 0xd9) }, + { "Grey86", RGB_(0xdb, 0xdb, 0xdb) }, + { "Grey87", RGB_(0xde, 0xde, 0xde) }, + { "Grey88", RGB_(0xe0, 0xe0, 0xe0) }, + { "Grey89", RGB_(0xe3, 0xe3, 0xe3) }, + { "Grey9", RGB_(0x17, 0x17, 0x17) }, + { "Grey90", RGB_(0xe5, 0xe5, 0xe5) }, + { "Grey91", RGB_(0xe8, 0xe8, 0xe8) }, + { "Grey92", RGB_(0xeb, 0xeb, 0xeb) }, + { "Grey93", RGB_(0xed, 0xed, 0xed) }, + { "Grey94", RGB_(0xf0, 0xf0, 0xf0) }, + { "Grey95", RGB_(0xf2, 0xf2, 0xf2) }, + { "Grey96", RGB_(0xf5, 0xf5, 0xf5) }, + { "Grey97", RGB_(0xf7, 0xf7, 0xf7) }, + { "Grey98", RGB_(0xfa, 0xfa, 0xfa) }, + { "Grey99", RGB_(0xfc, 0xfc, 0xfc) }, + { "Honeydew", RGB_(0xf0, 0xff, 0xf0) }, + { "Honeydew1", RGB_(0xf0, 0xff, 0xf0) }, + { "Honeydew2", RGB_(0xe0, 0xee, 0xe0) }, + { "Honeydew3", RGB_(0xc1, 0xcd, 0xc1) }, + { "Honeydew4", RGB_(0x83, 0x8b, 0x83) }, + { "HotPink", RGB_(0xff, 0x69, 0xb4) }, + { "HotPink1", RGB_(0xff, 0x6e, 0xb4) }, + { "HotPink2", RGB_(0xee, 0x6a, 0xa7) }, + { "HotPink3", RGB_(0xcd, 0x60, 0x90) }, + { "HotPink4", RGB_(0x8b, 0x3a, 0x62) }, + { "IndianRed", RGB_(0xcd, 0x5c, 0x5c) }, + { "IndianRed1", RGB_(0xff, 0x6a, 0x6a) }, + { "IndianRed2", RGB_(0xee, 0x63, 0x63) }, + { "IndianRed3", RGB_(0xcd, 0x55, 0x55) }, + { "IndianRed4", RGB_(0x8b, 0x3a, 0x3a) }, + { "Indigo", RGB_(0x4b, 0x00, 0x82) }, + { "Ivory", RGB_(0xff, 0xff, 0xf0) }, + { "Ivory1", RGB_(0xff, 0xff, 0xf0) }, + { "Ivory2", RGB_(0xee, 0xee, 0xe0) }, + { "Ivory3", RGB_(0xcd, 0xcd, 0xc1) }, + { "Ivory4", RGB_(0x8b, 0x8b, 0x83) }, + { "Khaki", RGB_(0xf0, 0xe6, 0x8c) }, + { "Khaki1", RGB_(0xff, 0xf6, 0x8f) }, + { "Khaki2", RGB_(0xee, 0xe6, 0x85) }, + { "Khaki3", RGB_(0xcd, 0xc6, 0x73) }, + { "Khaki4", RGB_(0x8b, 0x86, 0x4e) }, + { "Lavender", RGB_(0xe6, 0xe6, 0xfa) }, + { "LavenderBlush", RGB_(0xff, 0xf0, 0xf5) }, + { "LavenderBlush1", RGB_(0xff, 0xf0, 0xf5) }, + { "LavenderBlush2", RGB_(0xee, 0xe0, 0xe5) }, + { "LavenderBlush3", RGB_(0xcd, 0xc1, 0xc5) }, + { "LavenderBlush4", RGB_(0x8b, 0x83, 0x86) }, + { "LawnGreen", RGB_(0x7c, 0xfc, 0x00) }, + { "LemonChiffon", RGB_(0xff, 0xfa, 0xcd) }, + { "LemonChiffon1", RGB_(0xff, 0xfa, 0xcd) }, + { "LemonChiffon2", RGB_(0xee, 0xe9, 0xbf) }, + { "LemonChiffon3", RGB_(0xcd, 0xc9, 0xa5) }, + { "LemonChiffon4", RGB_(0x8b, 0x89, 0x70) }, + { "LightBlue", RGB_(0xad, 0xd8, 0xe6) }, + { "LightBlue1", RGB_(0xbf, 0xef, 0xff) }, + { "LightBlue2", RGB_(0xb2, 0xdf, 0xee) }, + { "LightBlue3", RGB_(0x9a, 0xc0, 0xcd) }, + { "LightBlue4", RGB_(0x68, 0x83, 0x8b) }, + { "LightCoral", RGB_(0xf0, 0x80, 0x80) }, + { "LightCyan", RGB_(0xe0, 0xff, 0xff) }, + { "LightCyan1", RGB_(0xe0, 0xff, 0xff) }, + { "LightCyan2", RGB_(0xd1, 0xee, 0xee) }, + { "LightCyan3", RGB_(0xb4, 0xcd, 0xcd) }, + { "LightCyan4", RGB_(0x7a, 0x8b, 0x8b) }, + { "LightGoldenrod", RGB_(0xee, 0xdd, 0x82) }, + { "LightGoldenrod1", RGB_(0xff, 0xec, 0x8b) }, + { "LightGoldenrod2", RGB_(0xee, 0xdc, 0x82) }, + { "LightGoldenrod3", RGB_(0xcd, 0xbe, 0x70) }, + { "LightGoldenrod4", RGB_(0x8b, 0x81, 0x4c) }, + { "LightGoldenRodYellow", RGB_(0xfa, 0xfa, 0xd2) }, + { "LightGray", RGB_(0xd3, 0xd3, 0xd3) }, + { "LightGreen", RGB_(0x90, 0xee, 0x90) }, + { "LightGrey", RGB_(0xd3, 0xd3, 0xd3) }, + { "LightMagenta", RGB_(0xff, 0xbb, 0xff) }, + { "LightPink", RGB_(0xff, 0xb6, 0xc1) }, + { "LightPink1", RGB_(0xff, 0xae, 0xb9) }, + { "LightPink2", RGB_(0xee, 0xa2, 0xad) }, + { "LightPink3", RGB_(0xcd, 0x8c, 0x95) }, + { "LightPink4", RGB_(0x8b, 0x5f, 0x65) }, + { "LightRed", RGB_(0xff, 0xbb, 0xbb) }, + { "LightSalmon", RGB_(0xff, 0xa0, 0x7a) }, + { "LightSalmon1", RGB_(0xff, 0xa0, 0x7a) }, + { "LightSalmon2", RGB_(0xee, 0x95, 0x72) }, + { "LightSalmon3", RGB_(0xcd, 0x81, 0x62) }, + { "LightSalmon4", RGB_(0x8b, 0x57, 0x42) }, + { "LightSeaGreen", RGB_(0x20, 0xb2, 0xaa) }, + { "LightSkyBlue", RGB_(0x87, 0xce, 0xfa) }, + { "LightSkyBlue1", RGB_(0xb0, 0xe2, 0xff) }, + { "LightSkyBlue2", RGB_(0xa4, 0xd3, 0xee) }, + { "LightSkyBlue3", RGB_(0x8d, 0xb6, 0xcd) }, + { "LightSkyBlue4", RGB_(0x60, 0x7b, 0x8b) }, + { "LightSlateBlue", RGB_(0x84, 0x70, 0xff) }, + { "LightSlateGray", RGB_(0x77, 0x88, 0x99) }, + { "LightSlateGrey", RGB_(0x77, 0x88, 0x99) }, + { "LightSteelBlue", RGB_(0xb0, 0xc4, 0xde) }, + { "LightSteelBlue1", RGB_(0xca, 0xe1, 0xff) }, + { "LightSteelBlue2", RGB_(0xbc, 0xd2, 0xee) }, + { "LightSteelBlue3", RGB_(0xa2, 0xb5, 0xcd) }, + { "LightSteelBlue4", RGB_(0x6e, 0x7b, 0x8b) }, + { "LightYellow", RGB_(0xff, 0xff, 0xe0) }, + { "LightYellow1", RGB_(0xff, 0xff, 0xe0) }, + { "LightYellow2", RGB_(0xee, 0xee, 0xd1) }, + { "LightYellow3", RGB_(0xcd, 0xcd, 0xb4) }, + { "LightYellow4", RGB_(0x8b, 0x8b, 0x7a) }, + { "Lime", RGB_(0x00, 0xff, 0x00) }, + { "LimeGreen", RGB_(0x32, 0xcd, 0x32) }, + { "Linen", RGB_(0xfa, 0xf0, 0xe6) }, + { "Magenta", RGB_(0xff, 0x00, 0xff) }, + { "Magenta1", RGB_(0xff, 0x0, 0xff) }, + { "Magenta2", RGB_(0xee, 0x0, 0xee) }, + { "Magenta3", RGB_(0xcd, 0x0, 0xcd) }, + { "Magenta4", RGB_(0x8b, 0x0, 0x8b) }, + { "Maroon", RGB_(0x80, 0x00, 0x00) }, + { "Maroon1", RGB_(0xff, 0x34, 0xb3) }, + { "Maroon2", RGB_(0xee, 0x30, 0xa7) }, + { "Maroon3", RGB_(0xcd, 0x29, 0x90) }, + { "Maroon4", RGB_(0x8b, 0x1c, 0x62) }, + { "MediumAquamarine", RGB_(0x66, 0xcd, 0xaa) }, + { "MediumBlue", RGB_(0x00, 0x00, 0xcd) }, + { "MediumOrchid", RGB_(0xba, 0x55, 0xd3) }, + { "MediumOrchid1", RGB_(0xe0, 0x66, 0xff) }, + { "MediumOrchid2", RGB_(0xd1, 0x5f, 0xee) }, + { "MediumOrchid3", RGB_(0xb4, 0x52, 0xcd) }, + { "MediumOrchid4", RGB_(0x7a, 0x37, 0x8b) }, + { "MediumPurple", RGB_(0x93, 0x70, 0xdb) }, + { "MediumPurple1", RGB_(0xab, 0x82, 0xff) }, + { "MediumPurple2", RGB_(0x9f, 0x79, 0xee) }, + { "MediumPurple3", RGB_(0x89, 0x68, 0xcd) }, + { "MediumPurple4", RGB_(0x5d, 0x47, 0x8b) }, + { "MediumSeaGreen", RGB_(0x3c, 0xb3, 0x71) }, + { "MediumSlateBlue", RGB_(0x7b, 0x68, 0xee) }, + { "MediumSpringGreen", RGB_(0x00, 0xfa, 0x9a) }, + { "MediumTurquoise", RGB_(0x48, 0xd1, 0xcc) }, + { "MediumVioletRed", RGB_(0xc7, 0x15, 0x85) }, + { "MidnightBlue", RGB_(0x19, 0x19, 0x70) }, + { "MintCream", RGB_(0xf5, 0xff, 0xfa) }, + { "MistyRose", RGB_(0xff, 0xe4, 0xe1) }, + { "MistyRose1", RGB_(0xff, 0xe4, 0xe1) }, + { "MistyRose2", RGB_(0xee, 0xd5, 0xd2) }, + { "MistyRose3", RGB_(0xcd, 0xb7, 0xb5) }, + { "MistyRose4", RGB_(0x8b, 0x7d, 0x7b) }, + { "Moccasin", RGB_(0xff, 0xe4, 0xb5) }, + { "NavajoWhite", RGB_(0xff, 0xde, 0xad) }, + { "NavajoWhite1", RGB_(0xff, 0xde, 0xad) }, + { "NavajoWhite2", RGB_(0xee, 0xcf, 0xa1) }, + { "NavajoWhite3", RGB_(0xcd, 0xb3, 0x8b) }, + { "NavajoWhite4", RGB_(0x8b, 0x79, 0x5e) }, + { "Navy", RGB_(0x00, 0x00, 0x80) }, + { "NavyBlue", RGB_(0x0, 0x0, 0x80) }, + { "OldLace", RGB_(0xfd, 0xf5, 0xe6) }, + { "Olive", RGB_(0x80, 0x80, 0x00) }, + { "OliveDrab", RGB_(0x6b, 0x8e, 0x23) }, + { "OliveDrab1", RGB_(0xc0, 0xff, 0x3e) }, + { "OliveDrab2", RGB_(0xb3, 0xee, 0x3a) }, + { "OliveDrab3", RGB_(0x9a, 0xcd, 0x32) }, + { "OliveDrab4", RGB_(0x69, 0x8b, 0x22) }, + { "Orange", RGB_(0xff, 0xa5, 0x00) }, + { "Orange1", RGB_(0xff, 0xa5, 0x0) }, + { "Orange2", RGB_(0xee, 0x9a, 0x0) }, + { "Orange3", RGB_(0xcd, 0x85, 0x0) }, + { "Orange4", RGB_(0x8b, 0x5a, 0x0) }, + { "OrangeRed", RGB_(0xff, 0x45, 0x00) }, + { "OrangeRed1", RGB_(0xff, 0x45, 0x0) }, + { "OrangeRed2", RGB_(0xee, 0x40, 0x0) }, + { "OrangeRed3", RGB_(0xcd, 0x37, 0x0) }, + { "OrangeRed4", RGB_(0x8b, 0x25, 0x0) }, + { "Orchid", RGB_(0xda, 0x70, 0xd6) }, + { "Orchid1", RGB_(0xff, 0x83, 0xfa) }, + { "Orchid2", RGB_(0xee, 0x7a, 0xe9) }, + { "Orchid3", RGB_(0xcd, 0x69, 0xc9) }, + { "Orchid4", RGB_(0x8b, 0x47, 0x89) }, + { "PaleGoldenRod", RGB_(0xee, 0xe8, 0xaa) }, + { "PaleGreen", RGB_(0x98, 0xfb, 0x98) }, + { "PaleGreen1", RGB_(0x9a, 0xff, 0x9a) }, + { "PaleGreen2", RGB_(0x90, 0xee, 0x90) }, + { "PaleGreen3", RGB_(0x7c, 0xcd, 0x7c) }, + { "PaleGreen4", RGB_(0x54, 0x8b, 0x54) }, + { "PaleTurquoise", RGB_(0xaf, 0xee, 0xee) }, + { "PaleTurquoise1", RGB_(0xbb, 0xff, 0xff) }, + { "PaleTurquoise2", RGB_(0xae, 0xee, 0xee) }, + { "PaleTurquoise3", RGB_(0x96, 0xcd, 0xcd) }, + { "PaleTurquoise4", RGB_(0x66, 0x8b, 0x8b) }, + { "PaleVioletRed", RGB_(0xdb, 0x70, 0x93) }, + { "PaleVioletRed1", RGB_(0xff, 0x82, 0xab) }, + { "PaleVioletRed2", RGB_(0xee, 0x79, 0x9f) }, + { "PaleVioletRed3", RGB_(0xcd, 0x68, 0x89) }, + { "PaleVioletRed4", RGB_(0x8b, 0x47, 0x5d) }, + { "PapayaWhip", RGB_(0xff, 0xef, 0xd5) }, + { "PeachPuff", RGB_(0xff, 0xda, 0xb9) }, + { "PeachPuff1", RGB_(0xff, 0xda, 0xb9) }, + { "PeachPuff2", RGB_(0xee, 0xcb, 0xad) }, + { "PeachPuff3", RGB_(0xcd, 0xaf, 0x95) }, + { "PeachPuff4", RGB_(0x8b, 0x77, 0x65) }, + { "Peru", RGB_(0xcd, 0x85, 0x3f) }, + { "Pink", RGB_(0xff, 0xc0, 0xcb) }, + { "Pink1", RGB_(0xff, 0xb5, 0xc5) }, + { "Pink2", RGB_(0xee, 0xa9, 0xb8) }, + { "Pink3", RGB_(0xcd, 0x91, 0x9e) }, + { "Pink4", RGB_(0x8b, 0x63, 0x6c) }, + { "Plum", RGB_(0xdd, 0xa0, 0xdd) }, + { "Plum1", RGB_(0xff, 0xbb, 0xff) }, + { "Plum2", RGB_(0xee, 0xae, 0xee) }, + { "Plum3", RGB_(0xcd, 0x96, 0xcd) }, + { "Plum4", RGB_(0x8b, 0x66, 0x8b) }, + { "PowderBlue", RGB_(0xb0, 0xe0, 0xe6) }, + { "Purple", RGB_(0x80, 0x00, 0x80) }, + { "Purple1", RGB_(0x9b, 0x30, 0xff) }, + { "Purple2", RGB_(0x91, 0x2c, 0xee) }, + { "Purple3", RGB_(0x7d, 0x26, 0xcd) }, + { "Purple4", RGB_(0x55, 0x1a, 0x8b) }, + { "RebeccaPurple", RGB_(0x66, 0x33, 0x99) }, + { "Red", RGB_(0xff, 0x00, 0x00) }, + { "Red1", RGB_(0xff, 0x0, 0x0) }, + { "Red2", RGB_(0xee, 0x0, 0x0) }, + { "Red3", RGB_(0xcd, 0x0, 0x0) }, + { "Red4", RGB_(0x8b, 0x0, 0x0) }, + { "RosyBrown", RGB_(0xbc, 0x8f, 0x8f) }, + { "RosyBrown1", RGB_(0xff, 0xc1, 0xc1) }, + { "RosyBrown2", RGB_(0xee, 0xb4, 0xb4) }, + { "RosyBrown3", RGB_(0xcd, 0x9b, 0x9b) }, + { "RosyBrown4", RGB_(0x8b, 0x69, 0x69) }, + { "RoyalBlue", RGB_(0x41, 0x69, 0xe1) }, + { "RoyalBlue1", RGB_(0x48, 0x76, 0xff) }, + { "RoyalBlue2", RGB_(0x43, 0x6e, 0xee) }, + { "RoyalBlue3", RGB_(0x3a, 0x5f, 0xcd) }, + { "RoyalBlue4", RGB_(0x27, 0x40, 0x8b) }, + { "SaddleBrown", RGB_(0x8b, 0x45, 0x13) }, + { "Salmon", RGB_(0xfa, 0x80, 0x72) }, + { "Salmon1", RGB_(0xff, 0x8c, 0x69) }, + { "Salmon2", RGB_(0xee, 0x82, 0x62) }, + { "Salmon3", RGB_(0xcd, 0x70, 0x54) }, + { "Salmon4", RGB_(0x8b, 0x4c, 0x39) }, + { "SandyBrown", RGB_(0xf4, 0xa4, 0x60) }, + { "SeaGreen", RGB_(0x2e, 0x8b, 0x57) }, + { "SeaGreen1", RGB_(0x54, 0xff, 0x9f) }, + { "SeaGreen2", RGB_(0x4e, 0xee, 0x94) }, + { "SeaGreen3", RGB_(0x43, 0xcd, 0x80) }, + { "SeaGreen4", RGB_(0x2e, 0x8b, 0x57) }, + { "SeaShell", RGB_(0xff, 0xf5, 0xee) }, + { "Seashell1", RGB_(0xff, 0xf5, 0xee) }, + { "Seashell2", RGB_(0xee, 0xe5, 0xde) }, + { "Seashell3", RGB_(0xcd, 0xc5, 0xbf) }, + { "Seashell4", RGB_(0x8b, 0x86, 0x82) }, + { "Sienna", RGB_(0xa0, 0x52, 0x2d) }, + { "Sienna1", RGB_(0xff, 0x82, 0x47) }, + { "Sienna2", RGB_(0xee, 0x79, 0x42) }, + { "Sienna3", RGB_(0xcd, 0x68, 0x39) }, + { "Sienna4", RGB_(0x8b, 0x47, 0x26) }, + { "Silver", RGB_(0xc0, 0xc0, 0xc0) }, + { "SkyBlue", RGB_(0x87, 0xce, 0xeb) }, + { "SkyBlue1", RGB_(0x87, 0xce, 0xff) }, + { "SkyBlue2", RGB_(0x7e, 0xc0, 0xee) }, + { "SkyBlue3", RGB_(0x6c, 0xa6, 0xcd) }, + { "SkyBlue4", RGB_(0x4a, 0x70, 0x8b) }, + { "SlateBlue", RGB_(0x6a, 0x5a, 0xcd) }, + { "SlateBlue1", RGB_(0x83, 0x6f, 0xff) }, + { "SlateBlue2", RGB_(0x7a, 0x67, 0xee) }, + { "SlateBlue3", RGB_(0x69, 0x59, 0xcd) }, + { "SlateBlue4", RGB_(0x47, 0x3c, 0x8b) }, + { "SlateGray", RGB_(0x70, 0x80, 0x90) }, + { "SlateGray1", RGB_(0xc6, 0xe2, 0xff) }, + { "SlateGray2", RGB_(0xb9, 0xd3, 0xee) }, + { "SlateGray3", RGB_(0x9f, 0xb6, 0xcd) }, + { "SlateGray4", RGB_(0x6c, 0x7b, 0x8b) }, + { "SlateGrey", RGB_(0x70, 0x80, 0x90) }, + { "Snow", RGB_(0xff, 0xfa, 0xfa) }, + { "Snow1", RGB_(0xff, 0xfa, 0xfa) }, + { "Snow2", RGB_(0xee, 0xe9, 0xe9) }, + { "Snow3", RGB_(0xcd, 0xc9, 0xc9) }, + { "Snow4", RGB_(0x8b, 0x89, 0x89) }, + { "SpringGreen", RGB_(0x00, 0xff, 0x7f) }, + { "SpringGreen1", RGB_(0x0, 0xff, 0x7f) }, + { "SpringGreen2", RGB_(0x0, 0xee, 0x76) }, + { "SpringGreen3", RGB_(0x0, 0xcd, 0x66) }, + { "SpringGreen4", RGB_(0x0, 0x8b, 0x45) }, + { "SteelBlue", RGB_(0x46, 0x82, 0xb4) }, + { "SteelBlue1", RGB_(0x63, 0xb8, 0xff) }, + { "SteelBlue2", RGB_(0x5c, 0xac, 0xee) }, + { "SteelBlue3", RGB_(0x4f, 0x94, 0xcd) }, + { "SteelBlue4", RGB_(0x36, 0x64, 0x8b) }, + { "Tan", RGB_(0xd2, 0xb4, 0x8c) }, + { "Tan1", RGB_(0xff, 0xa5, 0x4f) }, + { "Tan2", RGB_(0xee, 0x9a, 0x49) }, + { "Tan3", RGB_(0xcd, 0x85, 0x3f) }, + { "Tan4", RGB_(0x8b, 0x5a, 0x2b) }, + { "Teal", RGB_(0x00, 0x80, 0x80) }, + { "Thistle", RGB_(0xd8, 0xbf, 0xd8) }, + { "Thistle1", RGB_(0xff, 0xe1, 0xff) }, + { "Thistle2", RGB_(0xee, 0xd2, 0xee) }, + { "Thistle3", RGB_(0xcd, 0xb5, 0xcd) }, + { "Thistle4", RGB_(0x8b, 0x7b, 0x8b) }, + { "Tomato", RGB_(0xff, 0x63, 0x47) }, + { "Tomato1", RGB_(0xff, 0x63, 0x47) }, + { "Tomato2", RGB_(0xee, 0x5c, 0x42) }, + { "Tomato3", RGB_(0xcd, 0x4f, 0x39) }, + { "Tomato4", RGB_(0x8b, 0x36, 0x26) }, + { "Turquoise", RGB_(0x40, 0xe0, 0xd0) }, + { "Turquoise1", RGB_(0x0, 0xf5, 0xff) }, + { "Turquoise2", RGB_(0x0, 0xe5, 0xee) }, + { "Turquoise3", RGB_(0x0, 0xc5, 0xcd) }, + { "Turquoise4", RGB_(0x0, 0x86, 0x8b) }, + { "Violet", RGB_(0xee, 0x82, 0xee) }, + { "VioletRed", RGB_(0xd0, 0x20, 0x90) }, + { "VioletRed1", RGB_(0xff, 0x3e, 0x96) }, + { "VioletRed2", RGB_(0xee, 0x3a, 0x8c) }, + { "VioletRed3", RGB_(0xcd, 0x32, 0x78) }, + { "VioletRed4", RGB_(0x8b, 0x22, 0x52) }, + { "WebGray", RGB_(0x80, 0x80, 0x80) }, + { "WebGreen", RGB_(0x0, 0x80, 0x0) }, + { "WebGrey", RGB_(0x80, 0x80, 0x80) }, + { "WebMaroon", RGB_(0x80, 0x0, 0x0) }, + { "WebPurple", RGB_(0x80, 0x0, 0x80) }, + { "Wheat", RGB_(0xf5, 0xde, 0xb3) }, + { "Wheat1", RGB_(0xff, 0xe7, 0xba) }, + { "Wheat2", RGB_(0xee, 0xd8, 0xae) }, + { "Wheat3", RGB_(0xcd, 0xba, 0x96) }, + { "Wheat4", RGB_(0x8b, 0x7e, 0x66) }, + { "White", RGB_(0xff, 0xff, 0xff) }, + { "WhiteSmoke", RGB_(0xf5, 0xf5, 0xf5) }, + { "X11Gray", RGB_(0xbe, 0xbe, 0xbe) }, + { "X11Green", RGB_(0x0, 0xff, 0x0) }, + { "X11Grey", RGB_(0xbe, 0xbe, 0xbe) }, + { "X11Maroon", RGB_(0xb0, 0x30, 0x60) }, + { "X11Purple", RGB_(0xa0, 0x20, 0xf0) }, + { "Yellow", RGB_(0xff, 0xff, 0x00) }, + { "Yellow1", RGB_(0xff, 0xff, 0x0) }, + { "Yellow2", RGB_(0xee, 0xee, 0x0) }, + { "Yellow3", RGB_(0xcd, 0xcd, 0x0) }, + { "Yellow4", RGB_(0x8b, 0x8b, 0x0) }, + { "YellowGreen", RGB_(0x9a, 0xcd, 0x32) }, + { NULL, 0 }, +}; + +/// Translate to RgbValue if \p name is an hex value (e.g. #XXXXXX), +/// else look into color_name_table to translate a color name to its +/// hex value +/// +/// @param[in] name string value to convert to RGB +/// return the hex value or -1 if could not find a correct value +RgbValue name_to_color(const char *name) +{ + if (name[0] == '#' && isxdigit(name[1]) && isxdigit(name[2]) + && isxdigit(name[3]) && isxdigit(name[4]) && isxdigit(name[5]) + && isxdigit(name[6]) && name[7] == NUL) { + // rgb hex string + return (RgbValue)strtol((char *)(name + 1), NULL, 16); + } else if (!STRICMP(name, "bg") || !STRICMP(name, "background")) { + return normal_bg; + } else if (!STRICMP(name, "fg") || !STRICMP(name, "foreground")) { + return normal_fg; + } + + for (int i = 0; color_name_table[i].name != NULL; i++) { + if (!STRICMP(name, color_name_table[i].name)) { + return color_name_table[i].color; + } + } + + 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); +} diff --git a/src/nvim/highlight_group.h b/src/nvim/highlight_group.h new file mode 100644 index 0000000000..325113a4ab --- /dev/null +++ b/src/nvim/highlight_group.h @@ -0,0 +1,19 @@ +#ifndef NVIM_HIGHLIGHT_GROUP_H +#define NVIM_HIGHLIGHT_GROUP_H + +#include "nvim/types.h" +#include "nvim/eval.h" + +#define MAX_HL_ID 20000 // maximum value for a highlight ID. + +typedef struct { + char *name; + RgbValue color; +} color_name_table_T; +extern color_name_table_T color_name_table[]; + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "highlight_group.h.generated.h" +#endif + +#endif // NVIM_HIGHLIGHT_GROUP_H diff --git a/src/nvim/if_cscope.c b/src/nvim/if_cscope.c index 6f02ebfb48..9ca01137cf 100644 --- a/src/nvim/if_cscope.c +++ b/src/nvim/if_cscope.c @@ -223,9 +223,9 @@ void ex_cstag(exarg_T *eap) switch (p_csto) { case 0: if (cs_check_for_connections()) { - ret = cs_find_common("g", (char *)(eap->arg), eap->forceit, FALSE, - FALSE, *eap->cmdlinep); - if (ret == FALSE) { + ret = cs_find_common("g", (char *)(eap->arg), eap->forceit, false, + false, *eap->cmdlinep); + if (ret == false) { cs_free_tags(); if (msg_col) { msg_putchar('\n'); @@ -249,16 +249,16 @@ void ex_cstag(exarg_T *eap) if (cs_check_for_connections()) { ret = cs_find_common("g", (char *)(eap->arg), eap->forceit, - FALSE, FALSE, *eap->cmdlinep); - if (ret == FALSE) { + false, false, *eap->cmdlinep); + if (ret == false) { cs_free_tags(); } } } } else if (cs_check_for_connections()) { - ret = cs_find_common("g", (char *)(eap->arg), eap->forceit, FALSE, - FALSE, *eap->cmdlinep); - if (ret == FALSE) { + ret = cs_find_common("g", (char *)(eap->arg), eap->forceit, false, + false, *eap->cmdlinep); + if (ret == false) { cs_free_tags(); } } @@ -520,7 +520,7 @@ add_err: } -static int cs_check_for_connections(void) +static bool cs_check_for_connections(void) { return cs_cnt_connections() > 0; } @@ -887,20 +887,20 @@ static int cs_find(exarg_T *eap) { char *opt, *pat; - if (cs_check_for_connections() == FALSE) { + if (cs_check_for_connections() == false) { (void)emsg(_("E567: no cscope connections")); - return FALSE; + return false; } if ((opt = strtok((char *)NULL, (const char *)" ")) == NULL) { cs_usage_msg(Find); - return FALSE; + return false; } pat = opt + strlen(opt) + 1; if (pat >= (char *)eap->arg + eap_arg_len) { cs_usage_msg(Find); - return FALSE; + return false; } /* @@ -919,8 +919,8 @@ static int cs_find(exarg_T *eap) /// Common code for cscope find, shared by cs_find() and ex_cstag(). -static int cs_find_common(char *opt, char *pat, int forceit, int verbose, int 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; @@ -967,7 +967,7 @@ static int cs_find_common(char *opt, char *pat, int forceit, int verbose, int us // next symbol must be + or - if (strchr(CSQF_FLAGS, *qfpos) == NULL) { (void)semsg(_("E469: invalid cscopequickfix flag %c for %c"), *qfpos, *(qfpos - 1)); - return FALSE; + return false; } if (*qfpos != '0' @@ -982,7 +982,7 @@ static int cs_find_common(char *opt, char *pat, int forceit, int verbose, int us // create the actual command to send to cscope cmd = cs_create_cmd(opt, pat); if (cmd == NULL) { - return FALSE; + return false; } nummatches = xmalloc(sizeof(int) * csinfo_size); @@ -1019,7 +1019,7 @@ static int cs_find_common(char *opt, char *pat, int forceit, int verbose, int us (void)semsg(_("E259: no matches found for cscope query %s of %s"), opt, pat); } xfree(nummatches); - return FALSE; + return false; } if (qfpos != NULL && *qfpos != '0') { @@ -1064,7 +1064,7 @@ static int cs_find_common(char *opt, char *pat, int forceit, int verbose, int us os_remove((char *)tmp); xfree(tmp); xfree(nummatches); - return TRUE; + return true; } else { char **matches = NULL, **contexts = NULL; size_t matched = 0; @@ -1073,7 +1073,7 @@ static int cs_find_common(char *opt, char *pat, int forceit, int verbose, int us cs_fill_results(pat, totmatches, nummatches, &matches, &contexts, &matched); xfree(nummatches); if (matches == NULL) { - return FALSE; + return false; } (void)cs_manage_matches(matches, contexts, matched, Store); @@ -1499,12 +1499,13 @@ static void cs_file_results(FILE *f, int *nummatches_a) continue; } - context = xmalloc(strlen(cntx) + 5); + size_t context_len = strlen(cntx) + 5; + context = xmalloc(context_len); if (strcmp(cntx, "<global>") == 0) { - strcpy(context, "<<global>>"); + xstrlcpy(context, "<<global>>", context_len); } else { - sprintf(context, "<<%s>>", cntx); + snprintf(context, context_len, "<<%s>>", cntx); } if (search == NULL) { @@ -1593,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 == '/')) { @@ -1812,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 b/src/nvim/indent.c index f49aff6643..59ba2c92f7 100644 --- a/src/nvim/indent.c +++ b/src/nvim/indent.c @@ -17,7 +17,6 @@ #include "nvim/mark.h" #include "nvim/memline.h" #include "nvim/memory.h" -#include "nvim/misc1.h" #include "nvim/move.h" #include "nvim/option.h" #include "nvim/plines.h" @@ -71,7 +70,7 @@ int get_indent_str(const char_u *ptr, int ts, bool list) { int count = 0; - for (; *ptr; ++ptr) { + for (; *ptr; ptr++) { // Count a tab for what it is worth. if (*ptr == TAB) { if (!list || curwin->w_p_lcs_chars.tab1) { @@ -443,10 +442,9 @@ int get_breakindent_win(win_T *wp, char_u *line) static long *prev_vts = NULL; // Cached vartabs values. int bri = 0; // window width minus window margin space, i.e. what rests for text - const int eff_wwidth = wp->w_width_inner - - ((wp->w_p_nu || wp->w_p_rnu) - && (vim_strchr(p_cpo, CPO_NUMCOL) == NULL) - ? number_width(wp) + 1 : 0); + const int eff_wwidth = wp->w_width_inner - + ((wp->w_p_nu || wp->w_p_rnu) + && (vim_strchr(p_cpo, CPO_NUMCOL) == NULL) ? number_width(wp) + 1 : 0); // used cached indent, unless pointer or 'tabstop' changed if (prev_line != line || prev_ts != wp->w_buffer->b_p_ts @@ -513,7 +511,7 @@ int inindent(int extra) char_u *ptr; colnr_T col; - for (col = 0, ptr = get_cursor_line_ptr(); ascii_iswhite(*ptr); ++col) { + for (col = 0, ptr = get_cursor_line_ptr(); ascii_iswhite(*ptr); col++) { ptr++; } @@ -631,7 +629,7 @@ int get_lisp_indent(void) continue; } - for (that = get_cursor_line_ptr(); *that != NUL; ++that) { + for (that = get_cursor_line_ptr(); *that != NUL; that++) { if (*that == ';') { while (*(that + 1) != NUL) { that++; diff --git a/src/nvim/indent_c.c b/src/nvim/indent_c.c index 3e3e07e9d6..f2ae8079d8 100644 --- a/src/nvim/indent_c.c +++ b/src/nvim/indent_c.c @@ -8,7 +8,6 @@ #include "nvim/vim.h" #include "nvim/ascii.h" -#include "nvim/misc1.h" #include "nvim/charset.h" #include "nvim/cursor.h" #include "nvim/edit.h" @@ -42,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 (;; ) { @@ -56,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; @@ -111,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 (;;) @@ -125,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) { @@ -144,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; @@ -153,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++; @@ -179,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 } @@ -206,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. @@ -219,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; @@ -247,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); @@ -284,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; } @@ -313,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 == '"') { @@ -342,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 @@ -362,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. @@ -381,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) { @@ -425,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()); @@ -461,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 ) { @@ -504,44 +505,55 @@ 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)) == ':' && s[1] != ':'; } -/* - * Recognize a "public/private/protected" scope declaration label. - */ -bool cin_isscopedecl(char_u *s) +/// Recognize a scope declaration label set in 'cinscopedecls'. +bool cin_isscopedecl(const char_u *p) { - int i; + const char_u *s = cin_skipcomment(p); - s = cin_skipcomment(s); - if (STRNCMP(s, "public", 6) == 0) { - i = 6; - } else if (STRNCMP(s, "protected", 9) == 0) { - i = 9; - } else if (STRNCMP(s, "private", 7) == 0) { - i = 7; - } else { - return false; + const size_t cinsd_len = STRLEN(curbuf->b_p_cinsd) + 1; + char_u *cinsd_buf = xmalloc(cinsd_len); + + bool found = false; + + for (char_u *cinsd = curbuf->b_p_cinsd; *cinsd; ) { + const size_t len = copy_option_part(&cinsd, cinsd_buf, cinsd_len, ","); + if (STRNCMP(s, cinsd_buf, len) == 0) { + const char_u *skip = cin_skipcomment(s + len); + if (*skip == ':' && skip[1] != ':') { + found = true; + break; + } + } } - return *(s = cin_skipcomment(s + i)) == ':' && s[1] != ':'; + + xfree(cinsd_buf); + + return found; } // Maximum number of lines to search back for a "namespace" line. #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) { @@ -577,7 +589,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 == ':') { @@ -604,10 +616,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); @@ -626,9 +638,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; @@ -709,8 +721,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; @@ -748,7 +760,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; @@ -759,9 +771,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; @@ -795,7 +807,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] == '/'); } @@ -803,7 +815,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] == '/'; } @@ -818,8 +830,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 ) @@ -868,9 +880,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; @@ -924,11 +936,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] != '\\') @@ -972,12 +983,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); @@ -985,7 +996,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]); } @@ -995,7 +1006,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; @@ -1029,7 +1040,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; @@ -1073,10 +1084,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 @@ -1116,7 +1127,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]); } @@ -1136,10 +1147,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 @@ -1307,10 +1318,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) { @@ -1331,7 +1342,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); @@ -1339,10 +1350,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]))) { @@ -1381,9 +1392,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) { @@ -1413,8 +1424,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; @@ -1429,7 +1440,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; @@ -1526,7 +1537,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; @@ -1635,8 +1646,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 @@ -1648,11 +1659,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. @@ -1798,8 +1809,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 @@ -1903,12 +1914,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 @@ -3599,9 +3623,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 new file mode 100644 index 0000000000..ff6b559710 --- /dev/null +++ b/src/nvim/input.c @@ -0,0 +1,255 @@ +// 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 + +// input.c: high level functions for prompting the user or input +// like yes/no or number prompts. + +#include <inttypes.h> +#include <stdbool.h> + +#include "nvim/func_attr.h" +#include "nvim/getchar.h" +#include "nvim/input.h" +#include "nvim/mbyte.h" +#include "nvim/memory.h" +#include "nvim/mouse.h" +#include "nvim/os/input.h" +#include "nvim/ui.h" +#include "nvim/vim.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "input.c.generated.h" +#endif + +/// Ask for a reply from the user, 'y' or 'n' +/// +/// No other characters are accepted, the message is repeated until a valid +/// reply is entered or <C-c> is hit. +/// +/// @param[in] str Prompt: question to ask user. Is always followed by +/// " (y/n)?". +/// @param[in] direct Determines what function to use to get user input. If +/// true then ui_inchar() will be used, otherwise vgetc(). +/// I.e. when direct is true then characters are obtained +/// directly from the user without buffers involved. +/// +/// @return 'y' or 'n'. Last is also what will be returned in case of interrupt. +int ask_yesno(const char *const str, const bool direct) +{ + const int save_State = State; + + no_wait_return++; + State = CONFIRM; // Mouse behaves like with :confirm. + setmouse(); // Disable mouse in xterm. + no_mapping++; + + int r = ' '; + while (r != 'y' && r != 'n') { + // Same highlighting as for wait_return. + smsg_attr(HL_ATTR(HLF_R), "%s (y/n)?", str); + if (direct) { + r = get_keystroke(NULL); + } else { + r = plain_vgetc(); + } + if (r == Ctrl_C || r == ESC) { + r = 'n'; + } + msg_putchar(r); // Show what you typed. + ui_flush(); + } + no_wait_return--; + State = save_State; + setmouse(); + no_mapping--; + + return r; +} + +/// Get a key stroke directly from the user. +/// +/// Ignores mouse clicks and scrollbar events, except a click for the left +/// button (used at the more prompt). +/// Doesn't use vgetc(), because it syncs undo and eats mapped characters. +/// Disadvantage: typeahead is ignored. +/// Translates the interrupt character for unix to ESC. +int get_keystroke(MultiQueue *events) +{ + char_u *buf = NULL; + int buflen = 150; + int maxlen; + int len = 0; + int n; + int save_mapped_ctrl_c = mapped_ctrl_c; + int waited = 0; + + mapped_ctrl_c = 0; // mappings are not used here + for (;;) { + // flush output before waiting + ui_flush(); + // Leave some room for check_termcode() to insert a key code into (max + // 5 chars plus NUL). And fix_input_buffer() can triple the number of + // bytes. + maxlen = (buflen - 6 - len) / 3; + if (buf == NULL) { + buf = xmalloc((size_t)buflen); + } else if (maxlen < 10) { + // Need some more space. This might happen when receiving a long + // escape sequence. + buflen += 100; + buf = xrealloc(buf, (size_t)buflen); + maxlen = (buflen - 6 - len) / 3; + } + + // First time: blocking wait. Second time: wait up to 100ms for a + // terminal code to complete. + n = os_inchar(buf + len, maxlen, len == 0 ? -1L : 100L, 0, events); + if (n > 0) { + // Replace zero and K_SPECIAL by a special key code. + n = fix_input_buffer(buf + len, n); + len += n; + waited = 0; + } else if (len > 0) { + waited++; // keep track of the waiting time + } + if (n > 0) { // found a termcode: adjust length + len = n; + } + if (len == 0) { // nothing typed yet + continue; + } + + // Handle modifier and/or special key code. + n = buf[0]; + if (n == K_SPECIAL) { + n = TO_SPECIAL(buf[1], buf[2]); + if (buf[1] == KS_MODIFIER + || n == K_IGNORE + || (is_mouse_key(n) && n != K_LEFTMOUSE)) { + if (buf[1] == KS_MODIFIER) { + mod_mask = buf[2]; + } + len -= 3; + if (len > 0) { + memmove(buf, buf + 3, (size_t)len); + } + continue; + } + break; + } + if (MB_BYTE2LEN(n) > len) { + // more bytes to get. + continue; + } + buf[len >= buflen ? buflen - 1 : len] = NUL; + n = utf_ptr2char(buf); + break; + } + xfree(buf); + + mapped_ctrl_c = save_mapped_ctrl_c; + return n; +} + +/// Get a number from the user. +/// When "mouse_used" is not NULL allow using the mouse. +/// +/// @param colon allow colon to abort +int get_number(int colon, int *mouse_used) +{ + int n = 0; + int c; + int typed = 0; + + if (mouse_used != NULL) { + *mouse_used = false; + } + + // When not printing messages, the user won't know what to type, return a + // zero (as if CR was hit). + if (msg_silent != 0) { + return 0; + } + + no_mapping++; + for (;;) { + ui_cursor_goto(msg_row, msg_col); + c = safe_vgetc(); + if (ascii_isdigit(c)) { + n = n * 10 + c - '0'; + msg_putchar(c); + typed++; + } else if (c == K_DEL || c == K_KDEL || c == K_BS || c == Ctrl_H) { + if (typed > 0) { + msg_puts("\b \b"); + typed--; + } + n /= 10; + } else if (mouse_used != NULL && c == K_LEFTMOUSE) { + *mouse_used = true; + n = mouse_row + 1; + break; + } else if (n == 0 && c == ':' && colon) { + stuffcharReadbuff(':'); + if (!exmode_active) { + cmdline_row = msg_row; + } + skip_redraw = true; // skip redraw once + do_redraw = false; + break; + } else if (c == Ctrl_C || c == ESC || c == 'q') { + n = 0; + break; + } else if (c == CAR || c == NL) { + break; + } + } + no_mapping--; + return n; +} + +/// Ask the user to enter a number. +/// +/// When "mouse_used" is not NULL allow using the mouse and in that case return +/// the line number. +int prompt_for_number(int *mouse_used) +{ + int i; + int save_cmdline_row; + int save_State; + + // When using ":silent" assume that <CR> was entered. + if (mouse_used != NULL) { + msg_puts(_("Type number and <Enter> or click with the mouse " + "(q or empty cancels): ")); + } else { + msg_puts(_("Type number and <Enter> (q or empty cancels): ")); + } + + // Set the state such that text can be selected/copied/pasted and we still + // get mouse events. + save_cmdline_row = cmdline_row; + cmdline_row = 0; + save_State = State; + State = ASKMORE; // prevents a screen update when using a timer + // May show different mouse shape. + setmouse(); + + i = get_number(true, mouse_used); + if (KeyTyped) { + // don't call wait_return() now + if (msg_row > 0) { + cmdline_row = msg_row - 1; + } + need_wait_return = false; + msg_didany = false; + msg_didout = false; + } else { + cmdline_row = save_cmdline_row; + } + State = save_State; + // May need to restore mouse shape. + setmouse(); + + return i; +} diff --git a/src/nvim/input.h b/src/nvim/input.h new file mode 100644 index 0000000000..7975f21215 --- /dev/null +++ b/src/nvim/input.h @@ -0,0 +1,9 @@ +#ifndef NVIM_INPUT_H +#define NVIM_INPUT_H + +#include "nvim/vim.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "input.h.generated.h" +#endif +#endif // NVIM_INPUT_H diff --git a/src/nvim/keymap.c b/src/nvim/keymap.c index abf016b832..1e305528ba 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" }, @@ -222,6 +221,35 @@ static const struct key_name_entry { { K_F35, "F35" }, { K_F36, "F36" }, { K_F37, "F37" }, + { K_F38, "F38" }, + { K_F39, "F39" }, + { K_F40, "F40" }, + + { K_F41, "F41" }, + { K_F42, "F42" }, + { K_F43, "F43" }, + { K_F44, "F44" }, + { K_F45, "F45" }, + { K_F46, "F46" }, + { K_F47, "F47" }, + { K_F48, "F48" }, + { K_F49, "F49" }, + { K_F50, "F50" }, + + { K_F51, "F51" }, + { K_F52, "F52" }, + { K_F53, "F53" }, + { K_F54, "F54" }, + { K_F55, "F55" }, + { K_F56, "F56" }, + { K_F57, "F57" }, + { K_F58, "F58" }, + { K_F59, "F59" }, + { K_F60, "F60" }, + + { K_F61, "F61" }, + { K_F62, "F62" }, + { K_F63, "F63" }, { K_XF1, "xF1" }, { K_XF2, "xF2" }, @@ -745,12 +773,15 @@ static int extract_modifiers(int key, int *modp) modifiers &= ~MOD_MASK_SHIFT; } } - if ((modifiers & MOD_MASK_CTRL) - && ((key >= '?' && key <= '_') || ASCII_ISALPHA(key))) { - key = Ctrl_chr(key); - modifiers &= ~MOD_MASK_CTRL; - if (key == 0) { // <C-@> is <Nul> - key = K_ZERO; + if ((modifiers & MOD_MASK_CTRL) && ((key >= '?' && key <= '_') || ASCII_ISALPHA(key))) { + key = TOUPPER_ASC(key); + int new_key = Ctrl_chr(key); + if (new_key != TAB && new_key != CAR && new_key != ESC) { + key = new_key; + modifiers &= ~MOD_MASK_CTRL; + if (key == 0) { // <C-@> is <Nul> + key = K_ZERO; + } } } @@ -964,7 +995,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..9dff8ba333 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 }; @@ -326,6 +325,35 @@ enum key_extra { #define K_F35 TERMCAP2KEY('F', 'P') #define K_F36 TERMCAP2KEY('F', 'Q') #define K_F37 TERMCAP2KEY('F', 'R') +#define K_F38 TERMCAP2KEY('F', 'S') +#define K_F39 TERMCAP2KEY('F', 'T') +#define K_F40 TERMCAP2KEY('F', 'U') + +#define K_F41 TERMCAP2KEY('F', 'V') +#define K_F42 TERMCAP2KEY('F', 'W') +#define K_F43 TERMCAP2KEY('F', 'X') +#define K_F44 TERMCAP2KEY('F', 'Y') +#define K_F45 TERMCAP2KEY('F', 'Z') +#define K_F46 TERMCAP2KEY('F', 'a') +#define K_F47 TERMCAP2KEY('F', 'b') +#define K_F48 TERMCAP2KEY('F', 'c') +#define K_F49 TERMCAP2KEY('F', 'd') +#define K_F50 TERMCAP2KEY('F', 'e') + +#define K_F51 TERMCAP2KEY('F', 'f') +#define K_F52 TERMCAP2KEY('F', 'g') +#define K_F53 TERMCAP2KEY('F', 'h') +#define K_F54 TERMCAP2KEY('F', 'i') +#define K_F55 TERMCAP2KEY('F', 'j') +#define K_F56 TERMCAP2KEY('F', 'k') +#define K_F57 TERMCAP2KEY('F', 'l') +#define K_F58 TERMCAP2KEY('F', 'm') +#define K_F59 TERMCAP2KEY('F', 'n') +#define K_F60 TERMCAP2KEY('F', 'o') + +#define K_F61 TERMCAP2KEY('F', 'p') +#define K_F62 TERMCAP2KEY('F', 'q') +#define K_F63 TERMCAP2KEY('F', 'r') // extra set of shifted function keys F1-F4, for vt100 compatible xterm #define K_S_XF1 TERMCAP2KEY(KS_EXTRA, KE_S_XF1) @@ -434,7 +462,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 +470,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/lib/khash.h b/src/nvim/lib/khash.h index 8cfeb03cc4..e81db43038 100644 --- a/src/nvim/lib/khash.h +++ b/src/nvim/lib/khash.h @@ -666,7 +666,7 @@ static kh_inline khint_t __ac_Wang_hash(khint_t key) } \ } -// More conenient interfaces +// More convenient interfaces /*! @function @abstract Instantiate a hash set containing integer keys diff --git a/src/nvim/log.c b/src/nvim/log.c index 5539e3d6c5..7d50ecf69e 100644 --- a/src/nvim/log.c +++ b/src/nvim/log.c @@ -282,6 +282,7 @@ static bool do_log_to_file(FILE *log_file, int log_level, const char *context, static bool v_do_log_to_file(FILE *log_file, int log_level, const char *context, const char *func_name, int line_num, bool eol, const char *fmt, va_list args) + FUNC_ATTR_PRINTF(7, 0) { static const char *log_levels[] = { [DEBUG_LOG_LEVEL] = "DEBUG", diff --git a/src/nvim/lua/converter.c b/src/nvim/lua/converter.c index 9f2372f831..ef49a03660 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; } @@ -286,9 +286,7 @@ bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv) break; case LUA_TBOOLEAN: cur.tv->v_type = VAR_BOOL; - cur.tv->vval.v_bool = (lua_toboolean(lstate, -1) - ? kBoolVarTrue - : kBoolVarFalse); + cur.tv->vval.v_bool = (lua_toboolean(lstate, -1) ? kBoolVarTrue : kBoolVarFalse); break; case LUA_TSTRING: { size_t len; @@ -316,7 +314,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 +387,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 +399,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 +443,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) @@ -478,7 +476,12 @@ static bool typval_conv_special = false; #define TYPVAL_ENCODE_CONV_FUNC_START(tv, fun) \ do { \ - TYPVAL_ENCODE_CONV_NIL(tv); \ + ufunc_T *fp = find_func(fun); \ + if (fp != NULL && fp->uf_cb == nlua_CFunction_func_call) { \ + nlua_pushref(lstate, ((LuaCFunctionState *)fp->uf_cb_state)->lua_callable.func_ref); \ + } else { \ + TYPVAL_ENCODE_CONV_NIL(tv); \ + } \ goto typval_encode_stop_converting_one_item; \ } while (0) @@ -495,7 +498,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) @@ -551,8 +554,8 @@ static bool typval_conv_special = false; const MPConvStackVal mpval = kv_A(*mpstack, backref - 1); \ if (mpval.type == conv_type) { \ if (conv_type == kMPConvDict \ - ? (void *)mpval.data.d.dict == (void *)(val) \ - : (void *)mpval.data.l.list == (void *)(val)) { \ + ? (void *)mpval.data.d.dict == (void *)(val) \ + : (void *)mpval.data.l.list == (void *)(val)) { \ lua_pushvalue(lstate, \ -((int)((kv_size(*mpstack) - backref + 1) * 2))); \ break; \ @@ -726,7 +729,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 +777,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: { @@ -782,10 +785,10 @@ void nlua_push_Object(lua_State *lstate, const Object obj, bool special) break; } #define ADD_TYPE(type, data_key) \ -case kObjectType##type: { \ - nlua_push_##type(lstate, obj.data.data_key, special); \ - break; \ -} + case kObjectType##type: { \ + nlua_push_##type(lstate, obj.data.data_key, special); \ + break; \ + } ADD_TYPE(Boolean, boolean) ADD_TYPE(Integer, integer) ADD_TYPE(Float, floating) @@ -794,10 +797,10 @@ case kObjectType##type: { \ ADD_TYPE(Dictionary, dictionary) #undef ADD_TYPE #define ADD_REMOTE_TYPE(type) \ -case kObjectType##type: { \ - nlua_push_##type(lstate, (type)obj.data.integer, special); \ - break; \ -} + case kObjectType##type: { \ + nlua_push_##type(lstate, (type)obj.data.integer, special); \ + break; \ + } ADD_REMOTE_TYPE(Buffer) ADD_REMOTE_TYPE(Window) ADD_REMOTE_TYPE(Tabpage) @@ -1125,10 +1128,7 @@ Object nlua_pop_Object(lua_State *const lstate, bool ref, Error *const err) case LUA_TSTRING: { size_t len; const char *s = lua_tolstring(lstate, -1, &len); - *cur.obj = STRING_OBJ(((String) { - .data = xmemdupz(s, len), - .size = len, - })); + *cur.obj = STRING_OBJ(((String) { .data = xmemdupz(s, len), .size = len })); break; } case LUA_TNUMBER: { @@ -1146,11 +1146,7 @@ Object nlua_pop_Object(lua_State *const lstate, bool ref, Error *const err) switch (table_props.type) { case kObjectTypeArray: - *cur.obj = ARRAY_OBJ(((Array) { - .items = NULL, - .size = 0, - .capacity = 0, - })); + *cur.obj = ARRAY_OBJ(((Array) { .items = NULL, .size = 0, .capacity = 0 })); if (table_props.maxidx != 0) { cur.obj->data.array.items = xcalloc(table_props.maxidx, @@ -1161,11 +1157,7 @@ Object nlua_pop_Object(lua_State *const lstate, bool ref, Error *const err) } break; case kObjectTypeDictionary: - *cur.obj = DICTIONARY_OBJ(((Dictionary) { - .items = NULL, - .size = 0, - .capacity = 0, - })); + *cur.obj = DICTIONARY_OBJ(((Dictionary) { .items = NULL, .size = 0, .capacity = 0 })); if (table_props.string_keys_num != 0) { cur.obj->data.dictionary.items = xcalloc(table_props.string_keys_num, @@ -1191,14 +1183,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 +1224,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 +1234,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 6a8b70a158..81396f1715 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" @@ -14,10 +15,12 @@ #include "nvim/buffer_defs.h" #include "nvim/change.h" #include "nvim/cursor.h" +#include "nvim/eval/typval.h" #include "nvim/eval/userfunc.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" @@ -31,7 +34,6 @@ #include "nvim/map.h" #include "nvim/memline.h" #include "nvim/message.h" -#include "nvim/misc1.h" #include "nvim/msgpack_rpc/channel.h" #include "nvim/os/os.h" #include "nvim/screen.h" @@ -44,11 +46,19 @@ 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; } LuaError; +typedef struct { + char *name; + const uint8_t *data; + size_t size; +} ModuleDef; + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "lua/executor.c.generated.h" # include "lua/vim_module.generated.h" @@ -64,11 +74,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. @@ -77,7 +92,22 @@ static void nlua_error(lua_State *const lstate, const char *const msg) FUNC_ATTR_NONNULL_ALL { size_t len; - const char *const str = lua_tolstring(lstate, -1, &len); + const char *str = NULL; + + if (luaL_getmetafield(lstate, -1, "__tostring")) { + if (lua_isfunction(lstate, -1) && luaL_callmeta(lstate, -2, "__tostring")) { + // call __tostring, convert the result and pop result. + str = lua_tolstring(lstate, -1, &len); + lua_pop(lstate, 1); + } + // pop __tostring. + lua_pop(lstate, 1); + } + + if (!str) { + // defer to lua default conversion, this will render tables as [NULL]. + str = lua_tolstring(lstate, -1, &len); + } msg_ext_set_kind("lua_error"); semsg_multiline(msg, (int)len, str); @@ -121,8 +151,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); } @@ -147,7 +190,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 @@ -161,12 +204,109 @@ 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")); } @@ -183,7 +323,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); @@ -301,10 +441,154 @@ 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 int nlua_module_preloader(lua_State *lstate) +{ + size_t i = (size_t)lua_tointeger(lstate, lua_upvalueindex(1)); + ModuleDef def = builtin_modules[i]; + char name[256]; + name[0] = '@'; + size_t off = xstrlcpy(name+1, def.name, (sizeof name) - 2); + strchrsub(name+1, '.', '/'); + xstrlcpy(name+1+off, ".lua", (sizeof name)-2-off); + + if (luaL_loadbuffer(lstate, (const char *)def.data, def.size - 1, name)) { + return lua_error(lstate); + } + + lua_call(lstate, 0, 1); // propagates error to caller + return 1; +} + +static bool nlua_init_packages(lua_State *lstate) + FUNC_ATTR_NONNULL_ALL +{ + // put builtin packages in preload + lua_getglobal(lstate, "package"); // [package] + lua_getfield(lstate, -1, "preload"); // [package, preload] + for (size_t i = 0; i < ARRAY_SIZE(builtin_modules); i++) { + ModuleDef def = builtin_modules[i]; + lua_pushinteger(lstate, (long)i); // [package, preload, i] + lua_pushcclosure(lstate, nlua_module_preloader, 1); // [package, preload, cclosure] + lua_setfield(lstate, -2, def.name); // [package, preload] + + if (nlua_disable_preload && strequal(def.name, "vim.inspect")) { + break; + } + } + + lua_pop(lstate, 2); // [] + + lua_getglobal(lstate, "require"); + lua_pushstring(lstate, "vim._init_packages"); + if (nlua_pcall(lstate, 1, 0)) { + mch_errmsg(lua_tostring(lstate, -1)); + mch_errmsg("\n"); + return false; + } + + return true; +} + /// Initialize lua interpreter state /// /// Called by lua interpreter itself to initialize state. -static int nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL +static bool nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL { // print lua_pushcfunction(lstate, &nlua_print); @@ -361,108 +645,20 @@ 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, strlen(code), "@vim/shared.lua") - || nlua_pcall(lstate, 0, 0)) { - nlua_error(lstate, _("E5106: Error while creating shared module: %.*s")); - return 1; - } - } - - { - lua_getglobal(lstate, "package"); // [package] - lua_getfield(lstate, -1, "loaded"); // [package, loaded] - - const char *code = (char *)&inspect_module[0]; - if (luaL_loadbuffer(lstate, code, strlen(code), "@vim/inspect.lua") - || nlua_pcall(lstate, 0, 1)) { - nlua_error(lstate, _("E5106: Error while creating inspect module: %.*s")); - return 1; - } - // [package, loaded, inspect] - lua_setfield(lstate, -2, "vim.inspect"); // [package, loaded] - - code = (char *)&lua_F_module[0]; - if (luaL_loadbuffer(lstate, code, strlen(code), "@vim/F.lua") - || nlua_pcall(lstate, 0, 1)) { - nlua_error(lstate, _("E5106: Error while creating vim.F module: %.*s")); - return 1; - } - // [package, loaded, module] - lua_setfield(lstate, -2, "vim.F"); // [package, loaded] - - lua_pop(lstate, 2); // [] - } - - { - const char *code = (char *)&vim_module[0]; - if (luaL_loadbuffer(lstate, code, strlen(code), "@vim.lua") - || nlua_pcall(lstate, 0, 0)) { - nlua_error(lstate, _("E5106: Error while creating vim module: %.*s")); - return 1; - } - } - - { - lua_getglobal(lstate, "package"); // [package] - lua_getfield(lstate, -1, "loaded"); // [package, loaded] - - const char *code = (char *)&lua_meta_module[0]; - if (luaL_loadbuffer(lstate, code, strlen(code), "@vim/_meta.lua") - || nlua_pcall(lstate, 0, 1)) { - nlua_error(lstate, _("E5106: Error while creating vim._meta module: %.*s")); - return 1; - } - // [package, loaded, module] - lua_setfield(lstate, -2, "vim._meta"); // [package, loaded] - - lua_pop(lstate, 2); // [] + if (!nlua_init_packages(lstate)) { + return false; } - return 0; + return true; } /// Initialize global lua interpreter @@ -479,15 +675,66 @@ void nlua_init(void) lua_State *lstate = luaL_newstate(); if (lstate == NULL) { - emsg(_("E970: Failed to initialize lua interpreter")); - preserve_exit(); + mch_errmsg(_("E970: Failed to initialize lua interpreter\n")); + os_exit(1); } luaL_openlibs(lstate); - nlua_state_init(lstate); + if (!nlua_state_init(lstate)) { + mch_errmsg(_("E970: Failed to initialize builtin lua modules\n")); + os_exit(1); + } + + 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_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_setglobal(lstate, "vim"); + + nlua_init_packages(lstate); + + lua_getglobal(lstate, "package"); + lua_getfield(lstate, -1, "loaded"); + lua_getglobal(lstate, "vim"); + lua_setfield(lstate, -2, "vim"); + lua_pop(lstate, 2); + + return lstate; +} void nlua_free_all_mem(void) { @@ -495,32 +742,39 @@ 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]; const size_t len = (size_t)(intptr_t)argv[1]-1; // exclude final NUL for (size_t i = 0; i < len;) { + if (got_int) { + break; + } const size_t start = i; while (i < len) { switch (str[i]) { @@ -589,9 +843,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 }); @@ -600,10 +863,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); } @@ -669,7 +934,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); @@ -734,7 +999,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); @@ -793,40 +1058,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 @@ -848,7 +1126,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; } @@ -912,6 +1190,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) { @@ -1032,6 +1328,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) @@ -1094,11 +1403,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); @@ -1198,6 +1519,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) @@ -1206,11 +1530,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; @@ -1225,6 +1568,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); @@ -1246,6 +1595,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) @@ -1317,6 +1669,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) @@ -1333,7 +1692,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); } @@ -1383,7 +1742,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); @@ -1430,3 +1789,127 @@ 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 + size_t length = STRLEN(eap->arg); + size_t end = 0; + size_t len = 0; + int i = 1; + char *buf = xcalloc(length, sizeof(char)); + bool done = false; + while (!done) { + done = uc_split_args_iter(eap->arg, length, &end, buf, &len); + if (len > 0) { + lua_pushlstring(lstate, buf, len); + lua_rawseti(lstate, -2, i); + i++; + } + } + xfree(buf); + } + 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..e96494ec5a 100644 --- a/src/nvim/lua/executor.h +++ b/src/nvim/lua/executor.h @@ -5,26 +5,24 @@ #include <lua.h> #include "nvim/api/private/defs.h" +#include "nvim/assert.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,8 @@ 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); +EXTERN bool nlua_disable_preload INIT(= false); + #endif // NVIM_LUA_EXECUTOR_H diff --git a/src/nvim/lua/spell.c b/src/nvim/lua/spell.c new file mode 100644 index 0000000000..31a2b2d19f --- /dev/null +++ b/src/nvim/lua/spell.c @@ -0,0 +1,101 @@ +// 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 <lauxlib.h> +#include <lua.h> + +#include "nvim/lua/spell.h" +#include "nvim/spell.h" +#include "nvim/vim.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "lua/spell.c.generated.h" +#endif + +int nlua_spell_check(lua_State *lstate) +{ + if (lua_gettop(lstate) < 1) { + return luaL_error(lstate, "Expected 1 argument"); + } + + if (lua_type(lstate, 1) != LUA_TSTRING) { + luaL_argerror(lstate, 1, "expected string"); + } + + const char *str = lua_tolstring(lstate, 1, NULL); + + // spell.c requires that 'spell' is enabled, so we need to temporarily enable + // it before we can call spell functions. + const int wo_spell_save = curwin->w_p_spell; + + if (!curwin->w_p_spell) { + did_set_spelllang(curwin); + curwin->w_p_spell = true; + } + + // Check 'spelllang' + if (*curwin->w_s->b_p_spl == NUL) { + emsg(_(e_no_spell)); + curwin->w_p_spell = wo_spell_save; + return 0; + } + + hlf_T attr = HLF_COUNT; + size_t len = 0; + size_t pos = 0; + int capcol = -1; + int no_res = 0; + const char *result; + + lua_createtable(lstate, 0, 0); + + while (*str != NUL) { + attr = HLF_COUNT; + len = spell_check(curwin, (char_u *)str, &attr, &capcol, false); + assert(len <= INT_MAX); + + if (attr != HLF_COUNT) { + lua_createtable(lstate, 3, 0); + + lua_pushlstring(lstate, str, len); + lua_rawseti(lstate, -2, 1); + + result = attr == HLF_SPB ? "bad" : + attr == HLF_SPR ? "rare" : + attr == HLF_SPL ? "local" : + attr == HLF_SPC ? "caps" : + NULL; + + assert(result != NULL); + + lua_pushstring(lstate, result); + lua_rawseti(lstate, -2, 2); + + // +1 for 1-indexing + lua_pushinteger(lstate, (long)pos + 1); + lua_rawseti(lstate, -2, 3); + + lua_rawseti(lstate, -2, ++no_res); + } + + str += len; + pos += len; + capcol -= (int)len; + } + + // Restore 'spell' + curwin->w_p_spell = wo_spell_save; + return 1; +} + +static const luaL_Reg spell_functions[] = { + { "check", nlua_spell_check }, + { NULL, NULL } +}; + +int luaopen_spell(lua_State *L) +{ + lua_newtable(L); + luaL_register(L, NULL, spell_functions); + return 1; +} diff --git a/src/nvim/lua/spell.h b/src/nvim/lua/spell.h new file mode 100644 index 0000000000..8f798a5191 --- /dev/null +++ b/src/nvim/lua/spell.h @@ -0,0 +1,12 @@ +#ifndef NVIM_LUA_SPELL_H +#define NVIM_LUA_SPELL_H + +#include <lauxlib.h> +#include <lua.h> +#include <lualib.h> + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "lua/spell.h.generated.h" +#endif + +#endif // NVIM_LUA_SPELL_H diff --git a/src/nvim/lua/stdlib.c b/src/nvim/lua/stdlib.c index db79e9e7e9..e94c61b37c 100644 --- a/src/nvim/lua/stdlib.c +++ b/src/nvim/lua/stdlib.c @@ -25,8 +25,10 @@ #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/spell.h" #include "nvim/lua/stdlib.h" #include "nvim/lua/treesitter.h" #include "nvim/lua/xdiff.h" @@ -34,7 +36,6 @@ #include "nvim/map.h" #include "nvim/memline.h" #include "nvim/message.h" -#include "nvim/misc1.h" #include "nvim/msgpack_rpc/channel.h" #include "nvim/os/os.h" #include "nvim/regexp.h" @@ -175,13 +176,13 @@ int nlua_str_utfindex(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL size_t s1_len; const char *s1 = luaL_checklstring(lstate, 1, &s1_len); intptr_t idx; - if (lua_gettop(lstate) >= 2) { + if (lua_isnoneornil(lstate, 2)) { + idx = (intptr_t)s1_len; + } else { idx = luaL_checkinteger(lstate, 2); if (idx < 0 || idx > (intptr_t)s1_len) { return luaL_error(lstate, "index out of range"); } - } else { - idx = (intptr_t)s1_len; } size_t codepoints = 0, codeunits = 0; @@ -231,8 +232,8 @@ static int nlua_str_utf_start(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL if (offset < 0 || offset > (intptr_t)s1_len) { return luaL_error(lstate, "index out of range"); } - int tail_offset = mb_head_off((char_u *)s1, (char_u *)s1 + (char_u)offset - 1); - lua_pushinteger(lstate, tail_offset); + int head_offset = mb_head_off((char_u *)s1, (char_u *)s1 + offset - 1); + lua_pushinteger(lstate, head_offset); return 1; } @@ -251,7 +252,7 @@ static int nlua_str_utf_end(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL if (offset < 0 || offset > (intptr_t)s1_len) { return luaL_error(lstate, "index out of range"); } - int tail_offset = mb_tail_off((char_u *)s1, (char_u *)s1 + (char_u)offset - 1); + int tail_offset = mb_tail_off((char_u *)s1, (char_u *)s1 + offset - 1); lua_pushinteger(lstate, tail_offset); return 1; } @@ -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,6 +535,7 @@ void nlua_state_add_stdlib(lua_State *const lstate) lua_pushcfunction(lstate, &nlua_xdl_diff); lua_setfield(lstate, -2, "diff"); + // 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/xdiff.c b/src/nvim/lua/xdiff.c index b2e971f9f3..37855630d1 100644 --- a/src/nvim/lua/xdiff.c +++ b/src/nvim/lua/xdiff.c @@ -171,6 +171,7 @@ static NluaXdiffMode process_xdl_diff_opts(lua_State *lstate, xdemitconf_t *cfg, goto exit_1; } if (strequal("unified", v->data.string.data)) { + // the default } else if (strequal("indices", v->data.string.data)) { had_result_type_indices = true; } else { @@ -184,11 +185,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..be1ab935c0 100644 --- a/src/nvim/macros.h +++ b/src/nvim/macros.h @@ -103,7 +103,10 @@ // MB_PTR_BACK(): backup a pointer to the previous character, taking care of // multi-byte characters if needed. Only use with "p" > "s" ! #define MB_PTR_BACK(s, p) \ - (p -= utf_head_off((char_u *)s, (char_u *)p - 1) + 1) + (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 { \ @@ -130,7 +133,7 @@ #define ARRAY_LAST_ENTRY(arr) (arr)[ARRAY_SIZE(arr) - 1] // Duplicated in os/win_defs.h to avoid include-order sensitivity. -#define RGB_(r, g, b) ((r << 16) | (g << 8) | b) +#define RGB_(r, g, b) (((r) << 16) | ((g) << 8) | (b)) #define STR_(x) #x #define STR(x) STR_(x) diff --git a/src/nvim/main.c b/src/nvim/main.c index 921bc883cf..6ea1cb0875 100644 --- a/src/nvim/main.c +++ b/src/nvim/main.c @@ -9,25 +9,29 @@ #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" #include "nvim/decoration.h" +#include "nvim/decoration_provider.h" #include "nvim/diff.h" #include "nvim/eval.h" #include "nvim/ex_cmds.h" #include "nvim/ex_cmds2.h" #include "nvim/ex_docmd.h" +#include "nvim/ex_getln.h" #include "nvim/fileio.h" #include "nvim/fold.h" #include "nvim/getchar.h" #include "nvim/hashtab.h" #include "nvim/highlight.h" +#include "nvim/highlight_group.h" #include "nvim/iconv.h" #include "nvim/if_cscope.h" #include "nvim/lua/executor.h" #include "nvim/main.h" +#include "nvim/ui_client.h" #include "nvim/vim.h" #ifdef HAVE_LOCALE_H # include <locale.h> @@ -39,7 +43,6 @@ #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/message.h" -#include "nvim/misc1.h" #include "nvim/mouse.h" #include "nvim/move.h" #include "nvim/normal.h" @@ -111,7 +114,6 @@ static const char *err_too_many_args = N_("Too many edit arguments"); static const char *err_extra_cmd = N_("Too many \"+command\", \"-c command\" or \"--cmd command\" arguments"); - void event_init(void) { loop_init(&main_loop, NULL); @@ -155,10 +157,11 @@ bool event_teardown(void) void early_init(mparm_T *paramp) { env_init(); - fs_init(); + cmdline_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 @@ -231,6 +234,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. @@ -250,12 +257,12 @@ int main(int argc, char **argv) // Check if we have an interactive window. check_and_set_isatty(¶ms); - nlua_init(); - // Process the command line arguments. File names are put in the global // argument list "global_alist". command_line_scan(¶ms); + nlua_init(); + if (embedded_mode) { const char *err; if (!channel_from_stdio(true, CALLBACK_READER_INIT, &err)) { @@ -264,6 +271,9 @@ int main(int argc, char **argv) } server_init(params.listen_addr); + if (params.remote) { + remote_request(¶ms, params.remote, params.server_addr, argc, argv); + } if (GARGCOUNT > 0) { fname = get_fname(¶ms, cwd); @@ -336,15 +346,23 @@ int main(int argc, char **argv) TIME_MSG("init screen for UI"); } + if (ui_client_channel_id) { + ui_client_init(ui_client_channel_id); + ui_client_execute(ui_client_channel_id); + abort(); // unreachable + } + init_default_mappings(); // Default mappings. TIME_MSG("init default mappings"); 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; } @@ -352,14 +370,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(); } @@ -696,6 +723,50 @@ void getout(int exitval) os_exit(exitval); } +/// Preserve files and exit. +/// @note IObuff must contain a message. +/// @note This may be called from deadly_signal() in a signal handler, avoid +/// unsafe functions, such as allocating memory. +void preserve_exit(void) + FUNC_ATTR_NORETURN +{ + // 'true' when we are sure to exit, e.g., after a deadly signal + static bool really_exiting = false; + + // Prevent repeated calls into this method. + if (really_exiting) { + if (input_global_fd() >= 0) { + // normalize stream (#2598) + stream_set_blocking(input_global_fd(), true); + } + exit(2); + } + + really_exiting = true; + // Ignore SIGHUP while we are already exiting. #9274 + signal_reject_deadly(); + mch_errmsg(IObuff); + mch_errmsg("\n"); + ui_flush(); + + ml_close_notmod(); // close all not-modified buffers + + FOR_ALL_BUFFERS(buf) { + if (buf->b_ml.ml_mfp != NULL && buf->b_ml.ml_mfp->mf_fname != NULL) { + mch_errmsg("Vim: preserving files...\r\n"); + ui_flush(); + ml_sync_all(false, false, true); // preserve all swap files + break; + } + } + + ml_close_all(false); // close all memfiles, without deleting + + mch_errmsg("Vim: Finished.\r\n"); + + getout(1); +} + /// Gets the integer value of a numeric command line argument if given, /// such as '-o10'. /// @@ -744,6 +815,114 @@ static void init_locale(void) } #endif + +static uint64_t server_connect(char *server_addr, const char **errmsg) +{ + if (server_addr == NULL) { + *errmsg = "no address specified"; + return 0; + } + CallbackReader on_data = CALLBACK_READER_INIT; + const char *error = NULL; + bool is_tcp = strrchr(server_addr, ':') ? true : false; + // connected to channel + uint64_t chan = channel_connect(is_tcp, server_addr, true, on_data, 50, &error); + if (error) { + *errmsg = error; + return 0; + } + return chan; +} + +/// Handle remote subcommands +static void remote_request(mparm_T *params, int remote_args, + char *server_addr, int argc, char **argv) +{ + const char *connect_error = NULL; + uint64_t chan = server_connect(server_addr, &connect_error); + Object rvobj = OBJECT_INIT; + + if (strequal(argv[remote_args], "--remote-ui-test")) { + if (!chan) { + emsg(connect_error); + exit(1); + } + + ui_client_channel_id = chan; + return; + } + + Array args = ARRAY_DICT_INIT; + String arg_s; + for (int t_argc = remote_args; t_argc < argc; t_argc++) { + arg_s = cstr_to_string(argv[t_argc]); + ADD(args, STRING_OBJ(arg_s)); + } + + Error err = ERROR_INIT; + Array a = ARRAY_DICT_INIT; + ADD(a, INTEGER_OBJ((int)chan)); + ADD(a, CSTR_TO_OBJ(server_addr)); + ADD(a, CSTR_TO_OBJ(connect_error)); + ADD(a, ARRAY_OBJ(args)); + String s = STATIC_CSTR_AS_STRING("return vim._cs_remote(...)"); + Object o = nlua_exec(s, a, &err); + api_free_array(a); + if (ERROR_SET(&err)) { + mch_errmsg(err.msg); + mch_errmsg("\n"); + os_exit(2); + } + + if (o.type == kObjectTypeDictionary) { + rvobj.data.dictionary = o.data.dictionary; + } else { + mch_errmsg("vim._cs_remote returned unexpected value\n"); + os_exit(2); + } + + TriState should_exit = kNone; + TriState tabbed = kNone; + + for (size_t i = 0; i < rvobj.data.dictionary.size ; i++) { + if (strcmp(rvobj.data.dictionary.items[i].key.data, "errmsg") == 0) { + if (rvobj.data.dictionary.items[i].value.type != kObjectTypeString) { + mch_errmsg("vim._cs_remote returned an unexpected type for 'errmsg'\n"); + os_exit(2); + } + mch_errmsg(rvobj.data.dictionary.items[i].value.data.string.data); + mch_errmsg("\n"); + os_exit(2); + } else if (strcmp(rvobj.data.dictionary.items[i].key.data, "tabbed") == 0) { + if (rvobj.data.dictionary.items[i].value.type != kObjectTypeBoolean) { + mch_errmsg("vim._cs_remote returned an unexpected type for 'tabbed'\n"); + os_exit(2); + } + tabbed = rvobj.data.dictionary.items[i].value.data.boolean ? kTrue : kFalse; + } else if (strcmp(rvobj.data.dictionary.items[i].key.data, "should_exit") == 0) { + if (rvobj.data.dictionary.items[i].value.type != kObjectTypeBoolean) { + mch_errmsg("vim._cs_remote returned an unexpected type for 'should_exit'\n"); + os_exit(2); + } + should_exit = rvobj.data.dictionary.items[i].value.data.boolean ? kTrue : kFalse; + } + } + if (should_exit == kNone || tabbed == kNone) { + mch_errmsg("vim._cs_remote didn't return a value for should_exit or tabbed, bailing\n"); + os_exit(2); + } + api_free_object(o); + + if (should_exit == kTrue) { + os_exit(0); + } + if (tabbed == kTrue) { + params->window_count = argc - remote_args - 1; + params->window_layout = WIN_TABS; + } +} + + /// Decides whether text (as opposed to commands) will be read from stdin. /// @see EDIT_STDIN static bool edit_stdin(bool explicit, mparm_T *parmp) @@ -765,7 +944,6 @@ static void command_line_scan(mparm_T *parmp) bool had_stdin_file = false; // found explicit "-" argument bool had_minmin = false; // found "--" argument int want_argument; // option argument with argument - int c; long n; argc--; @@ -787,7 +965,7 @@ static void command_line_scan(mparm_T *parmp) // Optional argument. } else if (argv[0][0] == '-' && !had_minmin) { want_argument = false; - c = argv[0][argv_idx++]; + char c = argv[0][argv_idx++]; switch (c) { case NUL: // "nvim -" read from stdin if (exmode_active) { @@ -810,6 +988,8 @@ static void command_line_scan(mparm_T *parmp) // "--version" give version message // "--noplugin[s]" skip plugins // "--cmd <cmd>" execute cmd before vimrc + // "--remote" execute commands remotey on a server + // "--server" name of vim server to send remote commands to if (STRICMP(argv[0] + argv_idx, "help") == 0) { usage(); os_exit(0); @@ -848,6 +1028,11 @@ static void command_line_scan(mparm_T *parmp) argv_idx += 6; } else if (STRNICMP(argv[0] + argv_idx, "literal", 7) == 0) { // Do nothing: file args are always literal. #7679 + } else if (STRNICMP(argv[0] + argv_idx, "remote", 6) == 0) { + parmp->remote = parmp->argc - argc; + } else if (STRNICMP(argv[0] + argv_idx, "server", 6) == 0) { + want_argument = true; + argv_idx += 6; } else if (STRNICMP(argv[0] + argv_idx, "noplugin", 8) == 0) { p_lpl = false; } else if (STRNICMP(argv[0] + argv_idx, "cmd", 3) == 0) { @@ -860,6 +1045,8 @@ static void command_line_scan(mparm_T *parmp) parmp->use_vimrc = "NONE"; parmp->clean = true; set_option_value("shadafile", 0L, "NONE", 0); + } else if (STRNICMP(argv[0] + argv_idx, "luamod-dev", 9) == 0) { + nlua_disable_preload = true; } else { if (argv[0][argv_idx]) { mainerr(err_opt_unknown, argv[0]); @@ -1077,6 +1264,9 @@ static void command_line_scan(mparm_T *parmp) } else if (strequal(argv[-1], "--listen")) { // "--listen {address}" parmp->listen_addr = argv[0]; + } else if (strequal(argv[-1], "--server")) { + // "--server {address}" + parmp->server_addr = argv[0]; } // "--startuptime <file>" already handled break; @@ -1231,6 +1421,8 @@ static void init_params(mparm_T *paramp, int argc, char **argv) paramp->use_debug_break_level = -1; paramp->window_count = -1; paramp->listen_addr = NULL; + paramp->server_addr = NULL; + paramp->remote = 0; } /// Initialize global startuptime file if "--startuptime" passed as an argument. @@ -1506,7 +1698,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; } @@ -1518,7 +1710,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; } @@ -1565,7 +1757,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) { @@ -1909,6 +2101,7 @@ static bool file_owned(const char *fname) /// @param errstr string containing an error message /// @param str string to append to the primary error message, or NULL static void mainerr(const char *errstr, const char *str) + FUNC_ATTR_NORETURN { char *prgname = (char *)path_tail((char_u *)argv0); @@ -1932,6 +2125,8 @@ static void mainerr(const char *errstr, const char *str) /// Prints version information for "nvim -v" or "nvim --version". static void version(void) { + // TODO(bfred): not like this? + nlua_init(); info_message = true; // use mch_msg(), not mch_errmsg() list_version(); msg_putchar('\n'); @@ -1979,6 +2174,8 @@ static void usage(void) mch_msg(_(" --headless Don't start a user interface\n")); mch_msg(_(" --listen <address> Serve RPC API from this address\n")); mch_msg(_(" --noplugin Don't load plugins\n")); + mch_msg(_(" --remote[-subcommand] Execute commands remotely on a server\n")); + mch_msg(_(" --server <address> Specify RPC server to send commands to\n")); mch_msg(_(" --startuptime <file> Write startup timing messages to <file>\n")); mch_msg(_("\nSee \":help startup-options\" for all options.\n")); } diff --git a/src/nvim/main.h b/src/nvim/main.h index f73af5c288..e55bef6e33 100644 --- a/src/nvim/main.h +++ b/src/nvim/main.h @@ -39,6 +39,8 @@ typedef struct { int diff_mode; // start with 'diff' set char *listen_addr; // --listen {address} + int remote; // --remote-[subcmd] {file1} {file2} + char *server_addr; // --server {address} } mparm_T; #ifdef INCLUDE_GENERATED_DECLARATIONS diff --git a/src/nvim/map.c b/src/nvim/map.c index 1d9abe3ef2..b3f48ad5d6 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,15 @@ 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(int, String, DEFAULT_INITIALIZER) +MAP_IMPL(String, UIClientHandler, NULL) MAP_IMPL(ColorKey, ColorItem, COLOR_ITEM_INITIALIZER) diff --git a/src/nvim/map.h b/src/nvim/map.h index dbd85a4e1f..693ef50127 100644 --- a/src/nvim/map.h +++ b/src/nvim/map.h @@ -8,6 +8,7 @@ #include "nvim/extmark_defs.h" #include "nvim/highlight_defs.h" #include "nvim/map_defs.h" +#include "nvim/ui_client.h" #if defined(__NetBSD__) # undef uint64_t @@ -40,20 +41,15 @@ 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(int, String) +MAP_DECLS(String, UIClientHandler) MAP_DECLS(ColorKey, ColorItem) diff --git a/src/nvim/mark.c b/src/nvim/mark.c index 39f18b333d..6790bf8240 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 } /* @@ -630,7 +630,7 @@ static char_u *mark_line(pos_T *mp, int lead_len) if (mp->lnum == 0 || mp->lnum > curbuf->b_ml.ml_line_count) { return vim_strsave((char_u *)"-invalid-"); } - assert(Columns >= 0 && (size_t)Columns <= SIZE_MAX); + assert(Columns >= 0); // Allow for up to 5 bytes per character. s = vim_strnsave(skipwhite(ml_get(mp->lnum)), (size_t)Columns * 5); @@ -844,6 +844,11 @@ void ex_jumps(exarg_T *eap) if (curwin->w_jumplist[i].fmark.mark.lnum != 0) { name = fm_getname(&curwin->w_jumplist[i].fmark, 16); + // Make sure to output the current indicator, even when on an wiped + // out buffer. ":filter" may still skip it. + if (name == NULL && i == curwin->w_jumplistidx) { + name = vim_strsave((char_u *)"-invalid-"); + } // apply :filter /pat/ or file name not available if (name == NULL || message_filtered(name)) { xfree(name); diff --git a/src/nvim/marktree.c b/src/nvim/marktree.c index 38014ab375..937582572b 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)(rawkey(itr).flags & (uint16_t) ~MT_FLAG_DECOR_MASK); + rawkey(itr).flags = (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 void swap_id(uint64_t *id1, uint64_t *id2) +static bool itr_eq(MarkTreeIter *itr1, MarkTreeIter *itr2) { - uint64_t temp = *id1; - *id1 = *id2; - *id2 = temp; + return (&rawkey(itr1) == &rawkey(itr2)); +} + +static void itr_swap(MarkTreeIter *itr1, MarkTreeIter *itr2) +{ + 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 @@ -1130,15 +1159,13 @@ static size_t check_node(MarkTree *b, mtnode_t *x, mtpos_t *last, bool *last_rig if (i > 0) { unrelative(x->key[i-1].pos, last); } - if (x->level) { - } 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..ae6da92106 100644 --- a/src/nvim/marktree.h +++ b/src/nvim/marktree.h @@ -1,11 +1,14 @@ #ifndef NVIM_MARKTREE_H #define NVIM_MARKTREE_H +#include <assert.h> #include <stdint.h> +#include "nvim/assert.h" #include "nvim/garray.h" #include "nvim/map.h" #include "nvim/pos.h" +#include "nvim/types.h" #define MT_MAX_DEPTH 20 #define MT_BRANCH_FACTOR 10 @@ -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,80 @@ 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_start(mtkey_t key) +{ + return mt_paired(key) && !mt_end(key); +} + +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 +125,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 +135,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/match.c b/src/nvim/match.c new file mode 100644 index 0000000000..af89319a09 --- /dev/null +++ b/src/nvim/match.c @@ -0,0 +1,1181 @@ +// 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 + +// match.c: functions for highlighting matches + +#include <stdbool.h> +#include "nvim/charset.h" +#include "nvim/fold.h" +#include "nvim/highlight_group.h" +#include "nvim/match.h" +#include "nvim/memline.h" +#include "nvim/regexp.h" +#include "nvim/runtime.h" +#include "nvim/screen.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "match.c.generated.h" +#endif + +static char *e_invalwindow = N_("E957: Invalid window number"); + +#define SEARCH_HL_PRIORITY 0 + +/// Add match to the match list of window 'wp'. The pattern 'pat' will be +/// highlighted with the group 'grp' with priority 'prio'. +/// Optionally, a desired ID 'id' can be specified (greater than or equal to 1). +/// +/// @param[in] id a desired ID 'id' can be specified +/// (greater than or equal to 1). -1 must be specified if no +/// particular ID is desired +/// @param[in] conceal_char pointer to conceal replacement char +/// @return ID of added match, -1 on failure. +static int match_add(win_T *wp, const char *const grp, const char *const pat, int prio, int id, + list_T *pos_list, const char *const conceal_char) + FUNC_ATTR_NONNULL_ARG(1, 2) +{ + matchitem_T *cur; + matchitem_T *prev; + matchitem_T *m; + int hlg_id; + regprog_T *regprog = NULL; + int rtype = SOME_VALID; + + if (*grp == NUL || (pat != NULL && *pat == NUL)) { + return -1; + } + if (id < -1 || id == 0) { + semsg(_("E799: Invalid ID: %" PRId64 + " (must be greater than or equal to 1)"), + (int64_t)id); + return -1; + } + if (id != -1) { + cur = wp->w_match_head; + while (cur != NULL) { + if (cur->id == id) { + semsg(_("E801: ID already taken: %" PRId64), (int64_t)id); + return -1; + } + cur = cur->next; + } + } + if ((hlg_id = syn_check_group(grp, strlen(grp))) == 0) { + return -1; + } + if (pat != NULL && (regprog = vim_regcomp((char_u *)pat, RE_MAGIC)) == NULL) { + semsg(_(e_invarg2), pat); + return -1; + } + + // Find available match ID. + while (id == -1) { + cur = wp->w_match_head; + while (cur != NULL && cur->id != wp->w_next_match_id) { + cur = cur->next; + } + if (cur == NULL) { + id = wp->w_next_match_id; + } + wp->w_next_match_id++; + } + + // Build new match. + m = xcalloc(1, sizeof(matchitem_T)); + m->id = id; + m->priority = prio; + m->pattern = pat == NULL ? NULL: (char_u *)xstrdup(pat); + m->hlg_id = hlg_id; + m->match.regprog = regprog; + m->match.rmm_ic = false; + m->match.rmm_maxcol = 0; + m->conceal_char = 0; + if (conceal_char != NULL) { + m->conceal_char = utf_ptr2char((const char_u *)conceal_char); + } + + // Set up position matches + if (pos_list != NULL) { + linenr_T toplnum = 0; + linenr_T botlnum = 0; + + int i = 0; + TV_LIST_ITER(pos_list, li, { + linenr_T lnum = 0; + colnr_T col = 0; + int len = 1; + bool error = false; + + if (TV_LIST_ITEM_TV(li)->v_type == VAR_LIST) { + const list_T *const subl = TV_LIST_ITEM_TV(li)->vval.v_list; + const listitem_T *subli = tv_list_first(subl); + if (subli == NULL) { + semsg(_("E5030: Empty list at position %d"), + (int)tv_list_idx_of_item(pos_list, li)); + goto fail; + } + lnum = tv_get_number_chk(TV_LIST_ITEM_TV(subli), &error); + if (error) { + goto fail; + } + if (lnum <= 0) { + continue; + } + m->pos.pos[i].lnum = lnum; + subli = TV_LIST_ITEM_NEXT(subl, subli); + if (subli != NULL) { + col = (colnr_T)tv_get_number_chk(TV_LIST_ITEM_TV(subli), &error); + if (error) { + goto fail; + } + if (col < 0) { + continue; + } + subli = TV_LIST_ITEM_NEXT(subl, subli); + if (subli != NULL) { + len = (colnr_T)tv_get_number_chk(TV_LIST_ITEM_TV(subli), &error); + if (len < 0) { + continue; + } + if (error) { + goto fail; + } + } + } + m->pos.pos[i].col = col; + m->pos.pos[i].len = len; + } else if (TV_LIST_ITEM_TV(li)->v_type == VAR_NUMBER) { + if (TV_LIST_ITEM_TV(li)->vval.v_number <= 0) { + continue; + } + m->pos.pos[i].lnum = TV_LIST_ITEM_TV(li)->vval.v_number; + m->pos.pos[i].col = 0; + m->pos.pos[i].len = 0; + } else { + semsg(_("E5031: List or number required at position %d"), + (int)tv_list_idx_of_item(pos_list, li)); + goto fail; + } + if (toplnum == 0 || lnum < toplnum) { + toplnum = lnum; + } + if (botlnum == 0 || lnum >= botlnum) { + botlnum = lnum + 1; + } + i++; + if (i >= MAXPOSMATCH) { + break; + } + }); + + // Calculate top and bottom lines for redrawing area + if (toplnum != 0) { + if (wp->w_buffer->b_mod_set) { + if (wp->w_buffer->b_mod_top > toplnum) { + wp->w_buffer->b_mod_top = toplnum; + } + if (wp->w_buffer->b_mod_bot < botlnum) { + wp->w_buffer->b_mod_bot = botlnum; + } + } else { + wp->w_buffer->b_mod_set = true; + wp->w_buffer->b_mod_top = toplnum; + wp->w_buffer->b_mod_bot = botlnum; + wp->w_buffer->b_mod_xlines = 0; + } + m->pos.toplnum = toplnum; + m->pos.botlnum = botlnum; + rtype = VALID; + } + } + + // Insert new match. The match list is in ascending order with regard to + // the match priorities. + cur = wp->w_match_head; + prev = cur; + while (cur != NULL && prio >= cur->priority) { + prev = cur; + cur = cur->next; + } + if (cur == prev) { + wp->w_match_head = m; + } else { + prev->next = m; + } + m->next = cur; + + redraw_later(wp, rtype); + return id; + +fail: + xfree(m); + return -1; +} + +/// Delete match with ID 'id' in the match list of window 'wp'. +/// +/// @param perr print error messages if true. +static int match_delete(win_T *wp, int id, bool perr) +{ + matchitem_T *cur = wp->w_match_head; + matchitem_T *prev = cur; + int rtype = SOME_VALID; + + if (id < 1) { + if (perr) { + semsg(_("E802: Invalid ID: %" PRId64 + " (must be greater than or equal to 1)"), + (int64_t)id); + } + return -1; + } + while (cur != NULL && cur->id != id) { + prev = cur; + cur = cur->next; + } + if (cur == NULL) { + if (perr) { + semsg(_("E803: ID not found: %" PRId64), (int64_t)id); + } + return -1; + } + if (cur == prev) { + wp->w_match_head = cur->next; + } else { + prev->next = cur->next; + } + vim_regfree(cur->match.regprog); + xfree(cur->pattern); + if (cur->pos.toplnum != 0) { + if (wp->w_buffer->b_mod_set) { + if (wp->w_buffer->b_mod_top > cur->pos.toplnum) { + wp->w_buffer->b_mod_top = cur->pos.toplnum; + } + if (wp->w_buffer->b_mod_bot < cur->pos.botlnum) { + wp->w_buffer->b_mod_bot = cur->pos.botlnum; + } + } else { + wp->w_buffer->b_mod_set = true; + wp->w_buffer->b_mod_top = cur->pos.toplnum; + wp->w_buffer->b_mod_bot = cur->pos.botlnum; + wp->w_buffer->b_mod_xlines = 0; + } + rtype = VALID; + } + xfree(cur); + redraw_later(wp, rtype); + return 0; +} + +/// Delete all matches in the match list of window 'wp'. +void clear_matches(win_T *wp) +{ + matchitem_T *m; + + while (wp->w_match_head != NULL) { + m = wp->w_match_head->next; + vim_regfree(wp->w_match_head->match.regprog); + xfree(wp->w_match_head->pattern); + xfree(wp->w_match_head); + wp->w_match_head = m; + } + redraw_later(wp, SOME_VALID); +} + +/// Get match from ID 'id' in window 'wp'. +/// Return NULL if match not found. +matchitem_T *get_match(win_T *wp, int id) +{ + matchitem_T *cur = wp->w_match_head; + + while (cur != NULL && cur->id != id) { + cur = cur->next; + } + return cur; +} + +/// Init for calling prepare_search_hl(). +void init_search_hl(win_T *wp, match_T *search_hl) + FUNC_ATTR_NONNULL_ALL +{ + // Setup for match and 'hlsearch' highlighting. Disable any previous + // match + matchitem_T *cur = wp->w_match_head; + while (cur != NULL) { + cur->hl.rm = cur->match; + if (cur->hlg_id == 0) { + cur->hl.attr = 0; + } else { + cur->hl.attr = syn_id2attr(cur->hlg_id); + } + cur->hl.buf = wp->w_buffer; + cur->hl.lnum = 0; + cur->hl.first_lnum = 0; + // Set the time limit to 'redrawtime'. + cur->hl.tm = profile_setlimit(p_rdt); + cur = cur->next; + } + search_hl->buf = wp->w_buffer; + search_hl->lnum = 0; + search_hl->first_lnum = 0; + search_hl->attr = win_hl_attr(wp, HLF_L); + + // time limit is set at the toplevel, for all windows +} + +/// @param shl points to a match. Fill on match. +/// @param posmatch match positions +/// @param mincol minimal column for a match +/// +/// @return one on match, otherwise return zero. +static int next_search_hl_pos(match_T *shl, linenr_T lnum, posmatch_T *posmatch, colnr_T mincol) + FUNC_ATTR_NONNULL_ALL +{ + int i; + int found = -1; + + shl->lnum = 0; + for (i = posmatch->cur; i < MAXPOSMATCH; i++) { + llpos_T *pos = &posmatch->pos[i]; + + if (pos->lnum == 0) { + break; + } + if (pos->len == 0 && pos->col < mincol) { + continue; + } + if (pos->lnum == lnum) { + if (found >= 0) { + // if this match comes before the one at "found" then swap + // them + if (pos->col < posmatch->pos[found].col) { + llpos_T tmp = *pos; + + *pos = posmatch->pos[found]; + posmatch->pos[found] = tmp; + } + } else { + found = i; + } + } + } + posmatch->cur = 0; + if (found >= 0) { + colnr_T start = posmatch->pos[found].col == 0 + ? 0: posmatch->pos[found].col - 1; + colnr_T end = posmatch->pos[found].col == 0 + ? MAXCOL : start + posmatch->pos[found].len; + + shl->lnum = lnum; + shl->rm.startpos[0].lnum = 0; + shl->rm.startpos[0].col = start; + shl->rm.endpos[0].lnum = 0; + shl->rm.endpos[0].col = end; + shl->is_addpos = true; + posmatch->cur = found + 1; + return 1; + } + return 0; +} + +/// Search for a next 'hlsearch' or match. +/// Uses shl->buf. +/// Sets shl->lnum and shl->rm contents. +/// Note: Assumes a previous match is always before "lnum", unless +/// shl->lnum is zero. +/// Careful: Any pointers for buffer lines will become invalid. +/// +/// @param shl points to search_hl or a match +/// @param mincol minimal column for a match +/// @param cur to retrieve match positions if any +static void next_search_hl(win_T *win, match_T *search_hl, match_T *shl, linenr_T lnum, + colnr_T mincol, matchitem_T *cur) + FUNC_ATTR_NONNULL_ARG(2) +{ + linenr_T l; + colnr_T matchcol; + long nmatched = 0; + int save_called_emsg = called_emsg; + + // for :{range}s/pat only highlight inside the range + if (lnum < search_first_line || lnum > search_last_line) { + shl->lnum = 0; + return; + } + + if (shl->lnum != 0) { + // Check for three situations: + // 1. If the "lnum" is below a previous match, start a new search. + // 2. If the previous match includes "mincol", use it. + // 3. Continue after the previous match. + l = shl->lnum + shl->rm.endpos[0].lnum - shl->rm.startpos[0].lnum; + if (lnum > l) { + shl->lnum = 0; + } else if (lnum < l || shl->rm.endpos[0].col > mincol) { + return; + } + } + + // Repeat searching for a match until one is found that includes "mincol" + // or none is found in this line. + called_emsg = false; + for (;;) { + // Stop searching after passing the time limit. + if (profile_passed_limit(shl->tm)) { + shl->lnum = 0; // no match found in time + break; + } + // Three situations: + // 1. No useful previous match: search from start of line. + // 2. Not Vi compatible or empty match: continue at next character. + // Break the loop if this is beyond the end of the line. + // 3. Vi compatible searching: continue at end of previous match. + if (shl->lnum == 0) { + matchcol = 0; + } else if (vim_strchr(p_cpo, CPO_SEARCH) == NULL + || (shl->rm.endpos[0].lnum == 0 + && shl->rm.endpos[0].col <= shl->rm.startpos[0].col)) { + char_u *ml; + + matchcol = shl->rm.startpos[0].col; + ml = ml_get_buf(shl->buf, lnum, false) + matchcol; + if (*ml == NUL) { + matchcol++; + shl->lnum = 0; + break; + } + matchcol += utfc_ptr2len(ml); + } else { + matchcol = shl->rm.endpos[0].col; + } + + shl->lnum = lnum; + if (shl->rm.regprog != NULL) { + // Remember whether shl->rm is using a copy of the regprog in + // cur->match. + bool regprog_is_copy = (shl != search_hl + && cur != NULL + && shl == &cur->hl + && cur->match.regprog == cur->hl.rm.regprog); + int timed_out = false; + + nmatched = vim_regexec_multi(&shl->rm, win, shl->buf, lnum, matchcol, + &(shl->tm), &timed_out); + // Copy the regprog, in case it got freed and recompiled. + if (regprog_is_copy) { + cur->match.regprog = cur->hl.rm.regprog; + } + if (called_emsg || got_int || timed_out) { + // Error while handling regexp: stop using this regexp. + if (shl == search_hl) { + // don't free regprog in the match list, it's a copy + vim_regfree(shl->rm.regprog); + set_no_hlsearch(true); + } + shl->rm.regprog = NULL; + shl->lnum = 0; + got_int = false; // avoid the "Type :quit to exit Vim" message + break; + } + } else if (cur != NULL) { + nmatched = next_search_hl_pos(shl, lnum, &(cur->pos), matchcol); + } + if (nmatched == 0) { + shl->lnum = 0; // no match found + break; + } + if (shl->rm.startpos[0].lnum > 0 + || shl->rm.startpos[0].col >= mincol + || nmatched > 1 + || shl->rm.endpos[0].col > mincol) { + shl->lnum += shl->rm.startpos[0].lnum; + break; // useful match found + } + + // Restore called_emsg for assert_fails(). + called_emsg = save_called_emsg; + } +} + +/// Advance to the match in window "wp" line "lnum" or past it. +void prepare_search_hl(win_T *wp, match_T *search_hl, linenr_T lnum) + FUNC_ATTR_NONNULL_ALL +{ + matchitem_T *cur; // points to the match list + match_T *shl; // points to search_hl or a match + bool shl_flag; // flag to indicate whether search_hl + // has been processed or not + + // When using a multi-line pattern, start searching at the top + // of the window or just after a closed fold. + // Do this both for search_hl and the match list. + cur = wp->w_match_head; + shl_flag = false; + while (cur != NULL || shl_flag == false) { + if (shl_flag == false) { + shl = search_hl; + shl_flag = true; + } else { + shl = &cur->hl; // -V595 + } + if (shl->rm.regprog != NULL + && shl->lnum == 0 + && re_multiline(shl->rm.regprog)) { + if (shl->first_lnum == 0) { + for (shl->first_lnum = lnum; + shl->first_lnum > wp->w_topline; + shl->first_lnum--) { + if (hasFoldingWin(wp, shl->first_lnum - 1, NULL, NULL, true, NULL)) { + break; + } + } + } + if (cur != NULL) { + cur->pos.cur = 0; + } + bool pos_inprogress = true; // mark that a position match search is + // in progress + int n = 0; + while (shl->first_lnum < lnum && (shl->rm.regprog != NULL + || (cur != NULL && pos_inprogress))) { + next_search_hl(wp, search_hl, shl, shl->first_lnum, (colnr_T)n, + shl == search_hl ? NULL : cur); + pos_inprogress = !(cur == NULL || cur->pos.cur == 0); + if (shl->lnum != 0) { + shl->first_lnum = shl->lnum + + shl->rm.endpos[0].lnum + - shl->rm.startpos[0].lnum; + n = shl->rm.endpos[0].col; + } else { + shl->first_lnum++; + n = 0; + } + } + } + if (shl != search_hl && cur != NULL) { + cur = cur->next; + } + } +} + +/// Prepare for 'hlsearch' and match highlighting in one window line. +/// Return true if there is such highlighting and set "search_attr" to the +/// current highlight attribute. +bool prepare_search_hl_line(win_T *wp, linenr_T lnum, colnr_T mincol, char_u **line, + match_T *search_hl, int *search_attr, bool *search_attr_from_match) +{ + matchitem_T *cur = wp->w_match_head; // points to the match list + match_T *shl; // points to search_hl or a match + bool shl_flag = false; // flag to indicate whether search_hl + // has been processed or not + bool area_highlighting = false; + + // Handle highlighting the last used search pattern and matches. + // Do this for both search_hl and the match list. + while (cur != NULL || !shl_flag) { + if (!shl_flag) { + shl = search_hl; + shl_flag = true; + } else { + shl = &cur->hl; // -V595 + } + shl->startcol = MAXCOL; + shl->endcol = MAXCOL; + shl->attr_cur = 0; + shl->is_addpos = false; + if (cur != NULL) { + cur->pos.cur = 0; + } + next_search_hl(wp, search_hl, shl, lnum, mincol, + shl == search_hl ? NULL : cur); + + // Need to get the line again, a multi-line regexp may have made it + // invalid. + *line = ml_get_buf(wp->w_buffer, lnum, false); + + if (shl->lnum != 0 && shl->lnum <= lnum) { + if (shl->lnum == lnum) { + shl->startcol = shl->rm.startpos[0].col; + } else { + shl->startcol = 0; + } + if (lnum == shl->lnum + shl->rm.endpos[0].lnum + - shl->rm.startpos[0].lnum) { + shl->endcol = shl->rm.endpos[0].col; + } else { + shl->endcol = MAXCOL; + } + // Highlight one character for an empty match. + if (shl->startcol == shl->endcol) { + if ((*line)[shl->endcol] != NUL) { + shl->endcol += utfc_ptr2len(*line + shl->endcol); + } else { + shl->endcol++; + } + } + if ((long)shl->startcol < mincol) { // match at leftcol + shl->attr_cur = shl->attr; + *search_attr = shl->attr; + *search_attr_from_match = shl != search_hl; + } + area_highlighting = true; + } + if (shl != search_hl && cur != NULL) { + cur = cur->next; + } + } + return area_highlighting; +} + +/// For a position in a line: Check for start/end of 'hlsearch' and other +/// matches. +/// After end, check for start/end of next match. +/// When another match, have to check for start again. +/// Watch out for matching an empty string! +/// Return the updated search_attr. +int update_search_hl(win_T *wp, linenr_T lnum, colnr_T col, char_u **line, match_T *search_hl, + int *has_match_conc, int *match_conc, int lcs_eol_one, + bool *search_attr_from_match) +{ + matchitem_T *cur = wp->w_match_head; // points to the match list + match_T *shl; // points to search_hl or a match + bool shl_flag = false; // flag to indicate whether search_hl + // has been processed or not + int search_attr = 0; + + // Do this for 'search_hl' and the match list (ordered by priority). + while (cur != NULL || !shl_flag) { + if (!shl_flag + && (cur == NULL || cur->priority > SEARCH_HL_PRIORITY)) { + shl = search_hl; + shl_flag = true; + } else { + shl = &cur->hl; + } + if (cur != NULL) { + cur->pos.cur = 0; + } + bool pos_inprogress = true; // mark that a position match search is + // in progress + while (shl->rm.regprog != NULL + || (cur != NULL && pos_inprogress)) { + if (shl->startcol != MAXCOL + && col >= shl->startcol + && col < shl->endcol) { + int next_col = col + utfc_ptr2len(*line + col); + + if (shl->endcol < next_col) { + shl->endcol = next_col; + } + shl->attr_cur = shl->attr; + // Match with the "Conceal" group results in hiding + // the match. + if (cur != NULL + && shl != search_hl + && syn_name2id("Conceal") == cur->hlg_id) { + *has_match_conc = col == shl->startcol ? 2 : 1; + *match_conc = cur->conceal_char; + } else { + *has_match_conc = 0; + } + } else if (col == shl->endcol) { + shl->attr_cur = 0; + + next_search_hl(wp, search_hl, shl, lnum, col, + shl == search_hl ? NULL : cur); + pos_inprogress = !(cur == NULL || cur->pos.cur == 0); + + // Need to get the line again, a multi-line regexp + // may have made it invalid. + *line = ml_get_buf(wp->w_buffer, lnum, false); + + if (shl->lnum == lnum) { + shl->startcol = shl->rm.startpos[0].col; + if (shl->rm.endpos[0].lnum == 0) { + shl->endcol = shl->rm.endpos[0].col; + } else { + shl->endcol = MAXCOL; + } + + if (shl->startcol == shl->endcol) { + // highlight empty match, try again after it + shl->endcol += utfc_ptr2len(*line + shl->endcol); + } + + // Loop to check if the match starts at the + // current position + continue; + } + } + break; + } + if (shl != search_hl && cur != NULL) { + cur = cur->next; + } + } + + // Use attributes from match with highest priority among + // 'search_hl' and the match list. + *search_attr_from_match = false; + search_attr = search_hl->attr_cur; + cur = wp->w_match_head; + shl_flag = false; + while (cur != NULL || !shl_flag) { + if (!shl_flag + && (cur == NULL || cur->priority > SEARCH_HL_PRIORITY)) { + shl = search_hl; + shl_flag = true; + } else { + shl = &cur->hl; + } + if (shl->attr_cur != 0) { + search_attr = shl->attr_cur; + *search_attr_from_match = shl != search_hl; + } + if (shl != search_hl && cur != NULL) { + cur = cur->next; + } + } + // Only highlight one character after the last column. + if (*(*line + col) == NUL && (wp->w_p_list && lcs_eol_one == -1)) { + search_attr = 0; + } + return search_attr; +} + +bool get_prevcol_hl_flag(win_T *wp, match_T *search_hl, long curcol) +{ + long prevcol = curcol; + matchitem_T *cur; // points to the match list + + // we're not really at that column when skipping some text + if ((long)(wp->w_p_wrap ? wp->w_skipcol : wp->w_leftcol) > prevcol) { + prevcol++; + } + + if (!search_hl->is_addpos && prevcol == search_hl->startcol) { + return true; + } else { + cur = wp->w_match_head; + while (cur != NULL) { + if (!cur->hl.is_addpos && prevcol == cur->hl.startcol) { + return true; + } + cur = cur->next; + } + } + return false; +} + +/// Get highlighting for the char after the text in "char_attr" from 'hlsearch' +/// or match highlighting. +void get_search_match_hl(win_T *wp, match_T *search_hl, long col, int *char_attr) +{ + matchitem_T *cur = wp->w_match_head; // points to the match list + match_T *shl; // points to search_hl or a match + bool shl_flag = false; // flag to indicate whether search_hl + // has been processed or not + + *char_attr = search_hl->attr; + while (cur != NULL || !shl_flag) { + if (!shl_flag + && (cur == NULL || cur->priority > SEARCH_HL_PRIORITY)) { + shl = search_hl; + shl_flag = true; + } else { + shl = &cur->hl; + } + if (col - 1 == (long)shl->startcol + && (shl == search_hl || !shl->is_addpos)) { + *char_attr = shl->attr; + } + if (shl != search_hl && cur != NULL) { + cur = cur->next; + } + } +} + +static int matchadd_dict_arg(typval_T *tv, const char **conceal_char, win_T **win) +{ + dictitem_T *di; + + if (tv->v_type != VAR_DICT) { + emsg(_(e_dictreq)); + return FAIL; + } + + if ((di = tv_dict_find(tv->vval.v_dict, S_LEN("conceal"))) != NULL) { + *conceal_char = tv_get_string(&di->di_tv); + } + + if ((di = tv_dict_find(tv->vval.v_dict, S_LEN("window"))) != NULL) { + *win = find_win_by_nr_or_id(&di->di_tv); + if (*win == NULL) { + emsg(_(e_invalwindow)); + return FAIL; + } + } + + return OK; +} + +/// "clearmatches()" function +void f_clearmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + win_T *win = get_optional_window(argvars, 0); + + if (win != NULL) { + clear_matches(win); + } +} + +/// "getmatches()" function +void f_getmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + matchitem_T *cur; + int i; + win_T *win = get_optional_window(argvars, 0); + + if (win == NULL) { + return; + } + + tv_list_alloc_ret(rettv, kListLenMayKnow); + cur = win->w_match_head; + while (cur != NULL) { + dict_T *dict = tv_dict_alloc(); + if (cur->match.regprog == NULL) { + // match added with matchaddpos() + for (i = 0; i < MAXPOSMATCH; i++) { + llpos_T *llpos; + char buf[30]; // use 30 to avoid compiler warning + + llpos = &cur->pos.pos[i]; + if (llpos->lnum == 0) { + break; + } + list_T *const l = tv_list_alloc(1 + (llpos->col > 0 ? 2 : 0)); + tv_list_append_number(l, (varnumber_T)llpos->lnum); + if (llpos->col > 0) { + tv_list_append_number(l, (varnumber_T)llpos->col); + tv_list_append_number(l, (varnumber_T)llpos->len); + } + int len = snprintf(buf, sizeof(buf), "pos%d", i + 1); + assert((size_t)len < sizeof(buf)); + tv_dict_add_list(dict, buf, (size_t)len, l); + } + } else { + tv_dict_add_str(dict, S_LEN("pattern"), (const char *)cur->pattern); + } + tv_dict_add_str(dict, S_LEN("group"), + (const char *)syn_id2name(cur->hlg_id)); + tv_dict_add_nr(dict, S_LEN("priority"), (varnumber_T)cur->priority); + tv_dict_add_nr(dict, S_LEN("id"), (varnumber_T)cur->id); + + if (cur->conceal_char) { + char buf[MB_MAXBYTES + 1]; + + buf[utf_char2bytes(cur->conceal_char, (char_u *)buf)] = NUL; + tv_dict_add_str(dict, S_LEN("conceal"), buf); + } + + tv_list_append_dict(rettv->vval.v_list, dict); + cur = cur->next; + } +} + +/// "setmatches()" function +void f_setmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + dict_T *d; + list_T *s = NULL; + win_T *win = get_optional_window(argvars, 1); + + rettv->vval.v_number = -1; + if (argvars[0].v_type != VAR_LIST) { + emsg(_(e_listreq)); + return; + } + if (win == NULL) { + return; + } + + list_T *const l = argvars[0].vval.v_list; + // To some extent make sure that we are dealing with a list from + // "getmatches()". + int li_idx = 0; + TV_LIST_ITER_CONST(l, li, { + if (TV_LIST_ITEM_TV(li)->v_type != VAR_DICT + || (d = TV_LIST_ITEM_TV(li)->vval.v_dict) == NULL) { + semsg(_("E474: List item %d is either not a dictionary " + "or an empty one"), li_idx); + return; + } + if (!(tv_dict_find(d, S_LEN("group")) != NULL + && (tv_dict_find(d, S_LEN("pattern")) != NULL + || tv_dict_find(d, S_LEN("pos1")) != NULL) + && tv_dict_find(d, S_LEN("priority")) != NULL + && tv_dict_find(d, S_LEN("id")) != NULL)) { + semsg(_("E474: List item %d is missing one of the required keys"), + li_idx); + return; + } + li_idx++; + }); + + clear_matches(win); + bool match_add_failed = false; + TV_LIST_ITER_CONST(l, li, { + int i = 0; + + d = TV_LIST_ITEM_TV(li)->vval.v_dict; + dictitem_T *const di = tv_dict_find(d, S_LEN("pattern")); + if (di == NULL) { + if (s == NULL) { + s = tv_list_alloc(9); + } + + // match from matchaddpos() + for (i = 1; i < 9; i++) { + char buf[30]; // use 30 to avoid compiler warning + snprintf(buf, sizeof(buf), "pos%d", i); + dictitem_T *const pos_di = tv_dict_find(d, buf, -1); + if (pos_di != NULL) { + if (pos_di->di_tv.v_type != VAR_LIST) { + return; + } + + tv_list_append_tv(s, &pos_di->di_tv); + tv_list_ref(s); + } else { + break; + } + } + } + + // Note: there are three number buffers involved: + // - group_buf below. + // - numbuf in tv_dict_get_string(). + // - mybuf in tv_get_string(). + // + // If you change this code make sure that buffers will not get + // accidentally reused. + char group_buf[NUMBUFLEN]; + const char *const group = tv_dict_get_string_buf(d, "group", group_buf); + const int priority = (int)tv_dict_get_number(d, "priority"); + const int id = (int)tv_dict_get_number(d, "id"); + dictitem_T *const conceal_di = tv_dict_find(d, S_LEN("conceal")); + const char *const conceal = (conceal_di != NULL + ? tv_get_string(&conceal_di->di_tv) + : NULL); + if (i == 0) { + if (match_add(win, group, + tv_dict_get_string(d, "pattern", false), + priority, id, NULL, conceal) != id) { + match_add_failed = true; + } + } else { + if (match_add(win, group, NULL, priority, id, s, conceal) != id) { + match_add_failed = true; + } + tv_list_unref(s); + s = NULL; + } + }); + if (!match_add_failed) { + rettv->vval.v_number = 0; + } +} + +/// "matchadd()" function +void f_matchadd(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + char grpbuf[NUMBUFLEN]; + char patbuf[NUMBUFLEN]; + // group + const char *const grp = tv_get_string_buf_chk(&argvars[0], grpbuf); + // pattern + const char *const pat = tv_get_string_buf_chk(&argvars[1], patbuf); + // default priority + int prio = 10; + int id = -1; + bool error = false; + const char *conceal_char = NULL; + win_T *win = curwin; + + rettv->vval.v_number = -1; + + if (grp == NULL || pat == NULL) { + return; + } + if (argvars[2].v_type != VAR_UNKNOWN) { + prio = (int)tv_get_number_chk(&argvars[2], &error); + if (argvars[3].v_type != VAR_UNKNOWN) { + id = (int)tv_get_number_chk(&argvars[3], &error); + if (argvars[4].v_type != VAR_UNKNOWN + && matchadd_dict_arg(&argvars[4], &conceal_char, &win) == FAIL) { + return; + } + } + } + if (error) { + return; + } + if (id >= 1 && id <= 3) { + semsg(_("E798: ID is reserved for \":match\": %" PRId64), (int64_t)id); + return; + } + + rettv->vval.v_number = match_add(win, grp, pat, prio, id, NULL, conceal_char); +} + +/// "matchaddpo()" function +void f_matchaddpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->vval.v_number = -1; + + char buf[NUMBUFLEN]; + const char *const group = tv_get_string_buf_chk(&argvars[0], buf); + if (group == NULL) { + return; + } + + if (argvars[1].v_type != VAR_LIST) { + semsg(_(e_listarg), "matchaddpos()"); + return; + } + + list_T *l; + l = argvars[1].vval.v_list; + if (l == NULL) { + return; + } + + bool error = false; + int prio = 10; + int id = -1; + const char *conceal_char = NULL; + win_T *win = curwin; + + if (argvars[2].v_type != VAR_UNKNOWN) { + prio = (int)tv_get_number_chk(&argvars[2], &error); + if (argvars[3].v_type != VAR_UNKNOWN) { + id = (int)tv_get_number_chk(&argvars[3], &error); + if (argvars[4].v_type != VAR_UNKNOWN + && matchadd_dict_arg(&argvars[4], &conceal_char, &win) == FAIL) { + return; + } + } + } + if (error == true) { + return; + } + + // id == 3 is ok because matchaddpos() is supposed to substitute :3match + if (id == 1 || id == 2) { + semsg(_("E798: ID is reserved for \"match\": %" PRId64), (int64_t)id); + return; + } + + rettv->vval.v_number = match_add(win, group, NULL, prio, id, l, conceal_char); +} + +/// "matcharg()" function +void f_matcharg(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + const int id = (int)tv_get_number(&argvars[0]); + + tv_list_alloc_ret(rettv, (id >= 1 && id <= 3 + ? 2 + : 0)); + + if (id >= 1 && id <= 3) { + matchitem_T *const m = get_match(curwin, id); + + if (m != NULL) { + tv_list_append_string(rettv->vval.v_list, + (const char *)syn_id2name(m->hlg_id), -1); + tv_list_append_string(rettv->vval.v_list, (const char *)m->pattern, -1); + } else { + tv_list_append_string(rettv->vval.v_list, NULL, 0); + tv_list_append_string(rettv->vval.v_list, NULL, 0); + } + } +} + +/// "matchdelete()" function +void f_matchdelete(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + win_T *win = get_optional_window(argvars, 1); + if (win == NULL) { + rettv->vval.v_number = -1; + } else { + rettv->vval.v_number = match_delete(win, + (int)tv_get_number(&argvars[0]), true); + } +} + +/// ":[N]match {group} {pattern}" +/// Sets nextcmd to the start of the next command, if any. Also called when +/// skipping commands to find the next command. +void ex_match(exarg_T *eap) +{ + char_u *p; + char_u *g = NULL; + char_u *end; + int c; + int id; + + if (eap->line2 <= 3) { + id = (int)eap->line2; + } else { + emsg(e_invcmd); + return; + } + + // First clear any old pattern. + if (!eap->skip) { + match_delete(curwin, id, false); + } + + if (ends_excmd(*eap->arg)) { + end = eap->arg; + } else if ((STRNICMP(eap->arg, "none", 4) == 0 + && (ascii_iswhite(eap->arg[4]) || ends_excmd(eap->arg[4])))) { + end = eap->arg + 4; + } else { + p = skiptowhite(eap->arg); + if (!eap->skip) { + g = vim_strnsave(eap->arg, (size_t)(p - eap->arg)); + } + p = skipwhite(p); + if (*p == NUL) { + // There must be two arguments. + xfree(g); + semsg(_(e_invarg2), eap->arg); + return; + } + end = skip_regexp(p + 1, *p, true, NULL); + if (!eap->skip) { + if (*end != NUL && !ends_excmd(*skipwhite(end + 1))) { + xfree(g); + eap->errmsg = e_trailing; + return; + } + if (*end != *p) { + xfree(g); + semsg(_(e_invarg2), p); + return; + } + + c = *end; + *end = NUL; + match_add(curwin, (const char *)g, (const char *)p + 1, 10, id, + NULL, NULL); + xfree(g); + *end = (char_u)c; + } + } + eap->nextcmd = find_nextcmd(end); +} + diff --git a/src/nvim/match.h b/src/nvim/match.h new file mode 100644 index 0000000000..fdcec0ae05 --- /dev/null +++ b/src/nvim/match.h @@ -0,0 +1,12 @@ +#ifndef NVIM_MATCH_H +#define NVIM_MATCH_H + +#include "nvim/buffer_defs.h" +#include "nvim/eval/funcs.h" +#include "nvim/ex_cmds_defs.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "match.h.generated.h" +#endif + +#endif // NVIM_MATCH_H diff --git a/src/nvim/mbyte.c b/src/nvim/mbyte.c index 42117bc762..f634c7dda8 100644 --- a/src/nvim/mbyte.c +++ b/src/nvim/mbyte.c @@ -42,13 +42,13 @@ #include "nvim/eval.h" #include "nvim/fileio.h" #include "nvim/func_attr.h" +#include "nvim/getchar.h" #include "nvim/iconv.h" #include "nvim/mark.h" #include "nvim/mbyte.h" #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/message.h" -#include "nvim/misc1.h" #include "nvim/option.h" #include "nvim/os/os.h" #include "nvim/path.h" @@ -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; @@ -1609,7 +1615,8 @@ void show_utf8(void) msg((char *)IObuff); } -/// Return offset from "p" to the first byte of the character it points into. +/// Return offset from "p" to the start of a character, including composing characters. +/// "base" must be the start of the string, which must be NUL terminated. /// If "p" points to the NUL at the end of the string return 0. /// Returns 0 when already at the first byte of a character. int utf_head_off(const char_u *base, const char_u *p) @@ -1820,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; @@ -1850,11 +1855,10 @@ int mb_off_next(char_u *base, char_u *p) return i; } -/* - * Return the offset from "p" to the last byte of the character it points - * into. Can start anywhere in a stream of bytes. - */ -int mb_tail_off(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(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 08202a6d5c..5f6e1ea273 100644 --- a/src/nvim/memline.c +++ b/src/nvim/memline.c @@ -51,6 +51,7 @@ #include "nvim/fileio.h" #include "nvim/func_attr.h" #include "nvim/getchar.h" +#include "nvim/input.h" #include "nvim/main.h" #include "nvim/mark.h" #include "nvim/mbyte.h" @@ -58,7 +59,6 @@ #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/message.h" -#include "nvim/misc1.h" #include "nvim/option.h" #include "nvim/os/input.h" #include "nvim/os/os.h" @@ -230,7 +230,7 @@ static linenr_T lowest_marked = 0; #define ML_INSERT 0x12 // insert line #define ML_FIND 0x13 // just find the line #define ML_FLUSH 0x02 // flush locked block -#define ML_SIMPLE(x) (x & 0x10) // DEL, INS or FIND +#define ML_SIMPLE(x) ((x) & 0x10) // DEL, INS or FIND // argument for ml_upd_block0() typedef enum { @@ -242,11 +242,9 @@ typedef enum { # include "memline.c.generated.h" #endif -/* - * Open a new memline for "buf". - * - * Return FAIL for failure, OK otherwise. - */ +/// Open a new memline for "buf". +/// +/// @return FAIL for failure, OK otherwise. int ml_open(buf_T *buf) { bhdr_T *hp = NULL; @@ -303,7 +301,7 @@ int ml_open(buf_T *buf) b0p->b0_id[0] = BLOCK0_ID0; b0p->b0_id[1] = BLOCK0_ID1; - b0p->b0_magic_long = (long)B0_MAGIC_LONG; + b0p->b0_magic_long = B0_MAGIC_LONG; b0p->b0_magic_int = (int)B0_MAGIC_INT; b0p->b0_magic_short = (short)B0_MAGIC_SHORT; b0p->b0_magic_char = B0_MAGIC_CHAR; @@ -379,10 +377,8 @@ error: return FAIL; } -/* - * ml_setname() is called when the file name of "buf" has been changed. - * It may rename the swap file. - */ +/// ml_setname() is called when the file name of "buf" has been changed. +/// It may rename the swap file. void ml_setname(buf_T *buf) { bool success = false; @@ -458,11 +454,9 @@ void ml_setname(buf_T *buf) } } -/* - * Open a file for the memfile for all buffers that are not readonly or have - * been modified. - * Used when 'updatecount' changes from zero to non-zero. - */ +/// Open a file for the memfile for all buffers that are not readonly or have +/// been modified. +/// Used when 'updatecount' changes from zero to non-zero. void ml_open_files(void) { FOR_ALL_BUFFERS(buf) { @@ -472,11 +466,9 @@ void ml_open_files(void) } } -/* - * Open a swap file for an existing memfile, if there is no swap file yet. - * If we are unable to find a file name, mf_fname will be NULL - * and the memfile will be in memory only (no recovery possible). - */ +/// Open a swap file for an existing memfile, if there is no swap file yet. +/// If we are unable to find a file name, mf_fname will be NULL +/// and the memfile will be in memory only (no recovery possible). void ml_open_file(buf_T *buf) { memfile_T *mfp; @@ -563,10 +555,9 @@ void check_need_swap(bool newfile) msg_silent = old_msg_silent; } -/* - * Close memline for buffer 'buf'. - * If 'del_file' is TRUE, delete the swap file - */ +/// Close memline for buffer 'buf'. +/// +/// @param del_file if TRUE, delete the swap file void ml_close(buf_T *buf, int del_file) { if (buf->b_ml.ml_mfp == NULL) { // not open @@ -585,25 +576,21 @@ void ml_close(buf_T *buf, int del_file) buf->b_flags &= ~BF_RECOVERED; } -/* - * Close all existing memlines and memfiles. - * Only used when exiting. - * When 'del_file' is TRUE, delete the memfiles. - * But don't delete files that were ":preserve"d when we are POSIX compatible. - */ -void ml_close_all(int del_file) +/// Close all existing memlines and memfiles. +/// Only used when exiting. +/// +/// @param del_file if true, delete the memfiles. +void ml_close_all(bool del_file) { FOR_ALL_BUFFERS(buf) { - ml_close(buf, del_file && ((buf->b_flags & BF_PRESERVED) == 0)); + ml_close(buf, del_file); } spell_delete_wordlist(); // delete the internal wordlist vim_deltempdir(); // delete created temp directory } -/* - * Close all memfiles for not modified buffers. - * Only use just before exiting! - */ +/// Close all memfiles for not modified buffers. +/// Only use just before exiting! void ml_close_notmod(void) { FOR_ALL_BUFFERS(buf) { @@ -613,10 +600,8 @@ void ml_close_notmod(void) } } -/* - * Update the timestamp in the .swp file. - * Used when the file has been written. - */ +/// Update the timestamp in the .swp file. +/// Used when the file has been written. void ml_timestamp(buf_T *buf) { ml_upd_block0(buf, UB_FNAME); @@ -639,9 +624,7 @@ static bool ml_check_b0_strings(ZERO_BL *b0p) && memchr(b0p->b0_fname, NUL, B0_FNAME_SIZE_CRYPT)); // -V512 } -/* - * Update the timestamp or the B0_SAME_DIR flag of the .swp file. - */ +/// Update the timestamp or the B0_SAME_DIR flag of the .swp file. static void ml_upd_block0(buf_T *buf, upd_block0_T what) { memfile_T *mfp; @@ -665,11 +648,9 @@ static void ml_upd_block0(buf_T *buf, upd_block0_T what) mf_put(mfp, hp, true, false); } -/* - * Write file name and timestamp into block 0 of a swap file. - * Also set buf->b_mtime. - * Don't use NameBuff[]!!! - */ +/// Write file name and timestamp into block 0 of a swap file. +/// Also set buf->b_mtime. +/// Don't use NameBuff[]!!! static void set_b0_fname(ZERO_BL *b0p, buf_T *buf) { if (buf->b_ffname == NULL) { @@ -704,11 +685,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; } @@ -718,12 +702,10 @@ static void set_b0_fname(ZERO_BL *b0p, buf_T *buf) add_b0_fenc(b0p, curbuf); } -/* - * Update the B0_SAME_DIR flag of the swap file. It's set if the file and the - * swapfile for "buf" are in the same directory. - * This is fail safe: if we are not sure the directories are equal the flag is - * not set. - */ +/// Update the B0_SAME_DIR flag of the swap file. It's set if the file and the +/// swapfile for "buf" are in the same directory. +/// This is fail safe: if we are not sure the directories are equal the flag is +/// not set. static void set_b0_dir_flag(ZERO_BL *b0p, buf_T *buf) { if (same_directory(buf->b_ml.ml_mfp->mf_fname, buf->b_ffname)) { @@ -733,9 +715,7 @@ static void set_b0_dir_flag(ZERO_BL *b0p, buf_T *buf) } } -/* - * When there is room, add the 'fileencoding' to block zero. - */ +/// When there is room, add the 'fileencoding' to block zero. static void add_b0_fenc(ZERO_BL *b0p, buf_T *buf) { int n; @@ -754,8 +734,9 @@ static void add_b0_fenc(ZERO_BL *b0p, buf_T *buf) /// Try to recover curbuf from the .swp file. -/// @param checkext If true, check the extension and detect whether it is a -/// swap file. +/// +/// @param checkext if true, check the extension and detect whether it is a +/// swap file. void ml_recover(bool checkext) { buf_T *buf = NULL; @@ -1032,9 +1013,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; @@ -1462,10 +1443,8 @@ int recover_names(char_u *fname, int list, int nr, char_u **fname_out) return file_count; } -/* - * Append the full path to name with path separators made into percent - * signs, to dir. An unnamed buffer is handled as "" (<currentdir>/"") - */ +/// Append the full path to name with path separators made into percent +/// signs, to dir. An unnamed buffer is handled as "" (<currentdir>/"") char *make_percent_swname(const char *dir, const char *name) FUNC_ATTR_NONNULL_ARG(1) { @@ -1487,8 +1466,9 @@ char *make_percent_swname(const char *dir, const char *name) static bool process_still_running; -/// Return information found in swapfile "fname" in dictionary "d". /// This is used by the swapinfo() function. +/// +/// @return information found in swapfile "fname" in dictionary "d". void get_b0_dict(const char *fname, dict_T *d) { int fd; @@ -1525,7 +1505,8 @@ void get_b0_dict(const char *fname, dict_T *d) } /// Give information about an existing swap file. -/// Returns timestamp (0 when unknown). +/// +/// @return timestamp (0 when unknown). static time_t swapfile_info(char_u *fname) { assert(fname != NULL); @@ -1615,9 +1596,9 @@ static time_t swapfile_info(char_u *fname) return x; } -/// Returns TRUE if the swap file looks OK and there are no changes, thus it -/// can be safely deleted. -static time_t swapfile_unchanged(char *fname) +/// @return true if the swap file looks OK and there are no changes, thus it +/// can be safely deleted. +static bool swapfile_unchanged(char *fname) { struct block0 b0; int ret = true; @@ -1695,13 +1676,12 @@ static int recov_file_names(char_u **names, char_u *path, int prepend_dot) return num_names; } -/* - * sync all memlines - * - * If 'check_file' is TRUE, check if original file exists and was not changed. - * If 'check_char' is TRUE, stop syncing when character becomes available, but - * always sync at least one block. - */ +/// sync all memlines +/// +/// @param check_file if TRUE, check if original file exists and was not changed. +/// @param check_char if TRUE, stop syncing when character becomes available, but +/// +/// always sync at least one block. void ml_sync_all(int check_file, int check_char, bool do_fsync) { FOR_ALL_BUFFERS(buf) { @@ -1720,6 +1700,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; @@ -1736,16 +1717,14 @@ void ml_sync_all(int check_file, int check_char, bool do_fsync) } } -/* - * sync one buffer, including negative blocks - * - * after this all the blocks are in the swap file - * - * Used for the :preserve command and when the original file has been - * changed or deleted. - * - * when message is TRUE the success of preserving is reported - */ +/// sync one buffer, including negative blocks +/// +/// after this all the blocks are in the swap file +/// +/// Used for the :preserve command and when the original file has been +/// changed or deleted. +/// +/// @param message if TRUE, the success of preserving is reported. void ml_preserve(buf_T *buf, int message, bool do_fsync) { bhdr_T *hp; @@ -1821,29 +1800,37 @@ theend: * line2 = ml_get(2); // line1 is now invalid! * Make a copy of the line if necessary. */ -/* - * Return a pointer to a (read-only copy of a) line. - * - * On failure an error message is given and IObuff is returned (to avoid - * having to check for error everywhere). - */ + +/// @return a pointer to a (read-only copy of a) line. +/// +/// On failure an error message is given and IObuff is returned (to avoid +/// having to check for error everywhere). char_u *ml_get(linenr_T lnum) { return ml_get_buf(curbuf, lnum, false); } -/* - * Return pointer to position "pos". - */ +/// @return pointer to position "pos". char_u *ml_get_pos(const pos_T *pos) FUNC_ATTR_NONNULL_ALL { return ml_get_buf(curbuf, pos->lnum, false) + pos->col; } -/// Return a pointer to a line in a specific buffer -/// +/// @return codepoint at pos. pos must be either valid or have col set to MAXCOL! +int gchar_pos(pos_T *pos) + FUNC_ATTR_NONNULL_ARG(1) +{ + // When searching columns is sometimes put at the end of a line. + if (pos->col == MAXCOL) { + return NUL; + } + return utf_ptr2char(ml_get_pos(pos)); +} + /// @param will_change true mark the buffer dirty (chars in the line will be changed) +/// +/// @return a pointer to a line in a specific buffer char_u *ml_get_buf(buf_T *buf, linenr_T lnum, bool will_change) FUNC_ATTR_NONNULL_ALL { @@ -1916,10 +1903,8 @@ errorret: return buf->b_ml.ml_line_ptr; } -/* - * Check if a line that was just obtained by a call to ml_get - * is in allocated memory. - */ +/// Check if a line that was just obtained by a call to ml_get +/// is in allocated memory. int ml_line_alloced(void) { return curbuf->b_ml.ml_flags & ML_LINE_DIRTY; @@ -2342,95 +2327,88 @@ static int ml_append_int(buf_T *buf, linenr_T lnum, char_u *line, colnr_T len, b * We are finished, break the loop here. */ break; - } else { // pointer block full - /* - * split the pointer block - * allocate a new pointer block - * move some of the pointer into the new block - * prepare for updating the parent block - */ - for (;;) { // do this twice when splitting block 1 - hp_new = ml_new_ptr(mfp); - if (hp_new == NULL) { // TODO: try to fix tree - return FAIL; - } - pp_new = hp_new->bh_data; - - if (hp->bh_bnum != 1) { - break; - } - - /* - * if block 1 becomes full the tree is given an extra level - * The pointers from block 1 are moved into the new block. - * block 1 is updated to point to the new block - * then continue to split the new block - */ - memmove(pp_new, pp, (size_t)page_size); - pp->pb_count = 1; - pp->pb_pointer[0].pe_bnum = hp_new->bh_bnum; - pp->pb_pointer[0].pe_line_count = buf->b_ml.ml_line_count; - pp->pb_pointer[0].pe_old_lnum = 1; - pp->pb_pointer[0].pe_page_count = 1; - mf_put(mfp, hp, true, false); // release block 1 - hp = hp_new; // new block is to be split - pp = pp_new; - CHECK(stack_idx != 0, _("stack_idx should be 0")); - ip->ip_index = 0; - ++stack_idx; // do block 1 again later - } - /* - * move the pointers after the current one to the new block - * If there are none, the new entry will be in the new block. - */ - total_moved = pp->pb_count - pb_idx - 1; - if (total_moved) { - memmove(&pp_new->pb_pointer[0], - &pp->pb_pointer[pb_idx + 1], - (size_t)(total_moved) * sizeof(PTR_EN)); - pp_new->pb_count = total_moved; - pp->pb_count -= total_moved - 1; - pp->pb_pointer[pb_idx + 1].pe_bnum = bnum_right; - pp->pb_pointer[pb_idx + 1].pe_line_count = line_count_right; - pp->pb_pointer[pb_idx + 1].pe_page_count = page_count_right; - if (lnum_right) { - pp->pb_pointer[pb_idx + 1].pe_old_lnum = lnum_right; - } - } else { - pp_new->pb_count = 1; - pp_new->pb_pointer[0].pe_bnum = bnum_right; - pp_new->pb_pointer[0].pe_line_count = line_count_right; - pp_new->pb_pointer[0].pe_page_count = page_count_right; - pp_new->pb_pointer[0].pe_old_lnum = lnum_right; - } - pp->pb_pointer[pb_idx].pe_bnum = bnum_left; - pp->pb_pointer[pb_idx].pe_line_count = line_count_left; - pp->pb_pointer[pb_idx].pe_page_count = page_count_left; - if (lnum_left) { - pp->pb_pointer[pb_idx].pe_old_lnum = lnum_left; + } + // pointer block full + // + // split the pointer block + // allocate a new pointer block + // move some of the pointer into the new block + // prepare for updating the parent block + for (;;) { // do this twice when splitting block 1 + hp_new = ml_new_ptr(mfp); + if (hp_new == NULL) { // TODO(vim): try to fix tree + return FAIL; } - lnum_left = 0; - lnum_right = 0; + pp_new = hp_new->bh_data; - /* - * recompute line counts - */ - line_count_right = 0; - for (i = 0; i < (int)pp_new->pb_count; ++i) { - line_count_right += pp_new->pb_pointer[i].pe_line_count; + if (hp->bh_bnum != 1) { + break; } - line_count_left = 0; - for (i = 0; i < (int)pp->pb_count; ++i) { - line_count_left += pp->pb_pointer[i].pe_line_count; + + // if block 1 becomes full the tree is given an extra level + // The pointers from block 1 are moved into the new block. + // block 1 is updated to point to the new block + // then continue to split the new block + memmove(pp_new, pp, (size_t)page_size); + pp->pb_count = 1; + pp->pb_pointer[0].pe_bnum = hp_new->bh_bnum; + pp->pb_pointer[0].pe_line_count = buf->b_ml.ml_line_count; + pp->pb_pointer[0].pe_old_lnum = 1; + pp->pb_pointer[0].pe_page_count = 1; + mf_put(mfp, hp, true, false); // release block 1 + hp = hp_new; // new block is to be split + pp = pp_new; + CHECK(stack_idx != 0, _("stack_idx should be 0")); + ip->ip_index = 0; + stack_idx++; // do block 1 again later + } + // move the pointers after the current one to the new block + // If there are none, the new entry will be in the new block. + total_moved = pp->pb_count - pb_idx - 1; + if (total_moved) { + memmove(&pp_new->pb_pointer[0], + &pp->pb_pointer[pb_idx + 1], + (size_t)(total_moved) * sizeof(PTR_EN)); + pp_new->pb_count = total_moved; + pp->pb_count -= total_moved - 1; + pp->pb_pointer[pb_idx + 1].pe_bnum = bnum_right; + pp->pb_pointer[pb_idx + 1].pe_line_count = line_count_right; + pp->pb_pointer[pb_idx + 1].pe_page_count = page_count_right; + if (lnum_right) { + pp->pb_pointer[pb_idx + 1].pe_old_lnum = lnum_right; } + } else { + pp_new->pb_count = 1; + pp_new->pb_pointer[0].pe_bnum = bnum_right; + pp_new->pb_pointer[0].pe_line_count = line_count_right; + pp_new->pb_pointer[0].pe_page_count = page_count_right; + pp_new->pb_pointer[0].pe_old_lnum = lnum_right; + } + pp->pb_pointer[pb_idx].pe_bnum = bnum_left; + pp->pb_pointer[pb_idx].pe_line_count = line_count_left; + pp->pb_pointer[pb_idx].pe_page_count = page_count_left; + if (lnum_left) { + pp->pb_pointer[pb_idx].pe_old_lnum = lnum_left; + } + lnum_left = 0; + lnum_right = 0; - bnum_left = hp->bh_bnum; - bnum_right = hp_new->bh_bnum; - page_count_left = 1; - page_count_right = 1; - mf_put(mfp, hp, true, false); - mf_put(mfp, hp_new, true, false); + // recompute line counts + line_count_right = 0; + for (i = 0; i < (int)pp_new->pb_count; i++) { + line_count_right += pp_new->pb_pointer[i].pe_line_count; + } + line_count_left = 0; + for (i = 0; i < (int)pp->pb_count; i++) { + line_count_left += pp->pb_pointer[i].pe_line_count; } + + bnum_left = hp->bh_bnum; + bnum_right = hp_new->bh_bnum; + page_count_left = 1; + page_count_right = 1; + mf_put(mfp, hp, true, false); + mf_put(mfp, hp_new, true, false); } /* @@ -2476,17 +2454,18 @@ int ml_replace(linenr_T lnum, char_u *line, bool copy) return ml_replace_buf(curbuf, lnum, line, copy); } -// Replace line "lnum", with buffering, in current buffer. -// -// If "copy" is true, make a copy of the line, otherwise the line has been -// copied to allocated memory already. -// If "copy" is false the "line" may be freed to add text properties! -// Do not use it after calling ml_replace(). -// -// Check: The caller of this function should probably also call -// changed_lines(), unless update_screen(NOT_VALID) is used. -// -// return FAIL for failure, OK otherwise +/// Replace line "lnum", with buffering, in current buffer. +/// +/// @param copy if true, make a copy of the line, otherwise the line has been +/// copied to allocated memory already. +/// if false, the "line" may be freed to add text properties! +/// +/// Do not use it after calling ml_replace(). +/// +/// Check: The caller of this function should probably also call +/// changed_lines(), unless update_screen(NOT_VALID) is used. +/// +/// @return FAIL for failure, OK otherwise int ml_replace_buf(buf_T *buf, linenr_T lnum, char_u *line, bool copy) { if (line == NULL) { // just checking... @@ -2529,7 +2508,8 @@ int ml_replace_buf(buf_T *buf, linenr_T lnum, char_u *line, bool copy) /// deleted_lines() after this. /// /// @param message Show "--No lines in buffer--" message. -/// @return FAIL for failure, OK otherwise +/// +/// @return FAIL for failure, OK otherwise int ml_delete(linenr_T lnum, bool message) { ml_flush_line(curbuf); @@ -2686,9 +2666,7 @@ static int ml_delete_int(buf_T *buf, linenr_T lnum, bool message) return OK; } -/* - * set the B_MARKED flag for line 'lnum' - */ +/// set the B_MARKED flag for line 'lnum' void ml_setmarked(linenr_T lnum) { bhdr_T *hp; @@ -2715,9 +2693,7 @@ void ml_setmarked(linenr_T lnum) curbuf->b_ml.ml_flags |= ML_LOCKED_DIRTY; } -/* - * find the first line with its B_MARKED flag set - */ +/// find the first line with its B_MARKED flag set linenr_T ml_firstmarked(void) { bhdr_T *hp; @@ -2758,9 +2734,7 @@ linenr_T ml_firstmarked(void) return (linenr_T)0; } -/* - * clear all DB_MARKED flags - */ +/// clear all DB_MARKED flags void ml_clearmarked(void) { bhdr_T *hp; @@ -2809,9 +2783,7 @@ size_t ml_flush_deleted_bytes(buf_T *buf, size_t *codepoints, size_t *codeunits) return ret; } -/* - * flush ml_line if necessary - */ +/// flush ml_line if necessary static void ml_flush_line(buf_T *buf) { bhdr_T *hp; @@ -2908,9 +2880,7 @@ static void ml_flush_line(buf_T *buf) buf->b_ml.ml_line_offset = 0; } -/* - * create a new, empty, data block - */ +/// create a new, empty, data block static bhdr_T *ml_new_data(memfile_T *mfp, bool negative, int page_count) { assert(page_count >= 0); @@ -2924,9 +2894,7 @@ static bhdr_T *ml_new_data(memfile_T *mfp, bool negative, int page_count) return hp; } -/* - * create a new, empty, pointer block - */ +/// create a new, empty, pointer block static bhdr_T *ml_new_ptr(memfile_T *mfp) { bhdr_T *hp = mf_new(mfp, false, 1); @@ -2938,21 +2906,19 @@ static bhdr_T *ml_new_ptr(memfile_T *mfp) return hp; } -/* - * lookup line 'lnum' in a memline - * - * action: if ML_DELETE or ML_INSERT the line count is updated while searching - * if ML_FLUSH only flush a locked block - * if ML_FIND just find the line - * - * If the block was found it is locked and put in ml_locked. - * The stack is updated to lead to the locked block. The ip_high field in - * the stack is updated to reflect the last line in the block AFTER the - * insert or delete, also if the pointer block has not been updated yet. But - * if ml_locked != NULL ml_locked_lineadd must be added to ip_high. - * - * return: NULL for failure, pointer to block header otherwise - */ +/// lookup line 'lnum' in a memline +/// +/// @param action: if ML_DELETE or ML_INSERT the line count is updated while searching +/// if ML_FLUSH only flush a locked block +/// if ML_FIND just find the line +/// +/// If the block was found it is locked and put in ml_locked. +/// The stack is updated to lead to the locked block. The ip_high field in +/// the stack is updated to reflect the last line in the block AFTER the +/// insert or delete, also if the pointer block has not been updated yet. But +/// if ml_locked != NULL ml_locked_lineadd must be added to ip_high. +/// +/// @return NULL for failure, pointer to block header otherwise static bhdr_T *ml_find_line(buf_T *buf, linenr_T lnum, int action) { DATA_BL *dp; @@ -3133,11 +3099,9 @@ error_noblock: return NULL; } -/* - * add an entry to the info pointer stack - * - * return number of the new entry - */ +/// add an entry to the info pointer stack +/// +/// @return number of the new entry static int ml_add_stack(buf_T *buf) { int top = buf->b_ml.ml_stack_top; @@ -3155,16 +3119,14 @@ static int ml_add_stack(buf_T *buf) return top; } -/* - * Update the pointer blocks on the stack for inserted/deleted lines. - * The stack itself is also updated. - * - * When an insert/delete line action fails, the line is not inserted/deleted, - * but the pointer blocks have already been updated. That is fixed here by - * walking through the stack. - * - * Count is the number of lines added, negative if lines have been deleted. - */ +/// Update the pointer blocks on the stack for inserted/deleted lines. +/// The stack itself is also updated. +/// +/// When an insert/delete line action fails, the line is not inserted/deleted, +/// but the pointer blocks have already been updated. That is fixed here by +/// walking through the stack. +/// +/// Count is the number of lines added, negative if lines have been deleted. static void ml_lineadd(buf_T *buf, int count) { int idx; @@ -3191,13 +3153,13 @@ static void ml_lineadd(buf_T *buf, int count) } #if defined(HAVE_READLINK) -/* - * Resolve a symlink in the last component of a file name. - * Note that f_resolve() does it for every part of the path, we don't do that - * here. - * If it worked returns OK and the resolved link in "buf[MAXPATHL]". - * Otherwise returns FAIL. - */ + +/// Resolve a symlink in the last component of a file name. +/// Note that f_resolve() does it for every part of the path, we don't do that +/// here. +/// +/// @return OK if it worked and the resolved link in "buf[MAXPATHL]", +/// FAIL otherwise int resolve_symlink(const char_u *fname, char_u *buf) { char_u tmp[MAXPATHL]; @@ -3261,10 +3223,9 @@ int resolve_symlink(const char_u *fname, char_u *buf) } #endif -/* - * Make swap file name out of the file name and a directory name. - * Returns pointer to allocated memory or NULL. - */ +/// Make swap file name out of the file name and a directory name. +/// +/// @return pointer to allocated memory or NULL. char_u *makeswapname(char_u *fname, char_u *ffname, buf_T *buf, char_u *dir_name) { char_u *r, *s; @@ -3394,17 +3355,16 @@ static void attention_message(buf_T *buf, char_u *fname) } -/* - * Trigger the SwapExists autocommands. - * Returns a value for equivalent to do_dialog() (see below): - * 0: still need to ask for a choice - * 1: open read-only - * 2: edit anyway - * 3: recover - * 4: delete it - * 5: quit - * 6: abort - */ +/// Trigger the SwapExists autocommands. +/// +/// @return a value for equivalent to do_dialog() (see below): +/// 0: still need to ask for a choice +/// 1: open read-only +/// 2: edit anyway +/// 3: recover +/// 4: delete it +/// 5: quit +/// 6: abort static int do_swapexists(buf_T *buf, char_u *fname) { set_vim_var_string(VV_SWAPNAME, (char *)fname, -1); @@ -3694,7 +3654,7 @@ static char *findswapname(buf_T *buf, char **dirp, char *old_fname, bool *found_ static int b0_magic_wrong(ZERO_BL *b0p) { - return b0p->b0_magic_long != (long)B0_MAGIC_LONG + return b0p->b0_magic_long != B0_MAGIC_LONG || b0p->b0_magic_int != (int)B0_MAGIC_INT || b0p->b0_magic_short != (short)B0_MAGIC_SHORT || b0p->b0_magic_char != B0_MAGIC_CHAR; @@ -3798,10 +3758,8 @@ static bool fnamecmp_ino(char_u *fname_c, char_u *fname_s, long ino_block0) return true; } -/* - * Move a long integer into a four byte character array. - * Used for machine independency in block zero. - */ +/// Move a long integer into a four byte character array. +/// Used for machine independency in block zero. static void long_to_char(long n, char_u *s) { s[0] = (char_u)(n & 0xff); @@ -3828,12 +3786,10 @@ static long char_to_long(char_u *s) return retval; } -/* - * Set the flags in the first block of the swap file: - * - file is modified or not: buf->b_changed - * - 'fileformat' - * - 'fileencoding' - */ +/// Set the flags in the first block of the swap file: +/// - file is modified or not: buf->b_changed +/// - 'fileformat' +/// - 'fileencoding' void ml_setflags(buf_T *buf) { bhdr_T *hp; @@ -3859,13 +3815,13 @@ void ml_setflags(buf_T *buf) #define MLCS_MAXL 800 // max no of lines in chunk #define MLCS_MINL 400 // should be half of MLCS_MAXL -/* - * Keep information for finding byte offset of a line, updtype may be one of: - * ML_CHNK_ADDLINE: Add len to parent chunk, possibly splitting it - * Careful: ML_CHNK_ADDLINE may cause ml_find_line() to be called. - * ML_CHNK_DELLINE: Subtract len from parent chunk, possibly deleting it - * ML_CHNK_UPDLINE: Add len to parent chunk, as a signed entity. - */ +/// Keep information for finding byte offset of a line +/// +/// @param updtype may be one of: +/// ML_CHNK_ADDLINE: Add len to parent chunk, possibly splitting it +/// Careful: ML_CHNK_ADDLINE may cause ml_find_line() to be called. +/// ML_CHNK_DELLINE: Subtract len from parent chunk, possibly deleting it +/// ML_CHNK_UPDLINE: Add len to parent chunk, as a signed entity. static void ml_updatechunk(buf_T *buf, linenr_T line, long len, int updtype) { static buf_T *ml_upd_lastbuf = NULL; @@ -4068,7 +4024,7 @@ static void ml_updatechunk(buf_T *buf, linenr_T line, long len, int updtype) /// Should be NULL when getting offset of line /// @param no_ff ignore 'fileformat' option, always use one byte for NL. /// -/// @return -1 if information is not available +/// @return -1 if information is not available long ml_find_line_or_offset(buf_T *buf, linenr_T lnum, long *offp, bool no_ff) { linenr_T curline; @@ -4128,7 +4084,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) { @@ -4243,10 +4199,11 @@ void goto_byte(long cnt) } /// Increment the line pointer "lp" crossing line boundaries as necessary. -/// Return 1 when going to the next line. -/// Return 2 when moving forward onto a NUL at the end of the line). -/// Return -1 when at the end of file. -/// Return 0 otherwise. +/// +/// @return 1 when going to the next line. +/// 2 when moving forward onto a NUL at the end of the line). +/// -1 when at the end of file. +/// 0 otherwise. int inc(pos_T *lp) { // when searching position may be set to end of a line diff --git a/src/nvim/memory.c b/src/nvim/memory.c index 9683499eae..14930238e5 100644 --- a/src/nvim/memory.c +++ b/src/nvim/memory.c @@ -10,14 +10,14 @@ #include "nvim/api/extmark.h" #include "nvim/context.h" -#include "nvim/decoration.h" +#include "nvim/decoration_provider.h" #include "nvim/eval.h" #include "nvim/highlight.h" +#include "nvim/highlight_group.h" #include "nvim/lua/executor.h" #include "nvim/memfile.h" #include "nvim/memory.h" #include "nvim/message.h" -#include "nvim/misc1.h" #include "nvim/sign.h" #include "nvim/ui.h" #include "nvim/vim.h" @@ -573,7 +573,6 @@ void free_all_mem(void) return; } entered_free_all_mem = true; - // Don't want to trigger autocommands from here on. block_autocmds(); @@ -691,7 +690,7 @@ void free_all_mem(void) bufref_T bufref; set_bufref(&bufref, buf); nextbuf = buf->b_next; - close_buffer(NULL, buf, DOBUF_WIPE, false); + close_buffer(NULL, buf, DOBUF_WIPE, false, false); // Didn't work, try next one. buf = bufref_valid(&bufref) ? nextbuf : firstbuf; } diff --git a/src/nvim/menu.c b/src/nvim/menu.c index d596b31062..0db9d69a7e 100644 --- a/src/nvim/menu.c +++ b/src/nvim/menu.c @@ -22,7 +22,6 @@ #include "nvim/memory.h" #include "nvim/menu.h" #include "nvim/message.h" -#include "nvim/misc1.h" #include "nvim/screen.h" #include "nvim/state.h" #include "nvim/strings.h" @@ -362,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 6fcd4cef8a..6708001495 100644 --- a/src/nvim/message.c +++ b/src/nvim/message.c @@ -23,12 +23,12 @@ #include "nvim/garray.h" #include "nvim/getchar.h" #include "nvim/highlight.h" +#include "nvim/input.h" #include "nvim/keymap.h" #include "nvim/main.h" #include "nvim/mbyte.h" #include "nvim/memory.h" #include "nvim/message.h" -#include "nvim/misc1.h" #include "nvim/mouse.h" #include "nvim/normal.h" #include "nvim/ops.h" @@ -76,6 +76,8 @@ static int msg_hist_len = 0; static FILE *verbose_fd = NULL; static int verbose_did_open = FALSE; +bool keep_msg_more = false; // keep_msg was set by msgmore() + /* * When writing messages to the screen, there are many different situations. * A number of variables is used to remember the current state: @@ -204,11 +206,10 @@ void msg_grid_validate(void) } } -/* - * msg(s) - displays the string 's' on the status line - * When terminal not initialized (yet) mch_errmsg(..) is used. - * return TRUE if wait_return not called - */ +/// Displays the string 's' on the status line +/// When terminal not initialized (yet) mch_errmsg(..) is used. +/// +/// @return TRUE if wait_return not called int msg(char *s) { return msg_attr_keep(s, 0, false, false); @@ -230,7 +231,7 @@ int msg_attr(const char *s, const int attr) return msg_attr_keep(s, attr, false, false); } -/// similar to msg_outtrans_attr, but support newlines and tabs. +/// Similar to msg_outtrans_attr, but support newlines and tabs. void msg_multiline_attr(const char *s, int attr, bool check_int, bool *need_clear) FUNC_ATTR_NONNULL_ALL { @@ -260,7 +261,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; } @@ -327,18 +327,20 @@ 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) - * Columns + sc_col) { + if (keep && retval && vim_strsize((char_u *)s) < (Rows - cmdline_row - 1) * Columns + sc_col) { set_keep_msg((char *)s, 0); } + need_fileinfo = false; + xfree(buf); --entered; return retval; } /// Truncate a string such that it can be printed without causing a scroll. -/// Returns an allocated string or NULL when no truncating is done. +/// +/// @return an allocated string or NULL when no truncating is done. /// /// @param force always truncate char_u *msg_strtrunc(char_u *s, int force) @@ -354,10 +356,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 @@ -370,10 +372,8 @@ char_u *msg_strtrunc(char_u *s, int force) return buf; } -/* - * Truncate a string "s" to "buf" with cell width "room". - * "s" and "buf" may be equal. - */ +/// Truncate a string "s" to "buf" with cell width "room". +/// "s" and "buf" may be equal. void trunc_string(char_u *s, char_u *buf, int room_in, int buflen) { size_t room = room_in - 3; // "..." takes 3 chars @@ -383,6 +383,13 @@ void trunc_string(char_u *s, char_u *buf, int room_in, int buflen) int i; int n; + if (*s == NUL) { + if (buflen > 0) { + *buf = NUL; + } + return; + } + if (room_in < 3) { room = 0; } @@ -497,19 +504,15 @@ int smsg_attr_keep(int attr, const char *s, ...) static int last_sourcing_lnum = 0; static char_u *last_sourcing_name = NULL; -/* - * Reset the last used sourcing name/lnum. Makes sure it is displayed again - * for the next error message; - */ +/// Reset the last used sourcing name/lnum. Makes sure it is displayed again +/// for the next error message; void reset_last_sourcing(void) { XFREE_CLEAR(last_sourcing_name); last_sourcing_lnum = 0; } -/* - * Return TRUE if "sourcing_name" differs from "last_sourcing_name". - */ +/// @return TRUE if "sourcing_name" differs from "last_sourcing_name". static int other_sourcing_name(void) { if (sourcing_name != NULL) { @@ -559,11 +562,9 @@ static char *get_emsg_lnum(void) return NULL; } -/* - * Display name and line number for the source of an error. - * Remember the file name and line number, so that for the next error the info - * is only displayed if it changed. - */ +/// Display name and line number for the source of an error. +/// Remember the file name and line number, so that for the next error the info +/// is only displayed if it changed. void msg_source(int attr) { no_wait_return++; @@ -591,12 +592,10 @@ void msg_source(int attr) --no_wait_return; } -/* - * Return TRUE if not giving error messages right now: - * If "emsg_off" is set: no error messages at the moment. - * If "msg" is in 'debug': do error message but without side effects. - * If "emsg_skip" is set: never do error messages. - */ +/// @return TRUE if not giving error messages right now: +/// If "emsg_off" is set: no error messages at the moment. +/// If "msg" is in 'debug': do error message but without side effects. +/// If "emsg_skip" is set: never do error messages. int emsg_not_now(void) { if ((emsg_off > 0 && vim_strchr(p_debug, 'm') == NULL @@ -839,13 +838,12 @@ void msg_schedule_semsg(const char *const fmt, ...) multiqueue_put(main_loop.events, msg_semsg_event, 1, s); } -/* - * Like msg(), but truncate to a single line if p_shm contains 't', or when - * "force" is TRUE. This truncates in another way as for normal messages. - * Careful: The string may be changed by msg_may_trunc()! - * Returns a pointer to the printed message, if wait_return() not called. - */ -char *msg_trunc_attr(char *s, int force, int attr) +/// Like msg(), but truncate to a single line if p_shm contains 't', or when +/// "force" is true. This truncates in another way as for normal messages. +/// Careful: The string may be changed by msg_may_trunc()! +/// +/// @return a pointer to the printed message, if wait_return() not called. +char *msg_trunc_attr(char *s, bool force, int attr) { int n; @@ -864,16 +862,16 @@ char *msg_trunc_attr(char *s, int force, int attr) return NULL; } -/* - * Check if message "s" should be truncated at the start (for filenames). - * Return a pointer to where the truncated message starts. - * Note: May change the message by replacing a character with '<'. - */ -char_u *msg_may_trunc(int force, char_u *s) +/// Check if message "s" should be truncated at the start (for filenames). +/// +/// @return a pointer to where the truncated message starts. +/// +/// @note: May change the message by replacing a character with '<'. +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); @@ -968,10 +966,9 @@ static void add_msg_hist(const char *s, int len, int attr, bool multiline) msg_hist_len++; } -/* - * Delete the first (oldest) message from the history. - * Returns FAIL if there are no messages. - */ +/// Delete the first (oldest) message from the history. +/// +/// @return FAIL if there are no messages. int delete_first_msg(void) { struct msg_hist *p; @@ -1057,10 +1054,8 @@ void ex_messages(void *const eap_p) } } -/* - * Call this after prompting the user. This will avoid a hit-return message - * and a delay. - */ +/// Call this after prompting the user. This will avoid a hit-return message +/// and a delay. void msg_end_prompt(void) { msg_ext_clear_later(); @@ -1074,9 +1069,9 @@ void msg_end_prompt(void) /// Wait for the user to hit a key (normally Enter) /// -/// If 'redraw' is true, redraw the entire screen NOT_VALID -/// If 'redraw' is false, do a normal redraw -/// If 'redraw' is -1, don't redraw at all +/// @param redraw if true, redraw the entire screen NOT_VALID +/// if false, do a normal redraw +/// if -1, don't redraw at all void wait_return(int redraw) { int c; @@ -1095,6 +1090,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 +1214,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 } @@ -1262,14 +1261,12 @@ void wait_return(int redraw) } } -/* - * Write the hit-return prompt. - */ +/// Write the hit-return prompt. 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'); } @@ -1285,9 +1282,7 @@ static void hit_return_msg(void) p_more = save_p_more; } -/* - * Set "keep_msg" to "s". Free the old value and check for NULL pointer. - */ +/// Set "keep_msg" to "s". Free the old value and check for NULL pointer. void set_keep_msg(char *s, int attr) { xfree(keep_msg); @@ -1300,6 +1295,49 @@ void set_keep_msg(char *s, int attr) keep_msg_attr = attr; } +void msgmore(long n) +{ + long pn; + + if (global_busy // no messages now, wait until global is finished + || !messaging()) { // 'lazyredraw' set, don't do messages now + return; + } + + // We don't want to overwrite another important message, but do overwrite + // a previous "more lines" or "fewer lines" message, so that "5dd" and + // then "put" reports the last action. + if (keep_msg != NULL && !keep_msg_more) { + return; + } + + if (n > 0) { + pn = n; + } else { + pn = -n; + } + + if (pn > p_report) { + if (n > 0) { + vim_snprintf(msg_buf, MSG_BUF_LEN, + NGETTEXT("%ld more line", "%ld more lines", pn), + pn); + } else { + vim_snprintf(msg_buf, MSG_BUF_LEN, + NGETTEXT("%ld line less", "%ld fewer lines", pn), + pn); + } + if (got_int) { + xstrlcat(msg_buf, _(" (Interrupted)"), MSG_BUF_LEN); + } + if (msg(msg_buf)) { + set_keep_msg(msg_buf, 0); + keep_msg_more = true; + } + } +} + + void msg_ext_set_kind(const char *msg_kind) { // Don't change the label of an existing batch: @@ -1311,15 +1349,14 @@ void msg_ext_set_kind(const char *msg_kind) msg_ext_kind = msg_kind; } -/* - * Prepare for outputting characters in the command line. - */ +/// Prepare for outputting characters in the command line. void msg_start(void) { int did_return = false; if (!msg_silent) { XFREE_CLEAR(keep_msg); // don't display old message now + need_fileinfo = false; } if (need_clr_eos) { @@ -1360,9 +1397,7 @@ void msg_start(void) } } -/* - * Note that the current msg position is where messages start. - */ +/// Note that the current msg position is where messages start. void msg_starthere(void) { lines_left = cmdline_row; @@ -1416,12 +1451,11 @@ static void msg_home_replace_attr(char_u *fname, int attr) xfree(name); } -/* - * Output 'len' characters in 'str' (including NULs) with translation - * if 'len' is -1, output up to a NUL character. - * Use attributes 'attr'. - * Return the number of characters it takes on the screen. - */ +/// Output 'len' characters in 'str' (including NULs) with translation +/// if 'len' is -1, output up to a NUL character. +/// Use attributes 'attr'. +/// +/// @return the number of characters it takes on the screen. int msg_outtrans(char_u *str) { return msg_outtrans_attr(str, 0); @@ -1437,10 +1471,10 @@ int msg_outtrans_len(const char_u *str, int len) return msg_outtrans_len_attr(str, len, 0); } -/* - * Output one character at "p". Return pointer to the next character. - * Handles multi-byte characters. - */ +/// Output one character at "p". +/// Handles multi-byte characters. +/// +/// @return pointer to the next character. char_u *msg_outtrans_one(char_u *p, int attr) { int l; @@ -1461,6 +1495,10 @@ int msg_outtrans_len_attr(const char_u *msgstr, int len, int attr) char_u *s; int mb_l; int c; + int save_got_int = got_int; + + // Only quit when got_int was set in here. + got_int = false; // if MSG_HIST flag set, add message to history if (attr & MSG_HIST) { @@ -1478,7 +1516,7 @@ int msg_outtrans_len_attr(const char_u *msgstr, int len, int attr) * Go over the string. Special characters are translated and printed. * Normal characters are printed several at a time. */ - while (--len >= 0) { + while (--len >= 0 && !got_int) { // Don't include composing chars after the end. mb_l = utfc_ptr2len_len((char_u *)str, len + 1); if (mb_l > 1) { @@ -1517,11 +1555,13 @@ int msg_outtrans_len_attr(const char_u *msgstr, int len, int attr) } } - if (str > plain_start) { + if (str > plain_start && !got_int) { // Print the printable chars at the end. msg_puts_attr_len(plain_start, str - plain_start, attr); } + got_int |= save_got_int; + return retval; } @@ -1706,9 +1746,7 @@ void str2specialbuf(const char *sp, char *buf, size_t len) *buf = NUL; } -/* - * print line for :print or :list command - */ +/// print line for :print or :list command void msg_prt_line(char_u *s, int list) { int c; @@ -1857,8 +1895,9 @@ void msg_prt_line(char_u *s, int list) msg_clr_eos(); } -// Use grid_puts() to output one multi-byte character. -// Return the pointer "s" advanced to the next character. +/// Use grid_puts() to output one multi-byte character. +/// +/// @return the pointer "s" advanced to the next character. static char_u *screen_puts_mbyte(char_u *s, int l, int attr) { int cw; @@ -1890,10 +1929,8 @@ static char_u *screen_puts_mbyte(char_u *s, int l, int attr) return s + l; } -/* - * Output a string to the screen at position msg_row, msg_col. - * Update msg_row and msg_col for the next message. - */ +/// Output a string to the screen at position msg_row, msg_col. +/// Update msg_row and msg_col for the next message. void msg_puts(const char *s) { msg_puts_attr(s, 0); @@ -1904,11 +1941,9 @@ void msg_puts_title(const char *s) msg_puts_attr(s, HL_ATTR(HLF_T)); } -/* - * Show a message in such a way that it always fits in the line. Cut out a - * part in the middle and replace it with "..." when necessary. - * Does not handle multi-byte characters! - */ +/// Show a message in such a way that it always fits in the line. Cut out a +/// part in the middle and replace it with "..." when necessary. +/// Does not handle multi-byte characters! void msg_outtrans_long_attr(char_u *longstr, int attr) { msg_outtrans_long_len_attr(longstr, (int)STRLEN(longstr), attr); @@ -1928,9 +1963,7 @@ void msg_outtrans_long_len_attr(char_u *longstr, int len, int attr) msg_outtrans_len_attr(longstr + len - slen, slen, attr); } -/* - * Basic function for writing a message with highlight attributes. - */ +/// Basic function for writing a message with highlight attributes. void msg_puts_attr(const char *const s, const int attr) { msg_puts_attr_len(s, -1, attr); @@ -1995,6 +2028,8 @@ void msg_puts_attr_len(const char *const str, const ptrdiff_t len, int attr) if (!msg_use_printf() || (headless_mode && default_grid.chars)) { msg_puts_display((const char_u *)str, len, attr, false); } + + need_fileinfo = false; } /// Print a formatted message @@ -2032,10 +2067,8 @@ static void msg_ext_emit_chunk(void) ADD(msg_ext_chunks, ARRAY_OBJ(chunk)); } -/* - * The display part of msg_puts_attr_len(). - * May be called recursively to display scroll-back text. - */ +/// The display part of msg_puts_attr_len(). +/// May be called recursively to display scroll-back text. static void msg_puts_display(const char_u *str, int maxlen, int attr, int recurse) { const char_u *s = str; @@ -2056,7 +2089,7 @@ static void msg_puts_display(const char_u *str, int maxlen, int attr, int recurs msg_ext_last_attr = attr; } // Concat pieces with the same highlight - size_t len = strnlen((char *)str, maxlen); // -V781 + size_t len = STRNLEN(str, maxlen); // -V781 ga_concat_len(&msg_ext_last_chunk, (char *)str, len); msg_ext_cur_len += len; return; @@ -2240,8 +2273,8 @@ static void msg_puts_display(const char_u *str, int maxlen, int attr, int recurs msg_check(); } -/// Return true when ":filter pattern" was used and "msg" does not match -/// "pattern". +/// @return true when ":filter pattern" was used and "msg" does not match +/// "pattern". bool message_filtered(char_u *msg) { if (cmdmod.filter_regmatch.regprog == NULL) { @@ -2373,9 +2406,7 @@ void msg_reset_scroll(void) msg_scrolled_at_flush = 0; } -/* - * Increment "msg_scrolled". - */ +/// Increment "msg_scrolled". static void inc_msg_scrolled(void) { if (*get_vim_var_str(VV_SCROLLSTART) == NUL) { @@ -2454,9 +2485,7 @@ static void store_sb_text(char_u **sb_str, char_u *s, int attr, int *sb_col, int *sb_col = 0; } -/* - * Finished showing messages, clear the scroll-back text on the next message. - */ +/// Finished showing messages, clear the scroll-back text on the next message. void may_clear_sb_text(void) { do_clear_sb_text = SB_CLEAR_ALL; @@ -2499,9 +2528,7 @@ void clear_sb_text(int all) } } -/* - * "g<" command. - */ +/// "g<" command. void show_sb_text(void) { msgchunk_T *mp; @@ -2517,9 +2544,7 @@ void show_sb_text(void) } } -/* - * Move to the start of screen line in already displayed text. - */ +/// Move to the start of screen line in already displayed text. static msgchunk_T *msg_sb_start(msgchunk_T *mps) { msgchunk_T *mp = mps; @@ -2530,9 +2555,7 @@ static msgchunk_T *msg_sb_start(msgchunk_T *mps) return mp; } -/* - * Mark the last message chunk as finishing the line. - */ +/// Mark the last message chunk as finishing the line. void msg_sb_eol(void) { if (last_msgchunk != NULL) { @@ -2540,10 +2563,9 @@ void msg_sb_eol(void) } } -/* - * Display a screen line from previously displayed text at row "row". - * Returns a pointer to the text for the next line (can be NULL). - */ +/// Display a screen line from previously displayed text at row "row". +/// +/// @return a pointer to the text for the next line (can be NULL). static msgchunk_T *disp_sb_line(int row, msgchunk_T *smp) { msgchunk_T *mp = smp; @@ -2566,9 +2588,7 @@ static msgchunk_T *disp_sb_line(int row, msgchunk_T *smp) return mp->sb_next; } -/* - * Output any postponed text for msg_puts_attr_len(). - */ +/// Output any postponed text for msg_puts_attr_len(). static void t_puts(int *t_col, const char_u *t_s, const char_u *s, int attr) { attr = hl_combine_attr(HL_ATTR(HLF_MSG), attr); @@ -2589,9 +2609,9 @@ static void t_puts(int *t_col, const char_u *t_s, const char_u *s, int attr) } } -// Returns TRUE when messages should be printed to stdout/stderr: -// - "batch mode" ("silent mode", -es/-Es) -// - no UI and not embedded +/// @return TRUE when messages should be printed to stdout/stderr: +/// - "batch mode" ("silent mode", -es/-Es) +/// - no UI and not embedded int msg_use_printf(void) { return !embedded_mode && !ui_active(); @@ -2604,6 +2624,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)) { @@ -2641,13 +2672,12 @@ static void msg_puts_printf(const char *str, const ptrdiff_t maxlen) msg_didout = true; // assume that line is not empty } -/* - * Show the more-prompt and handle the user response. - * This takes care of scrolling back and displaying previously displayed text. - * When at hit-enter prompt "typed_char" is the already typed character, - * otherwise it's NUL. - * Returns TRUE when jumping ahead to "confirm_msg_tail". - */ +/// Show the more-prompt and handle the user response. +/// This takes care of scrolling back and displaying previously displayed text. +/// When at hit-enter prompt "typed_char" is the already typed character, +/// otherwise it's NUL. +/// +/// @return TRUE when jumping ahead to "confirm_msg_tail". static int do_more_prompt(int typed_char) { static bool entered = false; @@ -2905,7 +2935,7 @@ void mch_errmsg(char *str) } } -// Give a message. To be used when the UI is not initialized yet. +/// Give a message. To be used when the UI is not initialized yet. void mch_msg(char *str) { assert(str != NULL); @@ -2920,10 +2950,8 @@ void mch_msg(char *str) } #endif // WIN32 -/* - * Put a character on the screen at the current message position and advance - * to the next position. Only for printable ASCII! - */ +/// Put a character on the screen at the current message position and advance +/// to the next position. Only for printable ASCII! static void msg_screen_putchar(int c, int attr) { attr = hl_combine_attr(HL_ATTR(HLF_MSG), attr); @@ -2956,10 +2984,8 @@ void msg_moremsg(int full) } } -/* - * Repeat the message for the current mode: ASKMORE, EXTERNCMD, CONFIRM or - * exmode_active. - */ +/// Repeat the message for the current mode: ASKMORE, EXTERNCMD, CONFIRM or +/// exmode_active. void repeat_message(void) { if (State == ASKMORE) { @@ -2984,10 +3010,8 @@ void repeat_message(void) } } -/* - * Clear from current message position to end of screen. - * Skip this when ":silent" was used, no need to clear for redirection. - */ +/// Clear from current message position to end of screen. +/// Skip this when ":silent" was used, no need to clear for redirection. void msg_clr_eos(void) { if (msg_silent == 0) { @@ -2995,11 +3019,9 @@ void msg_clr_eos(void) } } -/* - * Clear from current message position to end of screen. - * Note: msg_col is not updated, so we remember the end of the message - * for msg_check(). - */ +/// Clear from current message position to end of screen. +/// Note: msg_col is not updated, so we remember the end of the message +/// for msg_check(). void msg_clr_eos_force(void) { if (ui_has(kUIMessages)) { @@ -3026,9 +3048,7 @@ void msg_clr_eos_force(void) } } -/* - * Clear the command line. - */ +/// Clear the command line. void msg_clr_cmdline(void) { msg_row = cmdline_row; @@ -3036,11 +3056,10 @@ void msg_clr_cmdline(void) msg_clr_eos_force(); } -/* - * end putting a message on the screen - * call wait_return if the message does not fit in the available space - * return TRUE if wait_return not called. - */ +/// end putting a message on the screen +/// call wait_return if the message does not fit in the available space +/// +/// @return TRUE if wait_return not called. int msg_end(void) { /* @@ -3130,10 +3149,8 @@ bool msg_ext_is_visible(void) return ui_has(kUIMessages) && msg_ext_visible > 0; } -/* - * If the written message runs into the shown command or ruler, we have to - * wait for hit-return and redraw the window later. - */ +/// If the written message runs into the shown command or ruler, we have to +/// wait for hit-return and redraw the window later. void msg_check(void) { if (ui_has(kUIMessages)) { @@ -3145,10 +3162,9 @@ void msg_check(void) } } -/* - * May write a string to the redirection file. - * When "maxlen" is -1 write the whole string, otherwise up to "maxlen" bytes. - */ +/// May write a string to the redirection file. +/// +/// @param maxlen if -1, write the whole string, otherwise up to "maxlen" bytes. static void redir_write(const char *const str, const ptrdiff_t maxlen) { const char_u *s = (char_u *)str; @@ -3233,10 +3249,8 @@ int redirecting(void) || redir_reg || redir_vname || capture_ga != NULL; } -/* - * Before giving verbose message. - * Must always be called paired with verbose_leave()! - */ +/// Before giving verbose message. +/// Must always be called paired with verbose_leave()! void verbose_enter(void) { if (*p_vfile != NUL) { @@ -3244,10 +3258,8 @@ void verbose_enter(void) } } -/* - * After giving verbose message. - * Must always be called paired with verbose_enter()! - */ +/// After giving verbose message. +/// Must always be called paired with verbose_enter()! void verbose_leave(void) { if (*p_vfile != NUL) { @@ -3257,9 +3269,7 @@ void verbose_leave(void) } } -/* - * Like verbose_enter() and set msg_scroll when displaying the message. - */ +/// Like verbose_enter() and set msg_scroll when displaying the message. void verbose_enter_scroll(void) { if (*p_vfile != NUL) { @@ -3270,9 +3280,7 @@ void verbose_enter_scroll(void) } } -/* - * Like verbose_leave() and set cmdline_row when displaying the message. - */ +/// Like verbose_leave() and set cmdline_row when displaying the message. void verbose_leave_scroll(void) { if (*p_vfile != NUL) { @@ -3284,9 +3292,7 @@ void verbose_leave_scroll(void) } } -/* - * Called when 'verbosefile' is set: stop writing to the file. - */ +/// Called when 'verbosefile' is set: stop writing to the file. void verbose_stop(void) { if (verbose_fd != NULL) { @@ -3296,10 +3302,9 @@ void verbose_stop(void) verbose_did_open = FALSE; } -/* - * Open the file 'verbosefile'. - * Return FAIL or OK. - */ +/// Open the file 'verbosefile'. +/// +/// @return FAIL or OK. int verbose_open(void) { if (verbose_fd == NULL && !verbose_did_open) { @@ -3315,10 +3320,8 @@ int verbose_open(void) return OK; } -/* - * Give a warning message (for searching). - * Use 'w' highlighting and may repeat the message after redrawing - */ +/// Give a warning message (for searching). +/// Use 'w' highlighting and may repeat the message after redrawing void give_warning(char_u *message, bool hl) FUNC_ATTR_NONNULL_ARG(1) { // Don't do this for ":silent". @@ -3357,9 +3360,7 @@ void give_warning2(char_u *const message, char_u *const a1, bool hl) give_warning(IObuff, hl); } -/* - * Advance msg cursor to column "col". - */ +/// Advance msg cursor to column "col". void msg_advance(int col) { if (msg_silent != 0) { // nothing to advance to @@ -3407,6 +3408,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) { @@ -3454,7 +3456,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; } @@ -3568,15 +3570,13 @@ static char_u *console_dialog_alloc(const char_u *message, char_u *buttons, bool return xmalloc(lenhotkey); } -/* - * Format the dialog string, and display it at the bottom of - * the screen. Return a string of hotkey chars (if defined) for - * each 'button'. If a button has no hotkey defined, the first character of - * the button is used. - * The hotkeys can be multi-byte characters, but without combining chars. - * - * Returns an allocated string with hotkeys. - */ +/// Format the dialog string, and display it at the bottom of +/// the screen. Return a string of hotkey chars (if defined) for +/// each 'button'. If a button has no hotkey defined, the first character of +/// the button is used. +/// The hotkeys can be multi-byte characters, but without combining chars. +/// +/// @return an allocated string with hotkeys. static char_u *msg_show_console_dialog(char_u *message, char_u *buttons, int dfltbutton) FUNC_ATTR_NONNULL_RET { @@ -3669,9 +3669,7 @@ static void copy_hotkeys_and_msg(const char_u *message, char_u *buttons, int def *msgp = NUL; } -/* - * Display the ":confirm" message. Also called when screen resized. - */ +/// Display the ":confirm" message. Also called when screen resized. void display_confirm_msg(void) { // Avoid that 'q' at the more prompt truncates the message here. diff --git a/src/nvim/misc1.c b/src/nvim/misc1.c deleted file mode 100644 index fd5d154cea..0000000000 --- a/src/nvim/misc1.c +++ /dev/null @@ -1,1061 +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 - -/* - * misc1.c: functions that didn't seem to fit elsewhere - */ - -#include <assert.h> -#include <inttypes.h> -#include <limits.h> -#include <stdbool.h> -#include <string.h> - -#include "nvim/ascii.h" -#include "nvim/buffer.h" -#include "nvim/buffer_updates.h" -#include "nvim/charset.h" -#include "nvim/cursor.h" -#include "nvim/diff.h" -#include "nvim/edit.h" -#include "nvim/eval.h" -#include "nvim/event/stream.h" -#include "nvim/ex_cmds.h" -#include "nvim/ex_docmd.h" -#include "nvim/ex_getln.h" -#include "nvim/fileio.h" -#include "nvim/fold.h" -#include "nvim/func_attr.h" -#include "nvim/garray.h" -#include "nvim/getchar.h" -#include "nvim/indent.h" -#include "nvim/indent_c.h" -#include "nvim/main.h" -#include "nvim/mbyte.h" -#include "nvim/memline.h" -#include "nvim/memory.h" -#include "nvim/message.h" -#include "nvim/misc1.h" -#include "nvim/mouse.h" -#include "nvim/move.h" -#include "nvim/option.h" -#include "nvim/os/input.h" -#include "nvim/os/os.h" -#include "nvim/os/shell.h" -#include "nvim/os/signal.h" -#include "nvim/os/time.h" -#include "nvim/os_unix.h" -#include "nvim/quickfix.h" -#include "nvim/regexp.h" -#include "nvim/screen.h" -#include "nvim/search.h" -#include "nvim/state.h" -#include "nvim/strings.h" -#include "nvim/tag.h" -#include "nvim/ui.h" -#include "nvim/undo.h" -#include "nvim/vim.h" -#include "nvim/window.h" - -#ifdef INCLUDE_GENERATED_DECLARATIONS -# include "misc1.c.generated.h" -#endif -// All user names (for ~user completion as done by shell). -static garray_T ga_users = GA_EMPTY_INIT_VALUE; - -/* - * get_leader_len() returns the length in bytes of the prefix of the given - * string which introduces a comment. If this string is not a comment then - * 0 is returned. - * When "flags" is not NULL, it is set to point to the flags of the recognized - * comment leader. - * "backward" must be true for the "O" command. - * If "include_space" is set, include trailing whitespace while calculating the - * length. - */ -int get_leader_len(char_u *line, char_u **flags, bool backward, bool include_space) -{ - int i, j; - int result; - int got_com = FALSE; - int found_one; - char_u part_buf[COM_MAX_LEN]; // buffer for one option part - char_u *string; // pointer to comment string - char_u *list; - int middle_match_len = 0; - char_u *prev_list; - char_u *saved_flags = NULL; - - result = i = 0; - while (ascii_iswhite(line[i])) { // leading white space is ignored - ++i; - } - - /* - * Repeat to match several nested comment strings. - */ - while (line[i] != NUL) { - /* - * scan through the 'comments' option for a match - */ - found_one = FALSE; - for (list = curbuf->b_p_com; *list;) { - // Get one option part into part_buf[]. Advance "list" to next - // one. Put "string" at start of string. - if (!got_com && flags != NULL) { - *flags = list; // remember where flags started - } - prev_list = list; - (void)copy_option_part(&list, part_buf, COM_MAX_LEN, ","); - string = vim_strchr(part_buf, ':'); - if (string == NULL) { // missing ':', ignore this part - continue; - } - *string++ = NUL; // isolate flags from string - - // If we found a middle match previously, use that match when this - // is not a middle or end. - if (middle_match_len != 0 - && vim_strchr(part_buf, COM_MIDDLE) == NULL - && vim_strchr(part_buf, COM_END) == NULL) { - break; - } - - // When we already found a nested comment, only accept further - // nested comments. - if (got_com && vim_strchr(part_buf, COM_NEST) == NULL) { - continue; - } - - // When 'O' flag present and using "O" command skip this one. - if (backward && vim_strchr(part_buf, COM_NOBACK) != NULL) { - continue; - } - - // Line contents and string must match. - // When string starts with white space, must have some white space - // (but the amount does not need to match, there might be a mix of - // TABs and spaces). - if (ascii_iswhite(string[0])) { - if (i == 0 || !ascii_iswhite(line[i - 1])) { - continue; // missing white space - } - while (ascii_iswhite(string[0])) { - ++string; - } - } - for (j = 0; string[j] != NUL && string[j] == line[i + j]; ++j) { - } - if (string[j] != NUL) { - continue; // string doesn't match - } - // When 'b' flag used, there must be white space or an - // end-of-line after the string in the line. - if (vim_strchr(part_buf, COM_BLANK) != NULL - && !ascii_iswhite(line[i + j]) && line[i + j] != NUL) { - continue; - } - - // We have found a match, stop searching unless this is a middle - // comment. The middle comment can be a substring of the end - // comment in which case it's better to return the length of the - // end comment and its flags. Thus we keep searching with middle - // and end matches and use an end match if it matches better. - if (vim_strchr(part_buf, COM_MIDDLE) != NULL) { - if (middle_match_len == 0) { - middle_match_len = j; - saved_flags = prev_list; - } - continue; - } - if (middle_match_len != 0 && j > middle_match_len) { - // Use this match instead of the middle match, since it's a - // longer thus better match. - middle_match_len = 0; - } - - if (middle_match_len == 0) { - i += j; - } - found_one = TRUE; - break; - } - - if (middle_match_len != 0) { - // Use the previously found middle match after failing to find a - // match with an end. - if (!got_com && flags != NULL) { - *flags = saved_flags; - } - i += middle_match_len; - found_one = TRUE; - } - - // No match found, stop scanning. - if (!found_one) { - break; - } - - result = i; - - // Include any trailing white space. - while (ascii_iswhite(line[i])) { - ++i; - } - - if (include_space) { - result = i; - } - - // If this comment doesn't nest, stop here. - got_com = TRUE; - if (vim_strchr(part_buf, COM_NEST) == NULL) { - break; - } - } - return result; -} - -/* - * Return the offset at which the last comment in line starts. If there is no - * comment in the whole line, -1 is returned. - * - * When "flags" is not null, it is set to point to the flags describing the - * recognized comment leader. - */ -int get_last_leader_offset(char_u *line, char_u **flags) -{ - int result = -1; - int i, j; - int lower_check_bound = 0; - char_u *string; - char_u *com_leader; - char_u *com_flags; - char_u *list; - int found_one; - char_u part_buf[COM_MAX_LEN]; // buffer for one option part - - /* - * Repeat to match several nested comment strings. - */ - i = (int)STRLEN(line); - while (--i >= lower_check_bound) { - /* - * scan through the 'comments' option for a match - */ - found_one = FALSE; - for (list = curbuf->b_p_com; *list;) { - char_u *flags_save = list; - - /* - * Get one option part into part_buf[]. Advance list to next one. - * put string at start of string. - */ - (void)copy_option_part(&list, part_buf, COM_MAX_LEN, ","); - string = vim_strchr(part_buf, ':'); - if (string == NULL) { // If everything is fine, this cannot actually - // happen. - continue; - } - *string++ = NUL; // Isolate flags from string. - com_leader = string; - - /* - * Line contents and string must match. - * When string starts with white space, must have some white space - * (but the amount does not need to match, there might be a mix of - * TABs and spaces). - */ - if (ascii_iswhite(string[0])) { - if (i == 0 || !ascii_iswhite(line[i - 1])) { - continue; - } - while (ascii_iswhite(*string)) { - string++; - } - } - for (j = 0; string[j] != NUL && string[j] == line[i + j]; ++j) { - // do nothing - } - if (string[j] != NUL) { - continue; - } - - /* - * When 'b' flag used, there must be white space or an - * end-of-line after the string in the line. - */ - if (vim_strchr(part_buf, COM_BLANK) != NULL - && !ascii_iswhite(line[i + j]) && line[i + j] != NUL) { - continue; - } - - if (vim_strchr(part_buf, COM_MIDDLE) != NULL) { - // For a middlepart comment, only consider it to match if - // everything before the current position in the line is - // whitespace. Otherwise we would think we are inside a - // comment if the middle part appears somewhere in the middle - // of the line. E.g. for C the "*" appears often. - for (j = 0; j <= i && ascii_iswhite(line[j]); j++) { - } - if (j < i) { - continue; - } - } - - /* - * We have found a match, stop searching. - */ - found_one = TRUE; - - if (flags) { - *flags = flags_save; - } - com_flags = flags_save; - - break; - } - - if (found_one) { - char_u part_buf2[COM_MAX_LEN]; // buffer for one option part - int len1, len2, off; - - result = i; - /* - * If this comment nests, continue searching. - */ - if (vim_strchr(part_buf, COM_NEST) != NULL) { - continue; - } - - lower_check_bound = i; - - // Let's verify whether the comment leader found is a substring - // of other comment leaders. If it is, let's adjust the - // lower_check_bound so that we make sure that we have determined - // the comment leader correctly. - - while (ascii_iswhite(*com_leader)) { - ++com_leader; - } - len1 = (int)STRLEN(com_leader); - - for (list = curbuf->b_p_com; *list;) { - char_u *flags_save = list; - - (void)copy_option_part(&list, part_buf2, COM_MAX_LEN, ","); - if (flags_save == com_flags) { - continue; - } - string = vim_strchr(part_buf2, ':'); - ++string; - while (ascii_iswhite(*string)) { - ++string; - } - len2 = (int)STRLEN(string); - if (len2 == 0) { - continue; - } - - // Now we have to verify whether string ends with a substring - // beginning the com_leader. - for (off = (len2 > i ? i : len2); off > 0 && off + len1 > len2;) { - --off; - if (!STRNCMP(string + off, com_leader, len2 - off)) { - if (i - off < lower_check_bound) { - lower_check_bound = i - off; - } - } - } - } - } - } - return result; -} - -int gchar_pos(pos_T *pos) - FUNC_ATTR_NONNULL_ARG(1) -{ - // When searching columns is sometimes put at the end of a line. - if (pos->col == MAXCOL) { - return NUL; - } - return utf_ptr2char(ml_get_pos(pos)); -} - -/* - * check_status: called when the status bars for the buffer 'buf' - * need to be updated - */ -void check_status(buf_T *buf) -{ - FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - if (wp->w_buffer == buf && wp->w_status_height) { - wp->w_redr_status = TRUE; - if (must_redraw < VALID) { - must_redraw = VALID; - } - } - } -} - -/// Ask for a reply from the user, 'y' or 'n' -/// -/// No other characters are accepted, the message is repeated until a valid -/// reply is entered or <C-c> is hit. -/// -/// @param[in] str Prompt: question to ask user. Is always followed by -/// " (y/n)?". -/// @param[in] direct Determines what function to use to get user input. If -/// true then ui_inchar() will be used, otherwise vgetc(). -/// I.e. when direct is true then characters are obtained -/// directly from the user without buffers involved. -/// -/// @return 'y' or 'n'. Last is also what will be returned in case of interrupt. -int ask_yesno(const char *const str, const bool direct) -{ - const int save_State = State; - - no_wait_return++; - State = CONFIRM; // Mouse behaves like with :confirm. - setmouse(); // Disable mouse in xterm. - no_mapping++; - - int r = ' '; - while (r != 'y' && r != 'n') { - // Same highlighting as for wait_return. - smsg_attr(HL_ATTR(HLF_R), "%s (y/n)?", str); - if (direct) { - r = get_keystroke(NULL); - } else { - r = plain_vgetc(); - } - if (r == Ctrl_C || r == ESC) { - r = 'n'; - } - msg_putchar(r); // Show what you typed. - ui_flush(); - } - no_wait_return--; - State = save_State; - setmouse(); - no_mapping--; - - return r; -} - -/* - * Return TRUE if "c" is a mouse key. - */ -int is_mouse_key(int c) -{ - return c == K_LEFTMOUSE - || c == K_LEFTMOUSE_NM - || c == K_LEFTDRAG - || c == K_LEFTRELEASE - || c == K_LEFTRELEASE_NM - || c == K_MOUSEMOVE - || c == K_MIDDLEMOUSE - || c == K_MIDDLEDRAG - || c == K_MIDDLERELEASE - || c == K_RIGHTMOUSE - || c == K_RIGHTDRAG - || c == K_RIGHTRELEASE - || c == K_MOUSEDOWN - || c == K_MOUSEUP - || c == K_MOUSELEFT - || c == K_MOUSERIGHT - || c == K_X1MOUSE - || c == K_X1DRAG - || c == K_X1RELEASE - || c == K_X2MOUSE - || c == K_X2DRAG - || c == K_X2RELEASE; -} - -/* - * Get a key stroke directly from the user. - * Ignores mouse clicks and scrollbar events, except a click for the left - * button (used at the more prompt). - * Doesn't use vgetc(), because it syncs undo and eats mapped characters. - * Disadvantage: typeahead is ignored. - * Translates the interrupt character for unix to ESC. - */ -int get_keystroke(MultiQueue *events) -{ - char_u *buf = NULL; - int buflen = 150; - int maxlen; - int len = 0; - int n; - int save_mapped_ctrl_c = mapped_ctrl_c; - int waited = 0; - - mapped_ctrl_c = 0; // mappings are not used here - for (;;) { - // flush output before waiting - ui_flush(); - // Leave some room for check_termcode() to insert a key code into (max - // 5 chars plus NUL). And fix_input_buffer() can triple the number of - // bytes. - maxlen = (buflen - 6 - len) / 3; - if (buf == NULL) { - buf = xmalloc((size_t)buflen); - } else if (maxlen < 10) { - // Need some more space. This might happen when receiving a long - // escape sequence. - buflen += 100; - buf = xrealloc(buf, (size_t)buflen); - maxlen = (buflen - 6 - len) / 3; - } - - // First time: blocking wait. Second time: wait up to 100ms for a - // 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. - n = fix_input_buffer(buf + len, n); - len += n; - waited = 0; - } else if (len > 0) { - ++waited; // keep track of the waiting time - } - if (n > 0) { // found a termcode: adjust length - len = n; - } - if (len == 0) { // nothing typed yet - continue; - } - - // Handle modifier and/or special key code. - n = buf[0]; - if (n == K_SPECIAL) { - n = TO_SPECIAL(buf[1], buf[2]); - if (buf[1] == KS_MODIFIER - || n == K_IGNORE - || (is_mouse_key(n) && n != K_LEFTMOUSE)) { - if (buf[1] == KS_MODIFIER) { - mod_mask = buf[2]; - } - len -= 3; - if (len > 0) { - memmove(buf, buf + 3, (size_t)len); - } - continue; - } - break; - } - if (MB_BYTE2LEN(n) > len) { - // more bytes to get. - continue; - } - buf[len >= buflen ? buflen - 1 : len] = NUL; - n = utf_ptr2char(buf); - break; - } - xfree(buf); - - mapped_ctrl_c = save_mapped_ctrl_c; - return n; -} - -/// Get a number from the user. -/// When "mouse_used" is not NULL allow using the mouse. -/// -/// @param colon allow colon to abort -int get_number(int colon, int *mouse_used) -{ - int n = 0; - int c; - int typed = 0; - - if (mouse_used != NULL) { - *mouse_used = FALSE; - } - - // When not printing messages, the user won't know what to type, return a - // zero (as if CR was hit). - if (msg_silent != 0) { - return 0; - } - - no_mapping++; - for (;;) { - ui_cursor_goto(msg_row, msg_col); - c = safe_vgetc(); - if (ascii_isdigit(c)) { - n = n * 10 + c - '0'; - msg_putchar(c); - ++typed; - } else if (c == K_DEL || c == K_KDEL || c == K_BS || c == Ctrl_H) { - if (typed > 0) { - msg_puts("\b \b"); - --typed; - } - n /= 10; - } else if (mouse_used != NULL && c == K_LEFTMOUSE) { - *mouse_used = TRUE; - n = mouse_row + 1; - break; - } else if (n == 0 && c == ':' && colon) { - stuffcharReadbuff(':'); - if (!exmode_active) { - cmdline_row = msg_row; - } - skip_redraw = true; // skip redraw once - do_redraw = false; - break; - } else if (c == Ctrl_C || c == ESC || c == 'q') { - n = 0; - break; - } else if (c == CAR || c == NL) { - break; - } - } - no_mapping--; - return n; -} - -/* - * Ask the user to enter a number. - * When "mouse_used" is not NULL allow using the mouse and in that case return - * the line number. - */ -int prompt_for_number(int *mouse_used) -{ - int i; - int save_cmdline_row; - int save_State; - - // When using ":silent" assume that <CR> was entered. - if (mouse_used != NULL) { - msg_puts(_("Type number and <Enter> or click with the mouse " - "(q or empty cancels): ")); - } else { - msg_puts(_("Type number and <Enter> (q or empty cancels): ")); - } - - /* Set the state such that text can be selected/copied/pasted and we still - * get mouse events. */ - save_cmdline_row = cmdline_row; - cmdline_row = 0; - save_State = State; - State = ASKMORE; // prevents a screen update when using a timer - // May show different mouse shape. - setmouse(); - - i = get_number(TRUE, mouse_used); - if (KeyTyped) { - // don't call wait_return() now - if (msg_row > 0) { - cmdline_row = msg_row - 1; - } - need_wait_return = false; - msg_didany = false; - msg_didout = false; - } else { - cmdline_row = save_cmdline_row; - } - State = save_State; - // May need to restore mouse shape. - setmouse(); - - return i; -} - -void msgmore(long n) -{ - long pn; - - if (global_busy // no messages now, wait until global is finished - || !messaging()) { // 'lazyredraw' set, don't do messages now - return; - } - - // We don't want to overwrite another important message, but do overwrite - // a previous "more lines" or "fewer lines" message, so that "5dd" and - // then "put" reports the last action. - if (keep_msg != NULL && !keep_msg_more) { - return; - } - - if (n > 0) { - pn = n; - } else { - pn = -n; - } - - if (pn > p_report) { - if (n > 0) { - vim_snprintf(msg_buf, MSG_BUF_LEN, - NGETTEXT("%ld more line", "%ld more lines", pn), - pn); - } else { - vim_snprintf(msg_buf, MSG_BUF_LEN, - NGETTEXT("%ld line less", "%ld fewer lines", pn), - pn); - } - if (got_int) { - xstrlcat(msg_buf, _(" (Interrupted)"), MSG_BUF_LEN); - } - if (msg(msg_buf)) { - set_keep_msg(msg_buf, 0); - keep_msg_more = true; - } - } -} - -/* - * flush map and typeahead buffers and give a warning for an error - */ -void beep_flush(void) -{ - if (emsg_silent == 0) { - flush_buffers(FLUSH_MINIMAL); - vim_beep(BO_ERROR); - } -} - -// Give a warning for an error -// val is one of the BO_ values, e.g., BO_OPER -void vim_beep(unsigned val) -{ - called_vim_beep = true; - - if (emsg_silent == 0) { - if (!((bo_flags & val) || (bo_flags & BO_ALL))) { - static int beeps = 0; - static uint64_t start_time = 0; - - // Only beep up to three times per half a second, - // otherwise a sequence of beeps would freeze Vim. - if (start_time == 0 || os_hrtime() - start_time > 500000000u) { - beeps = 0; - start_time = os_hrtime(); - } - beeps++; - if (beeps <= 3) { - if (p_vb) { - ui_call_visual_bell(); - } else { - ui_call_bell(); - } - } - } - - // When 'debug' contains "beep" produce a message. If we are sourcing - // a script or executing a function give the user a hint where the beep - // comes from. - if (vim_strchr(p_debug, 'e') != NULL) { - msg_source(HL_ATTR(HLF_W)); - msg_attr(_("Beep!"), HL_ATTR(HLF_W)); - } - } -} - -#if defined(EXITFREE) - -void free_users(void) -{ - ga_clear_strings(&ga_users); -} - -#endif - -/* - * Find all user names for user completion. - * Done only once and then cached. - */ -static void init_users(void) -{ - static int lazy_init_done = FALSE; - - if (lazy_init_done) { - return; - } - - lazy_init_done = TRUE; - - os_get_usernames(&ga_users); -} - -/* - * Function given to ExpandGeneric() to obtain an user names. - */ -char_u *get_users(expand_T *xp, int idx) -{ - init_users(); - if (idx < ga_users.ga_len) { - return ((char_u **)ga_users.ga_data)[idx]; - } - return NULL; -} - -/* - * Check whether name matches a user name. Return: - * 0 if name does not match any user name. - * 1 if name partially matches the beginning of a user name. - * 2 is name fully matches a user name. - */ -int match_user(char_u *name) -{ - int n = (int)STRLEN(name); - int result = 0; - - init_users(); - for (int i = 0; i < ga_users.ga_len; i++) { - if (STRCMP(((char_u **)ga_users.ga_data)[i], name) == 0) { - return 2; // full match - } - if (STRNCMP(((char_u **)ga_users.ga_data)[i], name, n) == 0) { - result = 1; // partial match - } - } - return result; -} - -/// Preserve files and exit. -/// @note IObuff must contain a message. -/// @note This may be called from deadly_signal() in a signal handler, avoid -/// unsafe functions, such as allocating memory. -void preserve_exit(void) - FUNC_ATTR_NORETURN -{ - // 'true' when we are sure to exit, e.g., after a deadly signal - static bool really_exiting = false; - - // Prevent repeated calls into this method. - if (really_exiting) { - if (input_global_fd() >= 0) { - // normalize stream (#2598) - stream_set_blocking(input_global_fd(), true); - } - exit(2); - } - - really_exiting = true; - // Ignore SIGHUP while we are already exiting. #9274 - signal_reject_deadly(); - mch_errmsg(IObuff); - mch_errmsg("\n"); - ui_flush(); - - ml_close_notmod(); // close all not-modified buffers - - FOR_ALL_BUFFERS(buf) { - if (buf->b_ml.ml_mfp != NULL && buf->b_ml.ml_mfp->mf_fname != NULL) { - mch_errmsg("Vim: preserving files...\r\n"); - ui_flush(); - ml_sync_all(false, false, true); // preserve all swap files - break; - } - } - - ml_close_all(false); // close all memfiles, without deleting - - mch_errmsg("Vim: Finished.\r\n"); - - getout(1); -} - -/* - * Check for CTRL-C pressed, but only once in a while. - * Should be used instead of os_breakcheck() for functions that check for - * each line in the file. Calling os_breakcheck() each time takes too much - * time, because it can be a system call. - */ - -#ifndef BREAKCHECK_SKIP -# define BREAKCHECK_SKIP 1000 -#endif - -static int breakcheck_count = 0; - -void line_breakcheck(void) -{ - if (++breakcheck_count >= BREAKCHECK_SKIP) { - breakcheck_count = 0; - os_breakcheck(); - } -} - -/* - * Like line_breakcheck() but check 10 times less often. - */ -void fast_breakcheck(void) -{ - if (++breakcheck_count >= BREAKCHECK_SKIP * 10) { - breakcheck_count = 0; - os_breakcheck(); - } -} - -// Like line_breakcheck() but check 100 times less often. -void veryfast_breakcheck(void) -{ - if (++breakcheck_count >= BREAKCHECK_SKIP * 100) { - breakcheck_count = 0; - os_breakcheck(); - } -} - -/// os_call_shell() wrapper. Handles 'verbose', :profile, and v:shell_error. -/// Invalidates cached tags. -/// -/// @return shell command exit code -int call_shell(char_u *cmd, ShellOpts opts, char_u *extra_shell_arg) -{ - int retval; - proftime_T wait_time; - - if (p_verbose > 3) { - verbose_enter(); - smsg(_("Executing command: \"%s\""), cmd == NULL ? p_sh : cmd); - msg_putchar('\n'); - verbose_leave(); - } - - if (do_profiling == PROF_YES) { - prof_child_enter(&wait_time); - } - - if (*p_sh == NUL) { - emsg(_(e_shellempty)); - retval = -1; - } else { - // The external command may update a tags file, clear cached tags. - tag_freematch(); - - retval = os_call_shell(cmd, opts, extra_shell_arg); - } - - set_vim_var_nr(VV_SHELL_ERROR, (varnumber_T)retval); - if (do_profiling == PROF_YES) { - prof_child_exit(&wait_time); - } - - return retval; -} - -/// Get the stdout of an external command. -/// If "ret_len" is NULL replace NUL characters with NL. When "ret_len" is not -/// NULL store the length there. -/// -/// @param cmd command to execute -/// @param infile optional input file name -/// @param flags can be kShellOptSilent or 0 -/// @param ret_len length of the stdout -/// -/// @return an allocated string, or NULL for error. -char_u *get_cmd_output(char_u *cmd, char_u *infile, ShellOpts flags, size_t *ret_len) -{ - char_u *buffer = NULL; - - if (check_secure()) { - return NULL; - } - - // get a name for the temp file - char_u *tempname = vim_tempname(); - if (tempname == NULL) { - emsg(_(e_notmp)); - return NULL; - } - - // Add the redirection stuff - char_u *command = make_filter_cmd(cmd, infile, tempname); - - /* - * Call the shell to execute the command (errors are ignored). - * Don't check timestamps here. - */ - ++no_check_timestamps; - call_shell(command, kShellOptDoOut | kShellOptExpand | flags, NULL); - --no_check_timestamps; - - xfree(command); - - // read the names from the file into memory - FILE *fd = os_fopen((char *)tempname, READBIN); - - if (fd == NULL) { - semsg(_(e_notopen), tempname); - goto done; - } - - fseek(fd, 0L, SEEK_END); - size_t len = (size_t)ftell(fd); // get size of temp file - fseek(fd, 0L, SEEK_SET); - - buffer = xmalloc(len + 1); - size_t i = fread((char *)buffer, 1, len, fd); - fclose(fd); - os_remove((char *)tempname); - if (i != len) { - semsg(_(e_notread), tempname); - XFREE_CLEAR(buffer); - } else if (ret_len == NULL) { - // Change NUL into SOH, otherwise the string is truncated. - for (i = 0; i < len; ++i) { - if (buffer[i] == NUL) { - buffer[i] = 1; - } - } - - buffer[len] = NUL; // make sure the buffer is terminated - } else { - *ret_len = len; - } - -done: - xfree(tempname); - return buffer; -} - -/* - * Free the list of files returned by expand_wildcards() or other expansion - * functions. - */ -void FreeWild(int count, char_u **files) -{ - if (count <= 0 || files == NULL) { - return; - } - while (count--) { - xfree(files[count]); - } - xfree(files); -} - -/* - * Return TRUE when need to go to Insert mode because of 'insertmode'. - * Don't do this when still processing a command or a mapping. - * Don't do this when inside a ":normal" command. - */ -int goto_im(void) -{ - return p_im && stuff_empty() && typebuf_typed(); -} - -/// Put the timestamp of an undo header in "buf[buflen]" in a nice format. -void add_time(char_u *buf, size_t buflen, time_t tt) -{ - struct tm curtime; - - if (time(NULL) - tt >= 100) { - os_localtime_r(&tt, &curtime); - if (time(NULL) - tt < (60L * 60L * 12L)) { - // within 12 hours - (void)strftime((char *)buf, buflen, "%H:%M:%S", &curtime); - } else { - // longer ago - (void)strftime((char *)buf, buflen, "%Y/%m/%d %H:%M:%S", &curtime); - } - } else { - int64_t seconds = time(NULL) - tt; - vim_snprintf((char *)buf, buflen, - NGETTEXT("%" PRId64 " second ago", - "%" PRId64 " seconds ago", (uint32_t)seconds), - seconds); - } -} diff --git a/src/nvim/misc1.h b/src/nvim/misc1.h deleted file mode 100644 index 4ce142c4c5..0000000000 --- a/src/nvim/misc1.h +++ /dev/null @@ -1,17 +0,0 @@ -#ifndef NVIM_MISC1_H -#define NVIM_MISC1_H - -#include "nvim/os/shell.h" -#include "nvim/vim.h" - -// flags for open_line() -#define OPENLINE_DELSPACES 1 // delete spaces after cursor -#define OPENLINE_DO_COM 2 // format comments -#define OPENLINE_KEEPTRAIL 4 // keep trailing spaces -#define OPENLINE_MARKFIX 8 // fix mark positions -#define OPENLINE_COM_LIST 16 // format comments with list/2nd line indent - -#ifdef INCLUDE_GENERATED_DECLARATIONS -# include "misc1.h.generated.h" -#endif -#endif // NVIM_MISC1_H diff --git a/src/nvim/mouse.c b/src/nvim/mouse.c index 386094e509..6b27ce9d7b 100644 --- a/src/nvim/mouse.c +++ b/src/nvim/mouse.c @@ -10,7 +10,6 @@ #include "nvim/diff.h" #include "nvim/fold.h" #include "nvim/memline.h" -#include "nvim/misc1.h" #include "nvim/mouse.h" #include "nvim/move.h" #include "nvim/os_unix.h" @@ -31,6 +30,32 @@ static linenr_T orig_topline = 0; static int orig_topfill = 0; +/// Return true if "c" is a mouse key. +bool is_mouse_key(int c) +{ + return c == K_LEFTMOUSE + || c == K_LEFTMOUSE_NM + || c == K_LEFTDRAG + || c == K_LEFTRELEASE + || c == K_LEFTRELEASE_NM + || c == K_MOUSEMOVE + || c == K_MIDDLEMOUSE + || c == K_MIDDLEDRAG + || c == K_MIDDLERELEASE + || c == K_RIGHTMOUSE + || c == K_RIGHTDRAG + || c == K_RIGHTRELEASE + || c == K_MOUSEDOWN + || c == K_MOUSEUP + || c == K_MOUSELEFT + || c == K_MOUSERIGHT + || c == K_X1MOUSE + || c == K_X1DRAG + || c == K_X1RELEASE + || c == K_X2MOUSE + || c == K_X2DRAG + || c == K_X2RELEASE; +} /// Move the cursor to the specified row and column on the screen. /// Change current window if necessary. Returns an integer with the /// CURSOR_MOVED bit set if the cursor has moved or unset otherwise. @@ -510,6 +535,22 @@ static win_T *mouse_find_grid_win(int *gridp, int *rowp, int *colp) return NULL; } +/// Convert a virtual (screen) column to a character column. +/// The first column is one. +colnr_T vcol2col(win_T *const wp, const linenr_T lnum, const colnr_T vcol) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT +{ + // try to advance to the specified column + char_u *ptr = ml_get_buf(wp->w_buffer, lnum, false); + char_u *const line = ptr; + colnr_T count = 0; + while (count < vcol && *ptr != NUL) { + count += win_lbr_chartabsize(wp, line, ptr, count, NULL); + MB_PTR_ADV(ptr); + } + return (colnr_T)(ptr - line); +} + /// Set UI mouse depending on current mode and 'mouse'. /// /// Emits mouse_on/mouse_off UI event (unless 'mouse' is empty). diff --git a/src/nvim/mouse.h b/src/nvim/mouse.h index bf4f9c57e5..04acb88bb3 100644 --- a/src/nvim/mouse.h +++ b/src/nvim/mouse.h @@ -38,8 +38,8 @@ // Direction for nv_mousescroll() and ins_mousescroll() #define MSCR_DOWN 0 // DOWN must be FALSE #define MSCR_UP 1 -#define MSCR_LEFT -1 -#define MSCR_RIGHT -2 +#define MSCR_LEFT (-1) +#define MSCR_RIGHT (-2) #ifdef INCLUDE_GENERATED_DECLARATIONS diff --git a/src/nvim/move.c b/src/nvim/move.c index d80e63e79d..eda3298101 100644 --- a/src/nvim/move.c +++ b/src/nvim/move.c @@ -23,9 +23,9 @@ #include "nvim/diff.h" #include "nvim/edit.h" #include "nvim/fold.h" +#include "nvim/getchar.h" #include "nvim/mbyte.h" #include "nvim/memline.h" -#include "nvim/misc1.h" #include "nvim/move.h" #include "nvim/option.h" #include "nvim/plines.h" @@ -95,33 +95,38 @@ static void comp_botline(win_T *wp) win_check_anchored_floats(wp); } -void reset_cursorline(void) +/// Redraw when w_cline_row changes and 'relativenumber' or 'cursorline' is set. +/// Also when concealing is on and 'concealcursor' is not active. +void redraw_for_cursorline(win_T *wp) + FUNC_ATTR_NONNULL_ALL { - curwin->w_last_cursorline = 0; + if ((wp->w_valid & VALID_CROW) == 0 && !pum_visible() + && (wp->w_p_rnu || win_cursorline_standout(wp))) { + // win_line() will redraw the number column and cursorline only. + redraw_later(wp, VALID); + } } -// Redraw when w_cline_row changes and 'relativenumber' or 'cursorline' is set. -void redraw_for_cursorline(win_T *wp) +/// Redraw when w_virtcol changes and 'cursorcolumn' is set or 'cursorlineopt' +/// contains "screenline". +/// Also when concealing is on and 'concealcursor' is active. +static void redraw_for_cursorcolumn(win_T *wp) FUNC_ATTR_NONNULL_ALL { - if ((wp->w_p_rnu || win_cursorline_standout(wp)) - && (wp->w_valid & VALID_CROW) == 0 - && !pum_visible()) { - if (wp->w_p_rnu) { - // win_line() will redraw the number column only. + if ((wp->w_valid & VALID_VIRTCOL) == 0 && !pum_visible()) { + if (wp->w_p_cuc) { + // When 'cursorcolumn' is set need to redraw with SOME_VALID. + redraw_later(wp, SOME_VALID); + } else if (wp->w_p_cul && (wp->w_p_culopt_flags & CULOPT_SCRLINE)) { + // When 'cursorlineopt' contains "screenline" need to redraw with VALID. redraw_later(wp, VALID); } - if (win_cursorline_standout(wp)) { - if (wp->w_redr_type <= VALID && wp->w_last_cursorline != 0) { - // "w_last_cursorline" may be outdated, worst case we redraw - // too much. This is optimized for moving the cursor around in - // the current window. - redrawWinline(wp, wp->w_last_cursorline); - redrawWinline(wp, wp->w_cursor.lnum); - } else { - redraw_later(wp, SOME_VALID); - } - } + } + // If the cursor moves horizontally when 'concealcursor' is active, then the + // current line needs to be redrawn in order to calculate the correct + // cursor position. + if ((wp->w_valid & VALID_VIRTCOL) == 0 && wp->w_p_cole > 0 && conceal_cursor_line(wp)) { + redrawWinline(wp, wp->w_cursor.lnum); } } @@ -346,10 +351,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); } /* @@ -641,11 +646,8 @@ void validate_virtcol_win(win_T *wp) check_cursor_moved(wp); if (!(wp->w_valid & VALID_VIRTCOL)) { getvvcol(wp, &wp->w_cursor, NULL, &(wp->w_virtcol), NULL); + redraw_for_cursorcolumn(wp); wp->w_valid |= VALID_VIRTCOL; - if (wp->w_p_cuc - && !pum_visible()) { - redraw_later(wp, SOME_VALID); - } } } @@ -776,8 +778,13 @@ void curs_columns(win_T *wp, int may_scroll) int textwidth = wp->w_width_inner - extra; if (textwidth <= 0) { // No room for text, put cursor in last char of window. + // If not wrapping, the last non-empty line. wp->w_wcol = wp->w_width_inner - 1; - wp->w_wrow = wp->w_height_inner - 1; + if (wp->w_p_wrap) { + wp->w_wrow = wp->w_height_inner - 1; + } else { + wp->w_wrow = wp->w_height_inner - 1 - wp->w_empty_rows; + } } else if (wp->w_p_wrap && wp->w_width_inner != 0) { width = textwidth + win_col_off2(wp); @@ -909,7 +916,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 +927,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; @@ -948,11 +955,7 @@ void curs_columns(win_T *wp, int may_scroll) redraw_later(wp, NOT_VALID); } - // Redraw when w_virtcol changes and 'cursorcolumn' is set - if (wp->w_p_cuc && (wp->w_valid & VALID_VIRTCOL) == 0 - && !pum_visible()) { - redraw_later(wp, SOME_VALID); - } + redraw_for_cursorcolumn(curwin); // now w_leftcol is valid, avoid check_cursor_moved() thinking otherwise wp->w_valid_leftcol = wp->w_leftcol; @@ -1011,7 +1014,7 @@ void textpos2screenpos(win_T *wp, pos_T *pos, int *rowp, int *scolp, int *ccolp, col -= wp->w_leftcol; if (col >= 0 && col < wp->w_width) { - coloff = col - scol + (local ? 0 : wp->w_wincol) + 1; + coloff = col - scol + (local ? 0 : wp->w_wincol + wp->w_border_adj[3]) + 1; } else { scol = ccol = ecol = 0; // character is left or right of the window @@ -1022,7 +1025,7 @@ void textpos2screenpos(win_T *wp, pos_T *pos, int *rowp, int *scolp, int *ccolp, } } } - *rowp = (local ? 0 : wp->w_winrow) + row + rowoff; + *rowp = (local ? 0 : wp->w_winrow + wp->w_border_adj[0]) + row + rowoff; *scolp = scol + coloff; *ccolp = ccol + coloff; *ecolp = ecol + coloff; diff --git a/src/nvim/msgpack_rpc/channel.c b/src/nvim/msgpack_rpc/channel.c index a1a1f0f8c0..48ecd5d0ea 100644 --- a/src/nvim/msgpack_rpc/channel.c +++ b/src/nvim/msgpack_rpc/channel.c @@ -24,7 +24,6 @@ #include "nvim/map.h" #include "nvim/memory.h" #include "nvim/message.h" -#include "nvim/misc1.h" #include "nvim/msgpack_rpc/channel.h" #include "nvim/msgpack_rpc/helpers.h" #include "nvim/os/input.h" @@ -548,7 +547,8 @@ void rpc_close(Channel *channel) channel->rpc.closed = true; channel_decref(channel); - if (channel->streamtype == kChannelStreamStdio) { + if (channel->streamtype == kChannelStreamStdio + || channel->id == ui_client_channel_id) { multiqueue_put(main_loop.fast_events, exit_event, 0); } } diff --git a/src/nvim/msgpack_rpc/helpers.c b/src/nvim/msgpack_rpc/helpers.c index f805858904..32014fcf2b 100644 --- a/src/nvim/msgpack_rpc/helpers.c +++ b/src/nvim/msgpack_rpc/helpers.c @@ -92,15 +92,15 @@ bool msgpack_rpc_to_object(const msgpack_object *const obj, Object *const arg) break; } #define STR_CASE(type, attr, obj, dest, conv) \ -case type: { \ - dest = conv(((String) { \ + case type: { \ + dest = conv(((String) { \ .size = obj->via.attr.size, \ .data = (obj->via.attr.ptr == NULL || obj->via.attr.size == 0 \ ? xmemdupz("", 0) \ : xmemdupz(obj->via.attr.ptr, obj->via.attr.size)), \ })); \ - break; \ -} + break; \ + } STR_CASE(MSGPACK_OBJECT_STR, str, cur.mobj, *cur.aobj, STRING_OBJ) STR_CASE(MSGPACK_OBJECT_BIN, bin, cur.mobj, *cur.aobj, STRING_OBJ) case MSGPACK_OBJECT_ARRAY: { @@ -143,10 +143,10 @@ case type: { \ const msgpack_object *const key = &cur.mobj->via.map.ptr[idx].key; switch (key->type) { #define ID(x) x - STR_CASE(MSGPACK_OBJECT_STR, str, key, - cur.aobj->data.dictionary.items[idx].key, ID) - STR_CASE(MSGPACK_OBJECT_BIN, bin, key, - cur.aobj->data.dictionary.items[idx].key, ID) + STR_CASE(MSGPACK_OBJECT_STR, str, key, + cur.aobj->data.dictionary.items[idx].key, ID) + STR_CASE(MSGPACK_OBJECT_BIN, bin, key, + cur.aobj->data.dictionary.items[idx].key, ID) #undef ID case MSGPACK_OBJECT_NIL: case MSGPACK_OBJECT_BOOLEAN: diff --git a/src/nvim/normal.c b/src/nvim/normal.c index 03312e5df4..a8769c4c80 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -31,8 +31,8 @@ #include "nvim/fileio.h" #include "nvim/fold.h" #include "nvim/getchar.h" +#include "nvim/globals.h" #include "nvim/indent.h" -#include "nvim/indent_c.h" #include "nvim/keymap.h" #include "nvim/log.h" #include "nvim/main.h" @@ -40,7 +40,6 @@ #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/message.h" -#include "nvim/misc1.h" #include "nvim/mouse.h" #include "nvim/move.h" #include "nvim/normal.h" @@ -84,12 +83,6 @@ typedef struct normal_state { pos_T old_pos; } NormalState; -/* - * The Visual area is remembered for reselection. - */ -static int resel_VIsual_mode = NUL; // 'v', 'V', or Ctrl-V -static linenr_T resel_VIsual_line_count; // number of lines -static colnr_T resel_VIsual_vcol; // nr of cols or end col static int VIsual_mode_orig = NUL; // saved Visual mode @@ -171,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 }, @@ -236,7 +229,7 @@ static const struct nv_cmd { { 'N', nv_next, 0, SEARCH_REV }, { 'O', nv_open, 0, 0 }, { 'P', nv_put, 0, 0 }, - { 'Q', nv_exmode, NV_NCW, 0 }, + { 'Q', nv_regreplay, 0, 0 }, { 'R', nv_Replace, 0, false }, { 'S', nv_subst, NV_KEEPREG, 0 }, { 'T', nv_csearch, NV_NCH_ALW|NV_LANG, BACKWARD }, @@ -341,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[]. @@ -487,6 +481,7 @@ static void normal_prepare(NormalState *s) if (finish_op != c) { ui_cursor_shape(); // may show different cursor shape } + may_trigger_modechanged(); // When not finishing an operator and no register name typed, reset the count. if (!finish_op && !s->oa.regname) { @@ -829,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. @@ -928,6 +920,7 @@ normal_end: // Reset finish_op, in case it was set s->c = finish_op; finish_op = false; + may_trigger_modechanged(); // Redraw the cursor with another shape, if we were in Operator-pending // mode or did a replace command. if (s->c || s->ca.cmdchar == 'r') { @@ -965,6 +958,8 @@ normal_end: && s->oa.regname == 0) { if (restart_VIsual_select == 1) { VIsual_select = true; + VIsual_select_reg = 0; + may_trigger_modechanged(); showmode(); restart_VIsual_select = 0; } @@ -1013,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 { @@ -1025,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 @@ -1041,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, @@ -1221,22 +1223,18 @@ static void normal_check_interrupt(NormalState *s) static void normal_check_window_scrolled(NormalState *s) { - // Trigger Scroll if the viewport changed. - if (!finish_op && has_event(EVENT_WINSCROLLED) - && win_did_scroll(curwin)) { - do_autocmd_winscrolled(curwin); + if (!finish_op) { + // Trigger Scroll if the viewport changed. + may_trigger_winscrolled(); } } static void normal_check_cursor_moved(NormalState *s) { // Trigger CursorMoved if the cursor moved. - if (!finish_op && (has_event(EVENT_CURSORMOVED) || curwin->w_p_cole > 0) + if (!finish_op && has_event(EVENT_CURSORMOVED) && !equalpos(curwin->w_last_cursormoved, curwin->w_cursor)) { - if (has_event(EVENT_CURSORMOVED)) { - apply_autocmds(EVENT_CURSORMOVED, NULL, NULL, false, curbuf); - } - + apply_autocmds(EVENT_CURSORMOVED, NULL, NULL, false, curbuf); curwin->w_last_cursormoved = curwin->w_cursor; } } @@ -1286,22 +1284,6 @@ static void normal_redraw(NormalState *s) update_topline(curwin); validate_cursor(); - // If the cursor moves horizontally when 'concealcursor' is active, then the - // current line needs to be redrawn in order to calculate the correct - // cursor position. - if (curwin->w_p_cole > 0 && conceal_cursor_line(curwin)) { - redrawWinline(curwin, curwin->w_cursor.lnum); - } - - // Might need to update for 'cursorline'. - // When 'cursorlineopt' is "screenline" need to redraw always. - if (curwin->w_p_cul - && (curwin->w_last_cursorline != curwin->w_cursor.lnum - || (curwin->w_p_culopt_flags & CULOPT_SCRLINE)) - && !char_avail()) { - redraw_later(curwin, VALID); - } - if (VIsual_active) { update_curbuf(INVERTED); // update inverted part } else if (must_redraw) { @@ -1370,9 +1352,10 @@ static int normal_check(VimState *state) if (skip_redraw || exmode_active) { skip_redraw = false; } else if (do_redraw || stuff_empty()) { - // Need to make sure w_topline and w_leftcol are correct before - // normal_check_window_scrolled() is called. + // Ensure curwin->w_topline and curwin->w_leftcol are up to date + // before triggering a WinScrolled autocommand. update_topline(curwin); + validate_cursor(); normal_check_cursor_moved(s); normal_check_text_changed(s); @@ -1452,758 +1435,6 @@ static void set_vcount_ca(cmdarg_T *cap, bool *set_prevcount) *set_prevcount = false; // only set v:prevcount once } -// Handle an operator after Visual mode or when the movement is finished. -// "gui_yank" is true when yanking text for the clipboard. -void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank) -{ - oparg_T *oap = cap->oap; - pos_T old_cursor; - bool empty_region_error; - int restart_edit_save; - int lbr_saved = curwin->w_p_lbr; - - - // The visual area is remembered for redo - static int redo_VIsual_mode = NUL; // 'v', 'V', or Ctrl-V - static linenr_T redo_VIsual_line_count; // number of lines - static colnr_T redo_VIsual_vcol; // number of cols or end column - static long redo_VIsual_count; // count for Visual operator - static int redo_VIsual_arg; // extra argument - bool include_line_break = false; - - old_cursor = curwin->w_cursor; - - /* - * If an operation is pending, handle it... - */ - if ((finish_op - || VIsual_active) - && oap->op_type != OP_NOP) { - // Yank can be redone when 'y' is in 'cpoptions', but not when yanking - // for the clipboard. - const bool redo_yank = vim_strchr(p_cpo, CPO_YANK) != NULL && !gui_yank; - - // Avoid a problem with unwanted linebreaks in block mode - if (curwin->w_p_lbr) { - curwin->w_valid &= ~VALID_VIRTCOL; - } - curwin->w_p_lbr = false; - oap->is_VIsual = VIsual_active; - if (oap->motion_force == 'V') { - oap->motion_type = kMTLineWise; - } else if (oap->motion_force == 'v') { - // If the motion was linewise, "inclusive" will not have been set. - // Use "exclusive" to be consistent. Makes "dvj" work nice. - if (oap->motion_type == kMTLineWise) { - oap->inclusive = false; - } else if (oap->motion_type == kMTCharWise) { - // If the motion already was charwise, toggle "inclusive" - oap->inclusive = !oap->inclusive; - } - oap->motion_type = kMTCharWise; - } else if (oap->motion_force == Ctrl_V) { - // Change line- or charwise motion into Visual block mode. - if (!VIsual_active) { - VIsual_active = true; - VIsual = oap->start; - } - VIsual_mode = Ctrl_V; - VIsual_select = false; - VIsual_reselect = false; - } - - // Only redo yank when 'y' flag is in 'cpoptions'. - // Never redo "zf" (define fold). - if ((redo_yank || oap->op_type != OP_YANK) - && ((!VIsual_active || oap->motion_force) - // Also redo Operator-pending Visual mode mappings. - || ((cap->cmdchar == ':' || cap->cmdchar == K_COMMAND) - && oap->op_type != OP_COLON)) - && cap->cmdchar != 'D' - && oap->op_type != OP_FOLD - && oap->op_type != OP_FOLDOPEN - && oap->op_type != OP_FOLDOPENREC - && oap->op_type != OP_FOLDCLOSE - && oap->op_type != OP_FOLDCLOSEREC - && oap->op_type != OP_FOLDDEL - && oap->op_type != OP_FOLDDELREC) { - prep_redo(oap->regname, cap->count0, - get_op_char(oap->op_type), get_extra_op_char(oap->op_type), - oap->motion_force, cap->cmdchar, cap->nchar); - if (cap->cmdchar == '/' || cap->cmdchar == '?') { // was a search - /* - * If 'cpoptions' does not contain 'r', insert the search - * pattern to really repeat the same command. - */ - if (vim_strchr(p_cpo, CPO_REDO) == NULL) { - AppendToRedobuffLit(cap->searchbuf, -1); - } - AppendToRedobuff(NL_STR); - } else if (cap->cmdchar == ':' || cap->cmdchar == K_COMMAND) { - // do_cmdline() has stored the first typed line in - // "repeat_cmdline". When several lines are typed repeating - // won't be possible. - if (repeat_cmdline == NULL) { - ResetRedobuff(); - } else { - AppendToRedobuffLit(repeat_cmdline, -1); - AppendToRedobuff(NL_STR); - XFREE_CLEAR(repeat_cmdline); - } - } - } - - if (redo_VIsual_busy) { - /* Redo of an operation on a Visual area. Use the same size from - * redo_VIsual_line_count and redo_VIsual_vcol. */ - oap->start = curwin->w_cursor; - curwin->w_cursor.lnum += redo_VIsual_line_count - 1; - if (curwin->w_cursor.lnum > curbuf->b_ml.ml_line_count) { - curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count; - } - VIsual_mode = redo_VIsual_mode; - if (redo_VIsual_vcol == MAXCOL || VIsual_mode == 'v') { - if (VIsual_mode == 'v') { - if (redo_VIsual_line_count <= 1) { - validate_virtcol(); - curwin->w_curswant = - curwin->w_virtcol + redo_VIsual_vcol - 1; - } else { - curwin->w_curswant = redo_VIsual_vcol; - } - } else { - curwin->w_curswant = MAXCOL; - } - coladvance(curwin->w_curswant); - } - cap->count0 = redo_VIsual_count; - cap->count1 = (cap->count0 == 0 ? 1 : cap->count0); - } else if (VIsual_active) { - if (!gui_yank) { - // Save the current VIsual area for '< and '> marks, and "gv" - curbuf->b_visual.vi_start = VIsual; - curbuf->b_visual.vi_end = curwin->w_cursor; - curbuf->b_visual.vi_mode = VIsual_mode; - if (VIsual_mode_orig != NUL) { - curbuf->b_visual.vi_mode = VIsual_mode_orig; - VIsual_mode_orig = NUL; - } - curbuf->b_visual.vi_curswant = curwin->w_curswant; - curbuf->b_visual_mode_eval = VIsual_mode; - } - - // In Select mode, a linewise selection is operated upon like a - // charwise selection. - // Special case: gH<Del> deletes the last line. - if (VIsual_select && VIsual_mode == 'V' - && cap->oap->op_type != OP_DELETE) { - if (lt(VIsual, curwin->w_cursor)) { - VIsual.col = 0; - curwin->w_cursor.col = - (colnr_T)STRLEN(ml_get(curwin->w_cursor.lnum)); - } else { - curwin->w_cursor.col = 0; - VIsual.col = (colnr_T)STRLEN(ml_get(VIsual.lnum)); - } - VIsual_mode = 'v'; - } - /* If 'selection' is "exclusive", backup one character for - * charwise selections. */ - else if (VIsual_mode == 'v') { - include_line_break = - unadjust_for_sel(); - } - - oap->start = VIsual; - if (VIsual_mode == 'V') { - oap->start.col = 0; - oap->start.coladd = 0; - } - } - - /* - * Set oap->start to the first position of the operated text, oap->end - * to the end of the operated text. w_cursor is equal to oap->start. - */ - if (lt(oap->start, curwin->w_cursor)) { - // Include folded lines completely. - if (!VIsual_active) { - if (hasFolding(oap->start.lnum, &oap->start.lnum, NULL)) { - oap->start.col = 0; - } - if ((curwin->w_cursor.col > 0 - || oap->inclusive - || oap->motion_type == kMTLineWise) - && hasFolding(curwin->w_cursor.lnum, NULL, - &curwin->w_cursor.lnum)) { - curwin->w_cursor.col = (colnr_T)STRLEN(get_cursor_line_ptr()); - } - } - oap->end = curwin->w_cursor; - curwin->w_cursor = oap->start; - - /* w_virtcol may have been updated; if the cursor goes back to its - * previous position w_virtcol becomes invalid and isn't updated - * automatically. */ - curwin->w_valid &= ~VALID_VIRTCOL; - } else { - // Include folded lines completely. - if (!VIsual_active && oap->motion_type == kMTLineWise) { - if (hasFolding(curwin->w_cursor.lnum, &curwin->w_cursor.lnum, - NULL)) { - curwin->w_cursor.col = 0; - } - if (hasFolding(oap->start.lnum, NULL, &oap->start.lnum)) { - oap->start.col = (colnr_T)STRLEN(ml_get(oap->start.lnum)); - } - } - oap->end = oap->start; - oap->start = curwin->w_cursor; - } - - // Just in case lines were deleted that make the position invalid. - check_pos(curwin->w_buffer, &oap->end); - oap->line_count = oap->end.lnum - oap->start.lnum + 1; - - // Set "virtual_op" before resetting VIsual_active. - virtual_op = virtual_active(); - - if (VIsual_active || redo_VIsual_busy) { - get_op_vcol(oap, redo_VIsual_vcol, true); - - if (!redo_VIsual_busy && !gui_yank) { - /* - * Prepare to reselect and redo Visual: this is based on the - * size of the Visual text - */ - resel_VIsual_mode = VIsual_mode; - if (curwin->w_curswant == MAXCOL) { - resel_VIsual_vcol = MAXCOL; - } else { - if (VIsual_mode != Ctrl_V) { - getvvcol(curwin, &(oap->end), - NULL, NULL, &oap->end_vcol); - } - if (VIsual_mode == Ctrl_V || oap->line_count <= 1) { - if (VIsual_mode != Ctrl_V) { - getvvcol(curwin, &(oap->start), - &oap->start_vcol, NULL, NULL); - } - resel_VIsual_vcol = oap->end_vcol - oap->start_vcol + 1; - } else { - resel_VIsual_vcol = oap->end_vcol; - } - } - resel_VIsual_line_count = oap->line_count; - } - - // can't redo yank (unless 'y' is in 'cpoptions') and ":" - if ((redo_yank || oap->op_type != OP_YANK) - && oap->op_type != OP_COLON - && oap->op_type != OP_FOLD - && oap->op_type != OP_FOLDOPEN - && oap->op_type != OP_FOLDOPENREC - && oap->op_type != OP_FOLDCLOSE - && oap->op_type != OP_FOLDCLOSEREC - && oap->op_type != OP_FOLDDEL - && oap->op_type != OP_FOLDDELREC - && oap->motion_force == NUL) { - /* Prepare for redoing. Only use the nchar field for "r", - * otherwise it might be the second char of the operator. */ - if (cap->cmdchar == 'g' && (cap->nchar == 'n' - || cap->nchar == 'N')) { - prep_redo(oap->regname, cap->count0, - get_op_char(oap->op_type), get_extra_op_char(oap->op_type), - oap->motion_force, cap->cmdchar, cap->nchar); - } else if (cap->cmdchar != ':' && cap->cmdchar != K_COMMAND) { - int nchar = oap->op_type == OP_REPLACE ? cap->nchar : NUL; - - // reverse what nv_replace() did - if (nchar == REPLACE_CR_NCHAR) { - nchar = CAR; - } else if (nchar == REPLACE_NL_NCHAR) { - nchar = NL; - } - prep_redo(oap->regname, 0L, NUL, 'v', get_op_char(oap->op_type), - get_extra_op_char(oap->op_type), nchar); - } - if (!redo_VIsual_busy) { - redo_VIsual_mode = resel_VIsual_mode; - redo_VIsual_vcol = resel_VIsual_vcol; - redo_VIsual_line_count = resel_VIsual_line_count; - redo_VIsual_count = cap->count0; - redo_VIsual_arg = cap->arg; - } - } - - // oap->inclusive defaults to true. - // If oap->end is on a NUL (empty line) oap->inclusive becomes - // false. This makes "d}P" and "v}dP" work the same. - if (oap->motion_force == NUL || oap->motion_type == kMTLineWise) { - oap->inclusive = true; - } - if (VIsual_mode == 'V') { - oap->motion_type = kMTLineWise; - } else if (VIsual_mode == 'v') { - oap->motion_type = kMTCharWise; - if (*ml_get_pos(&(oap->end)) == NUL - && (include_line_break || !virtual_op)) { - oap->inclusive = false; - // Try to include the newline, unless it's an operator - // that works on lines only. - if (*p_sel != 'o' - && !op_on_lines(oap->op_type) - && oap->end.lnum < curbuf->b_ml.ml_line_count) { - oap->end.lnum++; - oap->end.col = 0; - oap->end.coladd = 0; - oap->line_count++; - } - } - } - - redo_VIsual_busy = false; - - /* - * Switch Visual off now, so screen updating does - * not show inverted text when the screen is redrawn. - * With OP_YANK and sometimes with OP_COLON and OP_FILTER there is - * no screen redraw, so it is done here to remove the inverted - * part. - */ - if (!gui_yank) { - VIsual_active = false; - setmouse(); - mouse_dragging = 0; - may_clear_cmdline(); - if ((oap->op_type == OP_YANK - || oap->op_type == OP_COLON - || oap->op_type == OP_FUNCTION - || oap->op_type == OP_FILTER) - && oap->motion_force == NUL) { - // Make sure redrawing is correct. - curwin->w_p_lbr = lbr_saved; - redraw_curbuf_later(INVERTED); - } - } - } - - // Include the trailing byte of a multi-byte char. - if (oap->inclusive) { - const int l = utfc_ptr2len(ml_get_pos(&oap->end)); - if (l > 1) { - oap->end.col += l - 1; - } - } - curwin->w_set_curswant = true; - - /* - * oap->empty is set when start and end are the same. The inclusive - * flag affects this too, unless yanking and the end is on a NUL. - */ - oap->empty = (oap->motion_type != kMTLineWise - && (!oap->inclusive - || (oap->op_type == OP_YANK - && gchar_pos(&oap->end) == NUL)) - && equalpos(oap->start, oap->end) - && !(virtual_op && oap->start.coladd != oap->end.coladd) - ); - /* - * For delete, change and yank, it's an error to operate on an - * empty region, when 'E' included in 'cpoptions' (Vi compatible). - */ - empty_region_error = (oap->empty - && vim_strchr(p_cpo, CPO_EMPTYREGION) != NULL); - - /* Force a redraw when operating on an empty Visual region, when - * 'modifiable is off or creating a fold. */ - if (oap->is_VIsual && (oap->empty || !MODIFIABLE(curbuf) - || oap->op_type == OP_FOLD - )) { - curwin->w_p_lbr = lbr_saved; - redraw_curbuf_later(INVERTED); - } - - /* - * If the end of an operator is in column one while oap->motion_type - * is kMTCharWise and oap->inclusive is false, we put op_end after the last - * character in the previous line. If op_start is on or before the - * first non-blank in the line, the operator becomes linewise - * (strange, but that's the way vi does it). - */ - if (oap->motion_type == kMTCharWise - && oap->inclusive == false - && !(cap->retval & CA_NO_ADJ_OP_END) - && oap->end.col == 0 - && (!oap->is_VIsual || *p_sel == 'o') - && oap->line_count > 1) { - oap->end_adjusted = true; // remember that we did this - oap->line_count--; - oap->end.lnum--; - if (inindent(0)) { - oap->motion_type = kMTLineWise; - } else { - oap->end.col = (colnr_T)STRLEN(ml_get(oap->end.lnum)); - if (oap->end.col) { - --oap->end.col; - oap->inclusive = true; - } - } - } else { - oap->end_adjusted = false; - } - - switch (oap->op_type) { - case OP_LSHIFT: - case OP_RSHIFT: - op_shift(oap, true, - oap->is_VIsual ? (int)cap->count1 : - 1); - auto_format(false, true); - break; - - case OP_JOIN_NS: - case OP_JOIN: - if (oap->line_count < 2) { - oap->line_count = 2; - } - if (curwin->w_cursor.lnum + oap->line_count - 1 > - curbuf->b_ml.ml_line_count) { - beep_flush(); - } else { - do_join((size_t)oap->line_count, oap->op_type == OP_JOIN, - true, true, true); - auto_format(false, true); - } - break; - - case OP_DELETE: - VIsual_reselect = false; // don't reselect now - if (empty_region_error) { - vim_beep(BO_OPER); - CancelRedo(); - } else { - (void)op_delete(oap); - // save cursor line for undo if it wasn't saved yet - if (oap->motion_type == kMTLineWise - && has_format_option(FO_AUTO) - && u_save_cursor() == OK) { - auto_format(false, true); - } - } - break; - - case OP_YANK: - if (empty_region_error) { - if (!gui_yank) { - vim_beep(BO_OPER); - CancelRedo(); - } - } else { - curwin->w_p_lbr = lbr_saved; - oap->excl_tr_ws = cap->cmdchar == 'z'; - (void)op_yank(oap, !gui_yank, false); - } - check_cursor_col(); - break; - - case OP_CHANGE: - VIsual_reselect = false; // don't reselect now - if (empty_region_error) { - vim_beep(BO_OPER); - CancelRedo(); - } else { - /* This is a new edit command, not a restart. Need to - * remember it to make 'insertmode' work with mappings for - * Visual mode. But do this only once and not when typed and - * 'insertmode' isn't set. */ - if (p_im || !KeyTyped) { - restart_edit_save = restart_edit; - } else { - restart_edit_save = 0; - } - restart_edit = 0; - - // Restore linebreak, so that when the user edits it looks as before. - curwin->w_p_lbr = lbr_saved; - - // Reset finish_op now, don't want it set inside edit(). - finish_op = false; - if (op_change(oap)) { // will call edit() - cap->retval |= CA_COMMAND_BUSY; - } - if (restart_edit == 0) { - restart_edit = restart_edit_save; - } - } - break; - - case OP_FILTER: - if (vim_strchr(p_cpo, CPO_FILTER) != NULL) { - AppendToRedobuff("!\r"); // Use any last used !cmd. - } else { - bangredo = true; // do_bang() will put cmd in redo buffer. - } - FALLTHROUGH; - - case OP_INDENT: - case OP_COLON: - - /* - * If 'equalprg' is empty, do the indenting internally. - */ - if (oap->op_type == OP_INDENT && *get_equalprg() == NUL) { - if (curbuf->b_p_lisp) { - op_reindent(oap, get_lisp_indent); - break; - } - op_reindent(oap, - *curbuf->b_p_inde != NUL ? get_expr_indent : - get_c_indent); - break; - } - - op_colon(oap); - break; - - case OP_TILDE: - case OP_UPPER: - case OP_LOWER: - case OP_ROT13: - if (empty_region_error) { - vim_beep(BO_OPER); - CancelRedo(); - } else { - op_tilde(oap); - } - check_cursor_col(); - break; - - case OP_FORMAT: - if (*curbuf->b_p_fex != NUL) { - op_formatexpr(oap); // use expression - } else { - if (*p_fp != NUL || *curbuf->b_p_fp != NUL) { - op_colon(oap); // use external command - } else { - op_format(oap, false); // use internal function - } - } - break; - - case OP_FORMAT2: - op_format(oap, true); // use internal function - break; - - case OP_FUNCTION: - // Restore linebreak, so that when the user edits it looks as - // before. - curwin->w_p_lbr = lbr_saved; - op_function(oap); // call 'operatorfunc' - break; - - case OP_INSERT: - case OP_APPEND: - VIsual_reselect = false; // don't reselect now - if (empty_region_error) { - vim_beep(BO_OPER); - CancelRedo(); - } else { - /* This is a new edit command, not a restart. Need to - * remember it to make 'insertmode' work with mappings for - * Visual mode. But do this only once. */ - restart_edit_save = restart_edit; - restart_edit = 0; - - // Restore linebreak, so that when the user edits it looks as before. - curwin->w_p_lbr = lbr_saved; - - op_insert(oap, cap->count1); - - // Reset linebreak, so that formatting works correctly. - curwin->w_p_lbr = false; - - /* TODO: when inserting in several lines, should format all - * the lines. */ - auto_format(false, true); - - if (restart_edit == 0) { - restart_edit = restart_edit_save; - } else { - cap->retval |= CA_COMMAND_BUSY; - } - } - break; - - case OP_REPLACE: - VIsual_reselect = false; // don't reselect now - if (empty_region_error) { - vim_beep(BO_OPER); - CancelRedo(); - } else { - // Restore linebreak, so that when the user edits it looks as before. - curwin->w_p_lbr = lbr_saved; - - op_replace(oap, cap->nchar); - } - break; - - case OP_FOLD: - VIsual_reselect = false; // don't reselect now - foldCreate(curwin, oap->start, oap->end); - break; - - case OP_FOLDOPEN: - case OP_FOLDOPENREC: - case OP_FOLDCLOSE: - case OP_FOLDCLOSEREC: - VIsual_reselect = false; // don't reselect now - opFoldRange(oap->start, oap->end, - oap->op_type == OP_FOLDOPEN - || oap->op_type == OP_FOLDOPENREC, - oap->op_type == OP_FOLDOPENREC - || oap->op_type == OP_FOLDCLOSEREC, - oap->is_VIsual); - break; - - case OP_FOLDDEL: - case OP_FOLDDELREC: - VIsual_reselect = false; // don't reselect now - deleteFold(curwin, oap->start.lnum, oap->end.lnum, - oap->op_type == OP_FOLDDELREC, oap->is_VIsual); - break; - - case OP_NR_ADD: - case OP_NR_SUB: - if (empty_region_error) { - vim_beep(BO_OPER); - CancelRedo(); - } else { - VIsual_active = true; - curwin->w_p_lbr = lbr_saved; - op_addsub(oap, cap->count1, redo_VIsual_arg); - VIsual_active = false; - } - check_cursor_col(); - break; - default: - clearopbeep(oap); - } - virtual_op = kNone; - if (!gui_yank) { - /* - * if 'sol' not set, go back to old column for some commands - */ - if (!p_sol && oap->motion_type == kMTLineWise && !oap->end_adjusted - && (oap->op_type == OP_LSHIFT || oap->op_type == OP_RSHIFT - || oap->op_type == OP_DELETE)) { - curwin->w_p_lbr = false; - coladvance(curwin->w_curswant = old_col); - } - } else { - curwin->w_cursor = old_cursor; - } - clearop(oap); - motion_force = NUL; - } - curwin->w_p_lbr = lbr_saved; -} - -/* - * Handle indent and format operators and visual mode ":". - */ -static void op_colon(oparg_T *oap) -{ - stuffcharReadbuff(':'); - if (oap->is_VIsual) { - stuffReadbuff("'<,'>"); - } else { - // Make the range look nice, so it can be repeated. - if (oap->start.lnum == curwin->w_cursor.lnum) { - stuffcharReadbuff('.'); - } else { - stuffnumReadbuff((long)oap->start.lnum); - } - if (oap->end.lnum != oap->start.lnum) { - stuffcharReadbuff(','); - if (oap->end.lnum == curwin->w_cursor.lnum) { - stuffcharReadbuff('.'); - } else if (oap->end.lnum == curbuf->b_ml.ml_line_count) { - stuffcharReadbuff('$'); - } else if (oap->start.lnum == curwin->w_cursor.lnum) { - stuffReadbuff(".+"); - stuffnumReadbuff(oap->line_count - 1); - } else { - stuffnumReadbuff((long)oap->end.lnum); - } - } - } - if (oap->op_type != OP_COLON) { - stuffReadbuff("!"); - } - if (oap->op_type == OP_INDENT) { - stuffReadbuff((const char *)get_equalprg()); - stuffReadbuff("\n"); - } else if (oap->op_type == OP_FORMAT) { - if (*curbuf->b_p_fp != NUL) { - stuffReadbuff((const char *)curbuf->b_p_fp); - } else if (*p_fp != NUL) { - stuffReadbuff((const char *)p_fp); - } else { - stuffReadbuff("fmt"); - } - stuffReadbuff("\n']"); - } - - /* - * do_cmdline() does the rest - */ -} - -/* - * Handle the "g@" operator: call 'operatorfunc'. - */ -static void op_function(const oparg_T *oap) - FUNC_ATTR_NONNULL_ALL -{ - const TriState save_virtual_op = virtual_op; - const bool save_finish_op = finish_op; - - if (*p_opfunc == NUL) { - emsg(_("E774: 'operatorfunc' is empty")); - } else { - // Set '[ and '] marks to text to be operated on. - curbuf->b_op_start = oap->start; - curbuf->b_op_end = oap->end; - if (oap->motion_type != kMTLineWise && !oap->inclusive) { - // Exclude the end position. - decl(&curbuf->b_op_end); - } - - typval_T argv[2]; - argv[0].v_type = VAR_STRING; - argv[1].v_type = VAR_UNKNOWN; - argv[0].vval.v_string = - (char_u *)(((const char *const[]) { - [kMTBlockWise] = "block", - [kMTLineWise] = "line", - [kMTCharWise] = "char", - })[oap->motion_type]); - - // Reset virtual_op so that 'virtualedit' can be changed in the - // function. - virtual_op = kNone; - - // Reset finish_op so that mode() returns the right value. - finish_op = false; - - (void)call_func_retnr(p_opfunc, 1, argv); - - virtual_op = save_virtual_op; - finish_op = save_finish_op; - } -} - // Move the current tab to tab in same column as mouse or to end of the // tabline if there is no tab there. static void move_tab_to_mouse(void) @@ -2286,9 +1517,12 @@ bool do_mouse(oparg_T *oap, int c, int dir, long count, bool fixindent) for (;;) { which_button = get_mouse_button(KEY2TERMCAP1(c), &is_click, &is_drag); if (is_drag) { - /* If the next character is the same mouse event then use that - * one. Speeds up dragging the status line. */ - if (vpeekc() != NUL) { + // If the next character is the same mouse event then use that + // one. Speeds up dragging the status line. + // Note: Since characters added to the stuff buffer in the code + // below need to come before the next character, do not do this + // when the current character was stuffed. + if (!KeyStuffed && vpeekc() != NUL) { int nc; int save_mouse_grid = mouse_grid; int save_mouse_row = mouse_row; @@ -2855,10 +2089,9 @@ bool do_mouse(oparg_T *oap, int c, int dir, long count, bool fixindent) } else { // MOUSE_RIGHT stuffcharReadbuff('#'); } - } - // Handle double clicks, unless on status line - else if (in_status_line) { - } else if (in_sep_line) { + } else if (in_status_line || in_sep_line) { + // Do nothing if on status line or vertical separator + // Handle double clicks otherwise } else if ((mod_mask & MOD_MASK_MULTI_CLICK) && (State & (NORMAL | INSERT))) { if (is_click || !VIsual_active) { if (VIsual_active) { @@ -3066,6 +2299,7 @@ void end_visual_mode(void) may_clear_cmdline(); adjust_cursor_eol(); + may_trigger_modechanged(); } /* @@ -3092,6 +2326,14 @@ void reset_VIsual(void) } } +void restore_visual_mode(void) +{ + if (VIsual_mode_orig != NUL) { + curbuf->b_visual.vi_mode = VIsual_mode_orig; + VIsual_mode_orig = NUL; + } +} + // Check for a balloon-eval special item to include when searching for an // identifier. When "dir" is BACKWARD "ptr[-1]" must be valid! // Returns true if the character at "*ptr" should be included. @@ -3270,7 +2512,7 @@ static void prep_redo_cmd(cmdarg_T *cap) * Prepare for redo of any command. * Note that only the last argument can be a multi-byte char. */ -static void prep_redo(int regname, long num, int cmd1, int cmd2, int cmd3, int cmd4, int cmd5) +void prep_redo(int regname, long num, int cmd1, int cmd2, int cmd3, int cmd4, int cmd5) { ResetRedobuff(); if (regname != 0) { // yank from specified buffer @@ -3327,7 +2569,7 @@ static bool checkclearopq(oparg_T *oap) return true; } -static void clearop(oparg_T *oap) +void clearop(oparg_T *oap) { oap->op_type = OP_NOP; oap->regname = 0; @@ -3336,7 +2578,7 @@ static void clearop(oparg_T *oap) motion_force = NUL; } -static void clearopbeep(oparg_T *oap) +void clearopbeep(oparg_T *oap) { clearop(oap); beep_flush(); @@ -3366,7 +2608,7 @@ static void unshift_special(cmdarg_T *cap) /// If the mode is currently displayed clear the command line or update the /// command displayed. -static void may_clear_cmdline(void) +void may_clear_cmdline(void) { if (mode_displayed) { // unshow visual mode later @@ -3377,7 +2619,7 @@ static void may_clear_cmdline(void) } // Routines for displaying a partly typed command -#define SHOWCMD_BUFLEN SHOWCMD_COLS + 1 + 30 +#define SHOWCMD_BUFLEN (SHOWCMD_COLS + 1 + 30) static char_u showcmd_buf[SHOWCMD_BUFLEN]; static char_u old_showcmd_buf[SHOWCMD_BUFLEN]; // For push_showcmd() static bool showcmd_is_clear = true; @@ -3836,8 +3078,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; + } } } @@ -4027,7 +3275,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); @@ -4151,6 +3399,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) @@ -4297,7 +3552,6 @@ static void nv_zet(cmdarg_T *cap) bool undo = false; int l_p_siso = (int)get_sidescrolloff_value(curwin); - assert(l_p_siso <= INT_MAX); if (ascii_isdigit(nchar)) { /* @@ -4775,33 +4029,37 @@ dozet: /* * "Q" command. */ -static void nv_exmode(cmdarg_T *cap) +static void nv_regreplay(cmdarg_T *cap) { - /* - * Ignore 'Q' in Visual mode, just give a beep. - */ - if (VIsual_active) { - vim_beep(BO_EX); - } else if (!checkclearop(cap->oap)) { - do_exmode(); + if (checkclearop(cap->oap)) { + return; + } + + while (cap->count1-- && !got_int) { + if (do_execreg(reg_recorded, false, false, false) == false) { + clearopbeep(cap->oap); + break; + } + line_breakcheck(); } } -/// 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) { @@ -4817,9 +4075,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) { @@ -4851,6 +4113,7 @@ static void nv_ctrlg(cmdarg_T *cap) { if (VIsual_active) { // toggle Selection/Visual mode VIsual_select = !VIsual_select; + may_trigger_modechanged(); showmode(); } else if (!checkclearop(cap->oap)) { // print full name if count given or :cd used @@ -4894,6 +4157,7 @@ static void nv_ctrlo(cmdarg_T *cap) { if (VIsual_active && VIsual_select) { VIsual_select = false; + may_trigger_modechanged(); showmode(); restart_VIsual_select = 2; // restart Select mode later } else { @@ -5173,11 +4437,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); } } @@ -5212,8 +4472,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; @@ -5747,9 +5012,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; @@ -6590,7 +5853,7 @@ static void nv_gomark(cmdarg_T *cap) } } -// Handle CTRL-O, CTRL-I, "g;", "g,", and "CTRL-Tab" commands. +/// Handle CTRL-O, CTRL-I, "g;", "g,", and "CTRL-Tab" commands. static void nv_pcmark(cmdarg_T *cap) { pos_T *pos; @@ -6599,7 +5862,9 @@ static void nv_pcmark(cmdarg_T *cap) if (!checkclearopq(cap->oap)) { if (cap->cmdchar == TAB && mod_mask == MOD_MASK_CTRL) { - goto_tabpage_lastused(); + if (!goto_tabpage_lastused()) { + clearopbeep(cap->oap); + } return; } if (cap->cmdchar == 'g') { @@ -6680,6 +5945,7 @@ static void nv_visual(cmdarg_T *cap) // or char/line mode VIsual_mode = cap->cmdchar; showmode(); + may_trigger_modechanged(); } redraw_curbuf_later(INVERTED); // update the inversion } else { // start Visual mode @@ -6702,11 +5968,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') { @@ -6785,7 +6048,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); } @@ -6793,6 +6056,7 @@ static void n_start_visual_mode(int c) foldAdjustVisual(); + may_trigger_modechanged(); setmouse(); // Check for redraw after changing the state. conceal_check_cursor_line(); @@ -6924,6 +6188,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'); } @@ -7050,20 +6315,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 @@ -7366,9 +6628,10 @@ static void nv_g_cmd(cmdarg_T *cap) goto_tabpage(-(int)cap->count1); } break; + case TAB: - if (!checkclearop(oap)) { - goto_tabpage_lastused(); + if (!checkclearop(oap) && !goto_tabpage_lastused()) { + clearopbeep(oap); } break; @@ -7407,9 +6670,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; @@ -7436,11 +6698,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; @@ -7689,7 +6966,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(); @@ -7730,7 +7007,7 @@ static void adjust_for_sel(cmdarg_T *cap) * Should check VIsual_mode before calling this. * Returns true when backed up to the previous line. */ -static bool unadjust_for_sel(void) +bool unadjust_for_sel(void) { pos_T *pp; @@ -7761,6 +7038,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; @@ -7895,7 +7173,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 @@ -8213,9 +7491,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 @@ -8228,6 +7506,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; @@ -8237,6 +7519,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; } @@ -8320,71 +7606,6 @@ static void nv_open(cmdarg_T *cap) } } -/// Calculate start/end virtual columns for operating in block mode. -/// -/// @param initial when true: adjust position for 'selectmode' -static void get_op_vcol(oparg_T *oap, colnr_T redo_VIsual_vcol, bool initial) -{ - colnr_T start; - colnr_T end; - - if (VIsual_mode != Ctrl_V - || (!initial && oap->end.col < curwin->w_width_inner)) { - return; - } - - oap->motion_type = kMTBlockWise; - - // prevent from moving onto a trail byte - mark_mb_adjustpos(curwin->w_buffer, &oap->end); - - getvvcol(curwin, &(oap->start), &oap->start_vcol, NULL, &oap->end_vcol); - if (!redo_VIsual_busy) { - getvvcol(curwin, &(oap->end), &start, NULL, &end); - - if (start < oap->start_vcol) { - oap->start_vcol = start; - } - if (end > oap->end_vcol) { - if (initial && *p_sel == 'e' - && start >= 1 - && start - 1 >= oap->end_vcol) { - oap->end_vcol = start - 1; - } else { - oap->end_vcol = end; - } - } - } - - // if '$' was used, get oap->end_vcol from longest line - if (curwin->w_curswant == MAXCOL) { - curwin->w_cursor.col = MAXCOL; - oap->end_vcol = 0; - for (curwin->w_cursor.lnum = oap->start.lnum; - curwin->w_cursor.lnum <= oap->end.lnum; ++curwin->w_cursor.lnum) { - getvvcol(curwin, &curwin->w_cursor, NULL, NULL, &end); - if (end > oap->end_vcol) { - oap->end_vcol = end; - } - } - } else if (redo_VIsual_busy) { - oap->end_vcol = oap->start_vcol + redo_VIsual_vcol - 1; - } - - // Correct oap->end.col and oap->start.col to be the - // upper-left and lower-right corner of the block area. - // - // (Actually, this does convert column positions into character - // positions) - curwin->w_cursor.lnum = oap->end.lnum; - coladvance(oap->end_vcol); - oap->end = curwin->w_cursor; - - curwin->w_cursor = oap->start; - coladvance(oap->start_vcol); - oap->start = curwin->w_cursor; -} - // Handle an arbitrary event in normal mode static void nv_event(cmdarg_T *cap) { diff --git a/src/nvim/ops.c b/src/nvim/ops.c index b4b9545daf..badc00fb39 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -27,7 +27,9 @@ #include "nvim/fileio.h" #include "nvim/fold.h" #include "nvim/getchar.h" +#include "nvim/globals.h" #include "nvim/indent.h" +#include "nvim/indent_c.h" #include "nvim/lib/kvec.h" #include "nvim/log.h" #include "nvim/macros.h" @@ -36,7 +38,7 @@ #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/message.h" -#include "nvim/misc1.h" +#include "nvim/mouse.h" #include "nvim/move.h" #include "nvim/normal.h" #include "nvim/ops.h" @@ -133,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; @@ -265,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); @@ -379,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; } @@ -558,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); @@ -587,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) { @@ -692,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; + } } /* @@ -910,30 +924,45 @@ int do_record(int c) showmode(); regname = c; retval = OK; + apply_autocmds(EVENT_RECORDINGENTER, NULL, NULL, false, curbuf); } - } else { // stop recording - /* - * Get the recorded key hits. K_SPECIAL and CSI will be escaped, this - * needs to be removed again to put it in a register. exec_reg then - * adds the escaping back later. - */ + } else { // stop recording + 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); + tv_dict_set_keys_readonly(dict); + + // 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)) { showmode(); } 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. - */ + // We don't want to change the default register here, so save and + // restore the current register name. old_y_previous = y_previous; retval = stuff_yank(regname, p); @@ -995,6 +1024,60 @@ static int stuff_yank(int regname, char_u *p) static int execreg_lastc = NUL; +/// When executing a register as a series of ex-commands, if the +/// line-continuation character is used for a line, then join it with one or +/// more previous lines. Note that lines are processed backwards starting from +/// the last line in the register. +/// +/// @param lines list of lines in the register +/// @param idx index of the line starting with \ or "\. Join this line with all the immediate +/// predecessor lines that start with a \ and the first line that doesn't start +/// with a \. Lines that start with a comment "\ character are ignored. +/// @returns the concatenated line. The index of the line that should be +/// processed next is returned in idx. +static char_u *execreg_line_continuation(char_u **lines, size_t *idx) +{ + size_t i = *idx; + assert(i > 0); + const size_t cmd_end = i; + + garray_T ga; + ga_init(&ga, (int)sizeof(char_u), 400); + + char_u *p; + + // search backwards to find the first line of this command. + // Any line not starting with \ or "\ is the start of the + // command. + while (--i > 0) { + p = skipwhite(lines[i]); + if (*p != '\\' && (p[0] != '"' || p[1] != '\\' || p[2] != ' ')) { + break; + } + } + const size_t cmd_start = i; + + // join all the lines + ga_concat(&ga, (char *)lines[cmd_start]); + for (size_t j = cmd_start + 1; j <= cmd_end; j++) { + p = skipwhite(lines[j]); + if (*p == '\\') { + // Adjust the growsize to the current length to + // speed up concatenating many lines. + if (ga.ga_len > 400) { + ga_set_growsize(&ga, MIN(ga.ga_len, 8000)); + } + ga_concat(&ga, (char *)(p + 1)); + } + } + ga_append(&ga, NUL); + char_u *str = vim_strsave(ga.ga_data); + ga_clear(&ga); + + *idx = i; + return str; +} + /// Execute a yank register: copy it into the stuff buffer /// /// @param colon insert ':' before each line @@ -1083,7 +1166,21 @@ int do_execreg(int regname, int colon, int addcr, int silent) return FAIL; } } - escaped = vim_strsave_escape_csi(reg->y_array[i]); + + // Handle line-continuation for :@<register> + char_u *str = reg->y_array[i]; + bool free_str = false; + if (colon && i > 0) { + p = skipwhite(str); + if (*p == '\\' || (p[0] == '"' && p[1] == '\\' && p[2] == ' ')) { + str = execreg_line_continuation(reg->y_array, &i); + free_str = true; + } + } + escaped = vim_strsave_escape_ks(str); + if (free_str) { + xfree(str); + } retval = ins_typebuf(escaped, remap, 0, true, silent); xfree(escaped); if (retval == FAIL) { @@ -1125,7 +1222,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) @@ -1140,7 +1237,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; } @@ -1419,6 +1516,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); /* @@ -1471,20 +1573,20 @@ int op_delete(oparg_T *oap) yankreg_T *reg = NULL; int did_yank = false; if (oap->regname != 0) { - // yank without message - did_yank = op_yank(oap, false, true); - if (!did_yank) { - // op_yank failed, don't do anything + // check for read-only register + if (!valid_yank_reg(oap->regname, true)) { + beep_flush(); return OK; } + reg = get_yank_register(oap->regname, YREG_YANK); // yank into specif'd reg + op_yank_reg(oap, false, reg, is_append_register(oap->regname)); // yank without message + did_yank = true; } - /* - * Put deleted text into register 1 and shift number registers if the - * delete contains a line break, or when a regname has been specified. - */ - if (oap->regname != 0 || oap->motion_type == kMTLineWise - || oap->line_count > 1 || oap->use_reg_one) { + // Put deleted text into register 1 and shift number registers if the + // delete contains a line break, or when using a specific operator (Vi + // compatible) + if (oap->motion_type == kMTLineWise || oap->line_count > 1 || oap->use_reg_one) { shift_delete_registers(is_append_register(oap->regname)); reg = &y_regs[1]; op_yank_reg(oap, false, reg, false); @@ -1715,13 +1817,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; } @@ -1768,7 +1872,7 @@ static void replace_character(int c) /* * Replace a whole area with one character. */ -int op_replace(oparg_T *oap, int c) +static int op_replace(oparg_T *oap, int c) { int n, numc; int num_chars; @@ -1926,11 +2030,14 @@ 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 { @@ -1986,9 +2093,11 @@ 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; } @@ -2057,11 +2166,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", @@ -2162,7 +2271,8 @@ void op_insert(oparg_T *oap, long count1) { long ins_len, pre_textlen = 0; char_u *firstline, *ins_text; - colnr_T ind_pre = 0; + colnr_T ind_pre_col = 0, ind_post_col; + int ind_pre_vcol = 0, ind_post_vcol = 0; struct block_def bd; int i; pos_T t1; @@ -2179,24 +2289,28 @@ 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); // Get indent information - ind_pre = (colnr_T)getwhitecols_curline(); + ind_pre_col = (colnr_T)getwhitecols_curline(); + ind_pre_vcol = get_indent(); firstline = ml_get(oap->start.lnum) + bd.textcol; if (oap->op_type == OP_APPEND) { @@ -2238,6 +2352,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 @@ -2261,33 +2376,29 @@ void op_insert(oparg_T *oap, long count1) // if indent kicked in, the firstline might have changed // but only do that, if the indent actually increased - const colnr_T ind_post = (colnr_T)getwhitecols_curline(); - if (curbuf->b_op_start.col > ind_pre && ind_post > ind_pre) { - bd.textcol += ind_post - ind_pre; - bd.start_vcol += ind_post - ind_pre; + ind_post_col = (colnr_T)getwhitecols_curline(); + if (curbuf->b_op_start.col > ind_pre_col && ind_post_col > ind_pre_col) { + bd.textcol += ind_post_col - ind_pre_col; + ind_post_vcol = get_indent(); + bd.start_vcol += ind_post_vcol - ind_pre_vcol; did_indent = true; } // 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; @@ -2297,12 +2408,26 @@ void op_insert(oparg_T *oap, long count1) } } - /* - * Spaces and tabs in the indent may have changed to other spaces and - * tabs. Get the starting column again and correct the length. - * Don't do this when "$" used, end-of-line will have changed. - */ + // Spaces and tabs in the indent may have changed to other spaces and + // tabs. Get the starting column again and correct the length. + // Don't do this when "$" used, end-of-line will have changed. + // + // if indent was added and the inserted text was after the indent, + // correct the selection for the new indent. + if (did_indent && bd.textcol - ind_post_col > 0) { + oap->start.col += ind_post_col - ind_pre_col; + oap->start_vcol += ind_post_vcol - ind_pre_vcol; + oap->end.col += ind_post_col - ind_pre_col; + oap->end_vcol += ind_post_vcol - ind_pre_vcol; + } block_prep(oap, &bd2, oap->start.lnum, true); + if (did_indent && bd.textcol - ind_post_col > 0) { + // undo for where "oap" is used below + oap->start.col -= ind_post_col - ind_pre_col; + oap->start_vcol -= ind_post_vcol - ind_pre_vcol; + oap->end.col -= ind_post_col - ind_pre_col; + oap->end_vcol -= ind_post_vcol - ind_pre_vcol; + } if (!bd.is_MAX || bd2.textlen < bd.textlen) { if (oap->op_type == OP_APPEND) { pre_textlen += bd2.textlen - bd.textlen; @@ -2321,15 +2446,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 @@ -2511,12 +2648,12 @@ void free_register(yankreg_T *reg) /// Yanks the text between "oap->start" and "oap->end" into a yank register. /// If we are to append (uppercase register), we first yank into a new yank /// register and then concatenate the old and the new one. +/// Do not call this from a delete operation. Use op_yank_reg() instead. /// /// @param oap operator arguments /// @param message show message when more than `&report` lines are yanked. -/// @param deleting whether the function was called from a delete operation. /// @returns whether the operation register was writable. -bool op_yank(oparg_T *oap, bool message, int deleting) +bool op_yank(oparg_T *oap, bool message) FUNC_ATTR_NONNULL_ALL { // check for read-only register @@ -2530,11 +2667,8 @@ bool op_yank(oparg_T *oap, bool message, int deleting) yankreg_T *reg = get_yank_register(oap->regname, YREG_YANK); op_yank_reg(oap, message, reg, is_append_register(oap->regname)); - // op_delete will set_clipboard and do_autocmd - if (!deleting) { - set_clipboard(oap->regname, reg); - do_autocmd_textyankpost(oap, reg); - } + set_clipboard(oap->regname, reg); + do_autocmd_textyankpost(oap, reg); return true; } @@ -2702,9 +2836,7 @@ static void op_yank_reg(oparg_T *oap, bool message, yankreg_T *reg, bool append) curr->y_size = j; xfree(reg->y_array); } - if (curwin->w_p_rnu) { - redraw_later(curwin, SOME_VALID); // cursor moved to start - } + if (message) { // Display message about yank? if (yank_type == kMTCharWise && yanklines == 1) { yanklines = 0; @@ -2733,17 +2865,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. @@ -2768,7 +2898,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--; } @@ -2792,8 +2922,9 @@ static void do_autocmd_textyankpost(oparg_T *oap, yankreg_T *reg) recursive = true; + save_v_event_T save_v_event; // Set the v:event dictionary with information about the yank. - dict_T *dict = get_vim_var_dict(VV_EVENT); + dict_T *dict = get_v_event(&save_v_event); // The yanked text contents. list_T *const list = tv_list_alloc((ptrdiff_t)reg->y_size); @@ -2830,7 +2961,7 @@ static void do_autocmd_textyankpost(oparg_T *oap, yankreg_T *reg) textlock++; apply_autocmds(EVENT_TEXTYANKPOST, NULL, NULL, false, curbuf); textlock--; - tv_dict_clear(dict); + restore_v_event(dict, &save_v_event); recursive = false; } @@ -2872,6 +3003,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(); @@ -2942,7 +3076,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)) { @@ -3118,13 +3252,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 { @@ -3146,7 +3281,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); @@ -3160,9 +3295,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++; } @@ -3244,18 +3378,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); @@ -3269,9 +3413,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); @@ -3340,6 +3486,7 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) if (y_type == kMTCharWise && y_size == 1) { linenr_T end_lnum = 0; // init for gcc linenr_T start_lnum = lnum; + int first_byte_off = 0; if (VIsual_active) { end_lnum = curbuf->b_visual.vi_end.lnum; @@ -3359,10 +3506,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, @@ -3373,11 +3528,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++) { @@ -3386,6 +3541,10 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) } STRMOVE(ptr, oldp + col); ml_replace(lnum, newp, false); + + // compute the byte offset for the last character + first_byte_off = utf_head_off(newp, ptr - 1); + // Place cursor on last putted char. if (lnum == curwin->w_cursor.lnum) { // make sure curwin->w_virtcol is updated @@ -3395,22 +3554,30 @@ 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 curbuf->b_op_end = curwin->w_cursor; + curbuf->b_op_end.col -= first_byte_off; + // For "CTRL-O p" in Insert mode, put cursor after last char if (totlen && (restart_edit != 0 || (flags & PUT_CURSEND))) { curwin->w_cursor.col++; + } else { + 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++) { @@ -3427,6 +3594,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); @@ -3442,10 +3610,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; @@ -3495,6 +3664,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: @@ -3520,12 +3693,14 @@ error: curbuf->b_op_start.lnum, nr_lines, true); } - // put '] mark at last inserted character - curbuf->b_op_end.lnum = lnum; - // correct length for change in indent - col = (colnr_T)STRLEN(y_array[y_size - 1]) - lendiff; + // Put the '] mark on the first byte of the last inserted character. + // Correct the length for change in indent. + 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; + curbuf->b_op_end.col = col - 1 - utf_head_off(y_array[y_size - 1], + y_array[y_size - 1] + len - 1); } else { curbuf->b_op_end.col = 0; } @@ -3544,8 +3719,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 @@ -3564,6 +3743,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); } @@ -3583,14 +3766,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. @@ -3644,7 +3829,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; @@ -3657,11 +3842,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) { @@ -3688,88 +3873,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 != NUL && (n -= ptr2cells(p)) >= 0; p++) { // -V1019 + 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); } @@ -3908,7 +4092,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); @@ -4029,7 +4213,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; @@ -4093,7 +4277,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) { @@ -4148,7 +4332,7 @@ static int same_leader(linenr_T lnum, int leader1_len, char_u *leader1_flags, in /// Implementation of the format operator 'gq'. /// /// @param keep_cursor keep cursor on same text char -void op_format(oparg_T *oap, int keep_cursor) +static void op_format(oparg_T *oap, int keep_cursor) { long old_line_count = curbuf->b_ml.ml_line_count; @@ -4167,8 +4351,10 @@ 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). @@ -4190,8 +4376,10 @@ 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; @@ -4216,7 +4404,7 @@ void op_format(oparg_T *oap, int keep_cursor) /* * Implementation of the format operator 'gq' for when using 'formatexpr'. */ -void op_formatexpr(oparg_T *oap) +static void op_formatexpr(oparg_T *oap) { if (oap->is_VIsual) { // When there is no change: need to remove the Visual selection @@ -4278,7 +4466,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; @@ -4395,7 +4583,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; + } } /* @@ -4789,7 +4984,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; } @@ -5143,11 +5338,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: @@ -5578,7 +5775,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; @@ -5907,6 +6106,797 @@ void cursor_pos_info(dict_T *dict) } } +// Handle indent and format operators and visual mode ":". +static void op_colon(oparg_T *oap) +{ + stuffcharReadbuff(':'); + if (oap->is_VIsual) { + stuffReadbuff("'<,'>"); + } else { + // Make the range look nice, so it can be repeated. + if (oap->start.lnum == curwin->w_cursor.lnum) { + stuffcharReadbuff('.'); + } else { + stuffnumReadbuff((long)oap->start.lnum); + } + if (oap->end.lnum != oap->start.lnum) { + stuffcharReadbuff(','); + if (oap->end.lnum == curwin->w_cursor.lnum) { + stuffcharReadbuff('.'); + } else if (oap->end.lnum == curbuf->b_ml.ml_line_count) { + stuffcharReadbuff('$'); + } else if (oap->start.lnum == curwin->w_cursor.lnum) { + stuffReadbuff(".+"); + stuffnumReadbuff(oap->line_count - 1); + } else { + stuffnumReadbuff((long)oap->end.lnum); + } + } + } + if (oap->op_type != OP_COLON) { + stuffReadbuff("!"); + } + if (oap->op_type == OP_INDENT) { + stuffReadbuff((const char *)get_equalprg()); + stuffReadbuff("\n"); + } else if (oap->op_type == OP_FORMAT) { + if (*curbuf->b_p_fp != NUL) { + stuffReadbuff((const char *)curbuf->b_p_fp); + } else if (*p_fp != NUL) { + stuffReadbuff((const char *)p_fp); + } else { + stuffReadbuff("fmt"); + } + stuffReadbuff("\n']"); + } + + // do_cmdline() does the rest +} + +// Handle the "g@" operator: call 'operatorfunc'. +static void op_function(const oparg_T *oap) + FUNC_ATTR_NONNULL_ALL +{ + 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")); + } else { + // Set '[ and '] marks to text to be operated on. + curbuf->b_op_start = oap->start; + curbuf->b_op_end = oap->end; + if (oap->motion_type != kMTLineWise && !oap->inclusive) { + // Exclude the end position. + decl(&curbuf->b_op_end); + } + + typval_T argv[2]; + argv[0].v_type = VAR_STRING; + argv[1].v_type = VAR_UNKNOWN; + argv[0].vval.v_string = + (char_u *)(((const char *const[]) { + [kMTBlockWise] = "block", + [kMTLineWise] = "line", + [kMTCharWise] = "char", + })[oap->motion_type]); + + // Reset virtual_op so that 'virtualedit' can be changed in the + // function. + virtual_op = kNone; + + // Reset finish_op so that mode() returns the right value. + finish_op = false; + + (void)call_func_retnr(p_opfunc, 1, argv); + + 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; + } + } +} + +/// Calculate start/end virtual columns for operating in block mode. +/// +/// @param initial when true: adjust position for 'selectmode' +static void get_op_vcol(oparg_T *oap, colnr_T redo_VIsual_vcol, bool initial) +{ + colnr_T start; + colnr_T end; + + if (VIsual_mode != Ctrl_V + || (!initial && oap->end.col < curwin->w_width_inner)) { + return; + } + + oap->motion_type = kMTBlockWise; + + // prevent from moving onto a trail byte + mark_mb_adjustpos(curwin->w_buffer, &oap->end); + + getvvcol(curwin, &(oap->start), &oap->start_vcol, NULL, &oap->end_vcol); + if (!redo_VIsual_busy) { + getvvcol(curwin, &(oap->end), &start, NULL, &end); + + if (start < oap->start_vcol) { + oap->start_vcol = start; + } + if (end > oap->end_vcol) { + if (initial && *p_sel == 'e' + && start >= 1 + && start - 1 >= oap->end_vcol) { + oap->end_vcol = start - 1; + } else { + oap->end_vcol = end; + } + } + } + + // if '$' was used, get oap->end_vcol from longest line + if (curwin->w_curswant == MAXCOL) { + curwin->w_cursor.col = MAXCOL; + oap->end_vcol = 0; + for (curwin->w_cursor.lnum = oap->start.lnum; + curwin->w_cursor.lnum <= oap->end.lnum; curwin->w_cursor.lnum++) { + getvvcol(curwin, &curwin->w_cursor, NULL, NULL, &end); + if (end > oap->end_vcol) { + oap->end_vcol = end; + } + } + } else if (redo_VIsual_busy) { + oap->end_vcol = oap->start_vcol + redo_VIsual_vcol - 1; + } + + // Correct oap->end.col and oap->start.col to be the + // upper-left and lower-right corner of the block area. + // + // (Actually, this does convert column positions into character + // positions) + curwin->w_cursor.lnum = oap->end.lnum; + coladvance(oap->end_vcol); + oap->end = curwin->w_cursor; + + curwin->w_cursor = oap->start; + coladvance(oap->start_vcol); + oap->start = curwin->w_cursor; +} + +// Handle an operator after Visual mode or when the movement is finished. +// "gui_yank" is true when yanking text for the clipboard. +void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank) +{ + oparg_T *oap = cap->oap; + pos_T old_cursor; + bool empty_region_error; + int restart_edit_save; + int lbr_saved = curwin->w_p_lbr; + + + // The visual area is remembered for redo + static int redo_VIsual_mode = NUL; // 'v', 'V', or Ctrl-V + static linenr_T redo_VIsual_line_count; // number of lines + static colnr_T redo_VIsual_vcol; // number of cols or end column + static long redo_VIsual_count; // count for Visual operator + static int redo_VIsual_arg; // extra argument + bool include_line_break = false; + + old_cursor = curwin->w_cursor; + + // If an operation is pending, handle it... + if ((finish_op + || VIsual_active) + && oap->op_type != OP_NOP) { + // Yank can be redone when 'y' is in 'cpoptions', but not when yanking + // for the clipboard. + const bool redo_yank = vim_strchr(p_cpo, CPO_YANK) != NULL && !gui_yank; + + // Avoid a problem with unwanted linebreaks in block mode + if (curwin->w_p_lbr) { + curwin->w_valid &= ~VALID_VIRTCOL; + } + curwin->w_p_lbr = false; + oap->is_VIsual = VIsual_active; + if (oap->motion_force == 'V') { + oap->motion_type = kMTLineWise; + } else if (oap->motion_force == 'v') { + // If the motion was linewise, "inclusive" will not have been set. + // Use "exclusive" to be consistent. Makes "dvj" work nice. + if (oap->motion_type == kMTLineWise) { + oap->inclusive = false; + } else if (oap->motion_type == kMTCharWise) { + // If the motion already was charwise, toggle "inclusive" + oap->inclusive = !oap->inclusive; + } + oap->motion_type = kMTCharWise; + } else if (oap->motion_force == Ctrl_V) { + // Change line- or charwise motion into Visual block mode. + if (!VIsual_active) { + VIsual_active = true; + VIsual = oap->start; + } + VIsual_mode = Ctrl_V; + VIsual_select = false; + VIsual_reselect = false; + } + + // Only redo yank when 'y' flag is in 'cpoptions'. + // Never redo "zf" (define fold). + if ((redo_yank || oap->op_type != OP_YANK) + && ((!VIsual_active || oap->motion_force) + // Also redo Operator-pending Visual mode mappings. + || ((cap->cmdchar == ':' || cap->cmdchar == K_COMMAND) + && oap->op_type != OP_COLON)) + && cap->cmdchar != 'D' + && oap->op_type != OP_FOLD + && oap->op_type != OP_FOLDOPEN + && oap->op_type != OP_FOLDOPENREC + && oap->op_type != OP_FOLDCLOSE + && oap->op_type != OP_FOLDCLOSEREC + && oap->op_type != OP_FOLDDEL + && oap->op_type != OP_FOLDDELREC) { + prep_redo(oap->regname, cap->count0, + get_op_char(oap->op_type), get_extra_op_char(oap->op_type), + oap->motion_force, cap->cmdchar, cap->nchar); + if (cap->cmdchar == '/' || cap->cmdchar == '?') { // was a search + // If 'cpoptions' does not contain 'r', insert the search + // pattern to really repeat the same command. + if (vim_strchr(p_cpo, CPO_REDO) == NULL) { + AppendToRedobuffLit(cap->searchbuf, -1); + } + AppendToRedobuff(NL_STR); + } else if (cap->cmdchar == ':' || cap->cmdchar == K_COMMAND) { + // do_cmdline() has stored the first typed line in + // "repeat_cmdline". When several lines are typed repeating + // won't be possible. + if (repeat_cmdline == NULL) { + ResetRedobuff(); + } else { + AppendToRedobuffLit(repeat_cmdline, -1); + AppendToRedobuff(NL_STR); + XFREE_CLEAR(repeat_cmdline); + } + } + } + + if (redo_VIsual_busy) { + // Redo of an operation on a Visual area. Use the same size from + // redo_VIsual_line_count and redo_VIsual_vcol. + oap->start = curwin->w_cursor; + curwin->w_cursor.lnum += redo_VIsual_line_count - 1; + if (curwin->w_cursor.lnum > curbuf->b_ml.ml_line_count) { + curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count; + } + VIsual_mode = redo_VIsual_mode; + if (redo_VIsual_vcol == MAXCOL || VIsual_mode == 'v') { + if (VIsual_mode == 'v') { + if (redo_VIsual_line_count <= 1) { + validate_virtcol(); + curwin->w_curswant = + curwin->w_virtcol + redo_VIsual_vcol - 1; + } else { + curwin->w_curswant = redo_VIsual_vcol; + } + } else { + curwin->w_curswant = MAXCOL; + } + coladvance(curwin->w_curswant); + } + cap->count0 = redo_VIsual_count; + cap->count1 = (cap->count0 == 0 ? 1 : cap->count0); + } else if (VIsual_active) { + if (!gui_yank) { + // Save the current VIsual area for '< and '> marks, and "gv" + curbuf->b_visual.vi_start = VIsual; + curbuf->b_visual.vi_end = curwin->w_cursor; + curbuf->b_visual.vi_mode = VIsual_mode; + restore_visual_mode(); + curbuf->b_visual.vi_curswant = curwin->w_curswant; + curbuf->b_visual_mode_eval = VIsual_mode; + } + + // In Select mode, a linewise selection is operated upon like a + // charwise selection. + // Special case: gH<Del> deletes the last line. + if (VIsual_select && VIsual_mode == 'V' + && cap->oap->op_type != OP_DELETE) { + if (lt(VIsual, curwin->w_cursor)) { + VIsual.col = 0; + curwin->w_cursor.col = + (colnr_T)STRLEN(ml_get(curwin->w_cursor.lnum)); + } else { + curwin->w_cursor.col = 0; + VIsual.col = (colnr_T)STRLEN(ml_get(VIsual.lnum)); + } + VIsual_mode = 'v'; + } else if (VIsual_mode == 'v') { + // If 'selection' is "exclusive", backup one character for + // charwise selections. + include_line_break = + unadjust_for_sel(); + } + + oap->start = VIsual; + if (VIsual_mode == 'V') { + oap->start.col = 0; + oap->start.coladd = 0; + } + } + + // Set oap->start to the first position of the operated text, oap->end + // to the end of the operated text. w_cursor is equal to oap->start. + if (lt(oap->start, curwin->w_cursor)) { + // Include folded lines completely. + if (!VIsual_active) { + if (hasFolding(oap->start.lnum, &oap->start.lnum, NULL)) { + oap->start.col = 0; + } + if ((curwin->w_cursor.col > 0 + || oap->inclusive + || oap->motion_type == kMTLineWise) + && hasFolding(curwin->w_cursor.lnum, NULL, + &curwin->w_cursor.lnum)) { + curwin->w_cursor.col = (colnr_T)STRLEN(get_cursor_line_ptr()); + } + } + oap->end = curwin->w_cursor; + curwin->w_cursor = oap->start; + + // w_virtcol may have been updated; if the cursor goes back to its + // previous position w_virtcol becomes invalid and isn't updated + // automatically. + curwin->w_valid &= ~VALID_VIRTCOL; + } else { + // Include folded lines completely. + if (!VIsual_active && oap->motion_type == kMTLineWise) { + if (hasFolding(curwin->w_cursor.lnum, &curwin->w_cursor.lnum, + NULL)) { + curwin->w_cursor.col = 0; + } + if (hasFolding(oap->start.lnum, NULL, &oap->start.lnum)) { + oap->start.col = (colnr_T)STRLEN(ml_get(oap->start.lnum)); + } + } + oap->end = oap->start; + oap->start = curwin->w_cursor; + } + + // Just in case lines were deleted that make the position invalid. + check_pos(curwin->w_buffer, &oap->end); + oap->line_count = oap->end.lnum - oap->start.lnum + 1; + + // Set "virtual_op" before resetting VIsual_active. + virtual_op = virtual_active(); + + if (VIsual_active || redo_VIsual_busy) { + get_op_vcol(oap, redo_VIsual_vcol, true); + + if (!redo_VIsual_busy && !gui_yank) { + // Prepare to reselect and redo Visual: this is based on the + // size of the Visual text + resel_VIsual_mode = VIsual_mode; + if (curwin->w_curswant == MAXCOL) { + resel_VIsual_vcol = MAXCOL; + } else { + if (VIsual_mode != Ctrl_V) { + getvvcol(curwin, &(oap->end), + NULL, NULL, &oap->end_vcol); + } + if (VIsual_mode == Ctrl_V || oap->line_count <= 1) { + if (VIsual_mode != Ctrl_V) { + getvvcol(curwin, &(oap->start), + &oap->start_vcol, NULL, NULL); + } + resel_VIsual_vcol = oap->end_vcol - oap->start_vcol + 1; + } else { + resel_VIsual_vcol = oap->end_vcol; + } + } + resel_VIsual_line_count = oap->line_count; + } + + // can't redo yank (unless 'y' is in 'cpoptions') and ":" + if ((redo_yank || oap->op_type != OP_YANK) + && oap->op_type != OP_COLON + && oap->op_type != OP_FOLD + && oap->op_type != OP_FOLDOPEN + && oap->op_type != OP_FOLDOPENREC + && oap->op_type != OP_FOLDCLOSE + && oap->op_type != OP_FOLDCLOSEREC + && oap->op_type != OP_FOLDDEL + && oap->op_type != OP_FOLDDELREC + && oap->motion_force == NUL) { + // Prepare for redoing. Only use the nchar field for "r", + // otherwise it might be the second char of the operator. + if (cap->cmdchar == 'g' && (cap->nchar == 'n' + || cap->nchar == 'N')) { + prep_redo(oap->regname, cap->count0, + get_op_char(oap->op_type), get_extra_op_char(oap->op_type), + oap->motion_force, cap->cmdchar, cap->nchar); + } else if (cap->cmdchar != ':' && cap->cmdchar != K_COMMAND) { + int nchar = oap->op_type == OP_REPLACE ? cap->nchar : NUL; + + // reverse what nv_replace() did + if (nchar == REPLACE_CR_NCHAR) { + nchar = CAR; + } else if (nchar == REPLACE_NL_NCHAR) { + nchar = NL; + } + prep_redo(oap->regname, 0L, NUL, 'v', get_op_char(oap->op_type), + get_extra_op_char(oap->op_type), nchar); + } + if (!redo_VIsual_busy) { + redo_VIsual_mode = resel_VIsual_mode; + redo_VIsual_vcol = resel_VIsual_vcol; + redo_VIsual_line_count = resel_VIsual_line_count; + redo_VIsual_count = cap->count0; + redo_VIsual_arg = cap->arg; + } + } + + // oap->inclusive defaults to true. + // If oap->end is on a NUL (empty line) oap->inclusive becomes + // false. This makes "d}P" and "v}dP" work the same. + if (oap->motion_force == NUL || oap->motion_type == kMTLineWise) { + oap->inclusive = true; + } + if (VIsual_mode == 'V') { + oap->motion_type = kMTLineWise; + } else if (VIsual_mode == 'v') { + oap->motion_type = kMTCharWise; + if (*ml_get_pos(&(oap->end)) == NUL + && (include_line_break || !virtual_op)) { + oap->inclusive = false; + // Try to include the newline, unless it's an operator + // that works on lines only. + if (*p_sel != 'o' + && !op_on_lines(oap->op_type) + && oap->end.lnum < curbuf->b_ml.ml_line_count) { + oap->end.lnum++; + oap->end.col = 0; + oap->end.coladd = 0; + oap->line_count++; + } + } + } + + redo_VIsual_busy = false; + + // Switch Visual off now, so screen updating does + // not show inverted text when the screen is redrawn. + // With OP_YANK and sometimes with OP_COLON and OP_FILTER there is + // no screen redraw, so it is done here to remove the inverted + // part. + if (!gui_yank) { + VIsual_active = false; + setmouse(); + mouse_dragging = 0; + may_clear_cmdline(); + if ((oap->op_type == OP_YANK + || oap->op_type == OP_COLON + || oap->op_type == OP_FUNCTION + || oap->op_type == OP_FILTER) + && oap->motion_force == NUL) { + // Make sure redrawing is correct. + curwin->w_p_lbr = lbr_saved; + redraw_curbuf_later(INVERTED); + } + } + } + + // Include the trailing byte of a multi-byte char. + if (oap->inclusive) { + const int l = utfc_ptr2len(ml_get_pos(&oap->end)); + if (l > 1) { + oap->end.col += l - 1; + } + } + curwin->w_set_curswant = true; + + // oap->empty is set when start and end are the same. The inclusive + // flag affects this too, unless yanking and the end is on a NUL. + oap->empty = (oap->motion_type != kMTLineWise + && (!oap->inclusive + || (oap->op_type == OP_YANK + && gchar_pos(&oap->end) == NUL)) + && equalpos(oap->start, oap->end) + && !(virtual_op && oap->start.coladd != oap->end.coladd)); + // For delete, change and yank, it's an error to operate on an + // empty region, when 'E' included in 'cpoptions' (Vi compatible). + empty_region_error = (oap->empty + && vim_strchr(p_cpo, CPO_EMPTYREGION) != NULL); + + // Force a redraw when operating on an empty Visual region, when + // 'modifiable is off or creating a fold. + if (oap->is_VIsual && (oap->empty || !MODIFIABLE(curbuf) + || oap->op_type == OP_FOLD)) { + curwin->w_p_lbr = lbr_saved; + redraw_curbuf_later(INVERTED); + } + + // If the end of an operator is in column one while oap->motion_type + // is kMTCharWise and oap->inclusive is false, we put op_end after the last + // character in the previous line. If op_start is on or before the + // first non-blank in the line, the operator becomes linewise + // (strange, but that's the way vi does it). + if (oap->motion_type == kMTCharWise + && oap->inclusive == false + && !(cap->retval & CA_NO_ADJ_OP_END) + && oap->end.col == 0 + && (!oap->is_VIsual || *p_sel == 'o') + && oap->line_count > 1) { + oap->end_adjusted = true; // remember that we did this + oap->line_count--; + oap->end.lnum--; + if (inindent(0)) { + oap->motion_type = kMTLineWise; + } else { + oap->end.col = (colnr_T)STRLEN(ml_get(oap->end.lnum)); + if (oap->end.col) { + oap->end.col--; + oap->inclusive = true; + } + } + } else { + oap->end_adjusted = false; + } + + switch (oap->op_type) { + case OP_LSHIFT: + case OP_RSHIFT: + op_shift(oap, true, + oap->is_VIsual ? (int)cap->count1 : + 1); + auto_format(false, true); + break; + + case OP_JOIN_NS: + case OP_JOIN: + if (oap->line_count < 2) { + oap->line_count = 2; + } + if (curwin->w_cursor.lnum + oap->line_count - 1 > + curbuf->b_ml.ml_line_count) { + beep_flush(); + } else { + do_join((size_t)oap->line_count, oap->op_type == OP_JOIN, + true, true, true); + auto_format(false, true); + } + break; + + case OP_DELETE: + VIsual_reselect = false; // don't reselect now + if (empty_region_error) { + vim_beep(BO_OPER); + CancelRedo(); + } else { + (void)op_delete(oap); + // save cursor line for undo if it wasn't saved yet + if (oap->motion_type == kMTLineWise + && has_format_option(FO_AUTO) + && u_save_cursor() == OK) { + auto_format(false, true); + } + } + break; + + case OP_YANK: + if (empty_region_error) { + if (!gui_yank) { + vim_beep(BO_OPER); + CancelRedo(); + } + } else { + curwin->w_p_lbr = lbr_saved; + oap->excl_tr_ws = cap->cmdchar == 'z'; + (void)op_yank(oap, !gui_yank); + } + check_cursor_col(); + break; + + case OP_CHANGE: + VIsual_reselect = false; // don't reselect now + if (empty_region_error) { + vim_beep(BO_OPER); + CancelRedo(); + } else { + // This is a new edit command, not a restart. Need to + // remember it to make 'insertmode' work with mappings for + // Visual mode. But do this only once and not when typed and + // 'insertmode' isn't set. + if (p_im || !KeyTyped) { + restart_edit_save = restart_edit; + } else { + restart_edit_save = 0; + } + restart_edit = 0; + + // Restore linebreak, so that when the user edits it looks as before. + curwin->w_p_lbr = lbr_saved; + + // Reset finish_op now, don't want it set inside edit(). + finish_op = false; + if (op_change(oap)) { // will call edit() + cap->retval |= CA_COMMAND_BUSY; + } + if (restart_edit == 0) { + restart_edit = restart_edit_save; + } + } + break; + + case OP_FILTER: + if (vim_strchr(p_cpo, CPO_FILTER) != NULL) { + AppendToRedobuff("!\r"); // Use any last used !cmd. + } else { + bangredo = true; // do_bang() will put cmd in redo buffer. + } + FALLTHROUGH; + + case OP_INDENT: + case OP_COLON: + + // If 'equalprg' is empty, do the indenting internally. + if (oap->op_type == OP_INDENT && *get_equalprg() == NUL) { + if (curbuf->b_p_lisp) { + op_reindent(oap, get_lisp_indent); + break; + } + op_reindent(oap, + *curbuf->b_p_inde != NUL ? get_expr_indent : + get_c_indent); + break; + } + + op_colon(oap); + break; + + case OP_TILDE: + case OP_UPPER: + case OP_LOWER: + case OP_ROT13: + if (empty_region_error) { + vim_beep(BO_OPER); + CancelRedo(); + } else { + op_tilde(oap); + } + check_cursor_col(); + break; + + case OP_FORMAT: + if (*curbuf->b_p_fex != NUL) { + op_formatexpr(oap); // use expression + } else { + if (*p_fp != NUL || *curbuf->b_p_fp != NUL) { + op_colon(oap); // use external command + } else { + op_format(oap, false); // use internal function + } + } + break; + + case OP_FORMAT2: + op_format(oap, true); // use internal function + break; + + case OP_FUNCTION: + // Restore linebreak, so that when the user edits it looks as + // before. + curwin->w_p_lbr = lbr_saved; + op_function(oap); // call 'operatorfunc' + break; + + case OP_INSERT: + case OP_APPEND: + VIsual_reselect = false; // don't reselect now + if (empty_region_error) { + vim_beep(BO_OPER); + CancelRedo(); + } else { + // This is a new edit command, not a restart. Need to + // remember it to make 'insertmode' work with mappings for + // Visual mode. But do this only once. + restart_edit_save = restart_edit; + restart_edit = 0; + + // Restore linebreak, so that when the user edits it looks as before. + curwin->w_p_lbr = lbr_saved; + + op_insert(oap, cap->count1); + + // Reset linebreak, so that formatting works correctly. + curwin->w_p_lbr = false; + + // TODO(brammool): when inserting in several lines, should format all + // the lines. + auto_format(false, true); + + if (restart_edit == 0) { + restart_edit = restart_edit_save; + } else { + cap->retval |= CA_COMMAND_BUSY; + } + } + break; + + case OP_REPLACE: + VIsual_reselect = false; // don't reselect now + if (empty_region_error) { + vim_beep(BO_OPER); + CancelRedo(); + } else { + // Restore linebreak, so that when the user edits it looks as before. + curwin->w_p_lbr = lbr_saved; + + op_replace(oap, cap->nchar); + } + break; + + case OP_FOLD: + VIsual_reselect = false; // don't reselect now + foldCreate(curwin, oap->start, oap->end); + break; + + case OP_FOLDOPEN: + case OP_FOLDOPENREC: + case OP_FOLDCLOSE: + case OP_FOLDCLOSEREC: + VIsual_reselect = false; // don't reselect now + opFoldRange(oap->start, oap->end, + oap->op_type == OP_FOLDOPEN + || oap->op_type == OP_FOLDOPENREC, + oap->op_type == OP_FOLDOPENREC + || oap->op_type == OP_FOLDCLOSEREC, + oap->is_VIsual); + break; + + case OP_FOLDDEL: + case OP_FOLDDELREC: + VIsual_reselect = false; // don't reselect now + deleteFold(curwin, oap->start.lnum, oap->end.lnum, + oap->op_type == OP_FOLDDELREC, oap->is_VIsual); + break; + + case OP_NR_ADD: + case OP_NR_SUB: + if (empty_region_error) { + vim_beep(BO_OPER); + CancelRedo(); + } else { + VIsual_active = true; + curwin->w_p_lbr = lbr_saved; + op_addsub(oap, cap->count1, redo_VIsual_arg); + VIsual_active = false; + } + check_cursor_col(); + break; + default: + clearopbeep(oap); + } + virtual_op = kNone; + if (!gui_yank) { + // if 'sol' not set, go back to old column for some commands + if (!p_sol && oap->motion_type == kMTLineWise && !oap->end_adjusted + && (oap->op_type == OP_LSHIFT || oap->op_type == OP_RSHIFT + || oap->op_type == OP_DELETE)) { + curwin->w_p_lbr = false; + coladvance(curwin->w_curswant = old_col); + } + } else { + curwin->w_cursor = old_cursor; + } + clearop(oap); + motion_force = NUL; + } + curwin->w_p_lbr = lbr_saved; +} + /// Check if the default register (used in an unnamed paste) should be a /// clipboard register. This happens when `clipboard=unnamed[plus]` is set /// and a provider is available. @@ -6388,7 +7378,6 @@ const yankreg_T *op_reg_get(const char name) /// /// @return true on success, false on failure. bool op_reg_set_previous(const char name) - FUNC_ATTR_WARN_UNUSED_RESULT { int i = op_reg_index(name); if (i == -1) { diff --git a/src/nvim/option.c b/src/nvim/option.c index 44b7d98e88..f6037fc20a 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -47,6 +47,7 @@ #include "nvim/getchar.h" #include "nvim/hardcopy.h" #include "nvim/highlight.h" +#include "nvim/highlight_group.h" #include "nvim/indent_c.h" #include "nvim/keymap.h" #include "nvim/macros.h" @@ -55,7 +56,6 @@ #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/message.h" -#include "nvim/misc1.h" #include "nvim/mouse.h" #include "nvim/move.h" #include "nvim/normal.h" @@ -133,6 +133,7 @@ static int p_cin; static char_u *p_cink; static char_u *p_cino; static char_u *p_cinw; +static char_u *p_cinsd; static char_u *p_com; static char_u *p_cms; static char_u *p_cpt; @@ -262,6 +263,7 @@ typedef struct vimoption { #define HIGHLIGHT_INIT \ "8:SpecialKey,~:EndOfBuffer,z:TermCursor,Z:TermCursorNC,@:NonText,d:Directory,e:ErrorMsg," \ "i:IncSearch,l:Search,m:MoreMsg,M:ModeMsg,n:LineNr,a:LineNrAbove,b:LineNrBelow,N:CursorLineNr," \ + "G:CursorLineSign,O:CursorLineFold" \ "r:Question,s:StatusLine,S:StatusLineNC,c:VertSplit,t:Title,v:Visual,V:VisualNOS,w:WarningMsg," \ "W:WildMenu,f:Folded,F:FoldColumn,A:DiffAdd,C:DiffChange,D:DiffDelete,T:DiffText,>:SignColumn," \ "-:Conceal,B:SpellBad,P:SpellCap,R:SpellRare,L:SpellLocal,+:Pmenu,=:PmenuSel,x:PmenuSbar," \ @@ -281,7 +283,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 +933,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 @@ -1301,7 +1318,11 @@ int do_set(char_u *arg, int opt_flags) char_u *oldval = NULL; // previous value if *varp char_u *newval; char_u *origval = NULL; + char_u *origval_l = NULL; + char_u *origval_g = NULL; char *saved_origval = NULL; + char *saved_origval_l = NULL; + char *saved_origval_g = NULL; char *saved_newval = NULL; unsigned newlen; int comma; @@ -1319,10 +1340,21 @@ int do_set(char_u *arg, int opt_flags) // new value is valid. oldval = *(char_u **)varp; + if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0) { + origval_l = *(char_u **)get_varp_scope(&(options[opt_idx]), OPT_LOCAL); + origval_g = *(char_u **)get_varp_scope(&(options[opt_idx]), OPT_GLOBAL); + + // A global-local string option might have an empty + // option as value to indicate that the global + // value should be used. + if (((int)options[opt_idx].indir & PV_BOTH) && origval_l == empty_option) { + origval_l = origval_g; + } + } + // When setting the local value of a global // option, the old value may be the global value. - if (((int)options[opt_idx].indir & PV_BOTH) && (opt_flags - & OPT_LOCAL)) { + if (((int)options[opt_idx].indir & PV_BOTH) && (opt_flags & OPT_LOCAL)) { origval = *(char_u **)get_varp(&options[opt_idx]); } else { origval = oldval; @@ -1330,7 +1362,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 @@ -1388,6 +1420,12 @@ int do_set(char_u *arg, int opt_flags) if (origval == oldval) { origval = *(char_u **)varp; } + if (origval_l == oldval) { + origval_l = *(char_u **)varp; + } + if (origval_g == oldval) { + origval_g = *(char_u **)varp; + } oldval = *(char_u **)varp; } /* @@ -1596,6 +1634,8 @@ int do_set(char_u *arg, int opt_flags) // origval may be freed by // did_set_string_option(), make a copy. saved_origval = (origval != NULL) ? xstrdup((char *)origval) : 0; + saved_origval_l = (origval_l != NULL) ? xstrdup((char *)origval_l) : 0; + saved_origval_g = (origval_g != NULL) ? xstrdup((char *)origval_g) : 0; // newval (and varp) may become invalid if the // buffer is closed by autocommands. @@ -1630,8 +1670,8 @@ int do_set(char_u *arg, int opt_flags) if (errmsg == NULL) { if (!starting) { - trigger_optionsset_string(opt_idx, opt_flags, saved_origval, - saved_newval); + trigger_optionsset_string(opt_idx, opt_flags, saved_origval, saved_origval_l, + saved_origval_g, saved_newval); } if (options[opt_idx].flags & P_UI_OPTION) { ui_call_option_set(cstr_as_string(options[opt_idx].fullname), @@ -1639,6 +1679,8 @@ int do_set(char_u *arg, int opt_flags) } } xfree(saved_origval); + xfree(saved_origval_l); + xfree(saved_origval_g); xfree(saved_newval); // If error detected, print the error message. @@ -1741,7 +1783,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; } @@ -1944,10 +1986,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. @@ -1968,9 +2009,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). @@ -2020,6 +2061,7 @@ void check_buf_options(buf_T *buf) parse_cino(buf); check_string_option(&buf->b_p_ft); check_string_option(&buf->b_p_cinw); + check_string_option(&buf->b_p_cinsd); check_string_option(&buf->b_p_cpt); check_string_option(&buf->b_p_cfu); check_string_option(&buf->b_p_ofu); @@ -2233,13 +2275,23 @@ static char *set_string_option(const int opt_idx, const char *const value, const ? OPT_GLOBAL : OPT_LOCAL) : opt_flags)); char *const oldval = *varp; + char *oldval_l = NULL; + char *oldval_g = NULL; + + if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0) { + oldval_l = *(char **)get_varp_scope(&(options[opt_idx]), OPT_LOCAL); + oldval_g = *(char **)get_varp_scope(&(options[opt_idx]), OPT_GLOBAL); + } + *varp = s; char *const saved_oldval = xstrdup(oldval); + 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) { @@ -2249,7 +2301,8 @@ static char *set_string_option(const int opt_idx, const char *const value, const // call autocommand after handling side effects if (r == NULL) { if (!starting) { - trigger_optionsset_string(opt_idx, opt_flags, saved_oldval, saved_newval); + trigger_optionsset_string(opt_idx, opt_flags, saved_oldval, saved_oldval_l, saved_oldval_g, + saved_newval); } if (options[opt_idx].flags & P_UI_OPTION) { ui_call_option_set(cstr_as_string(options[opt_idx].fullname), @@ -2257,6 +2310,8 @@ static char *set_string_option(const int opt_idx, const char *const value, const } } xfree(saved_oldval); + xfree(saved_oldval_l); + xfree(saved_oldval_g); xfree(saved_newval); return r; @@ -2650,24 +2705,38 @@ ambw_end: } s = skip_to_option_part(s); } - } else if (varp == &p_lcs) { // 'listchars' + } else if (varp == &p_lcs) { // global 'listchars' errmsg = set_chars_option(curwin, varp, false); - if (!errmsg) { + if (errmsg == NULL) { + // The current window is set to use the global 'listchars' value. + // So clear the window-local value. + if (!(opt_flags & OPT_GLOBAL)) { + clear_string_option(&curwin->w_p_lcs); + } FOR_ALL_TAB_WINDOWS(tp, wp) { - set_chars_option(wp, &wp->w_p_lcs, true); + // If no error was returned above, we don't expect an error + // here, so ignore the return value. + (void)set_chars_option(wp, &wp->w_p_lcs, true); } + redraw_all_later(NOT_VALID); } - redraw_all_later(NOT_VALID); } else if (varp == &curwin->w_p_lcs) { // local 'listchars' errmsg = set_chars_option(curwin, varp, true); - } else if (varp == &p_fcs) { // 'fillchars' + } else if (varp == &p_fcs) { // global 'fillchars' errmsg = set_chars_option(curwin, varp, false); - if (!errmsg) { + if (errmsg == NULL) { + // The current window is set to use the global 'fillchars' value. + // So clear the window-local value. + if (!(opt_flags & OPT_GLOBAL)) { + clear_string_option(&curwin->w_p_fcs); + } FOR_ALL_TAB_WINDOWS(tp, wp) { - set_chars_option(wp, &wp->w_p_fcs, true); + // If no error was returned above, we don't expect an error + // here, so ignore the return value. + (void)set_chars_option(wp, &wp->w_p_fcs, true); } + redraw_all_later(NOT_VALID); } - redraw_all_later(NOT_VALID); } else if (varp == &curwin->w_p_fcs) { // local 'fillchars' errmsg = set_chars_option(curwin, varp, true); } else if (varp == &p_cedit) { // 'cedit' @@ -2710,7 +2779,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; @@ -2844,7 +2913,7 @@ ambw_end: || check_opt_strings(curbuf->b_p_bt, p_buftype_values, false) != OK) { errmsg = e_invarg; } else { - if (curwin->w_status_height) { + if (curwin->w_status_height || global_stl_height()) { curwin->w_redr_status = true; redraw_later(curwin, VALID); } @@ -2901,7 +2970,7 @@ ambw_end: } } else { if (errbuf != NULL) { - vim_snprintf((char *)errbuf, errbuflen, + vim_snprintf(errbuf, errbuflen, _("E535: Illegal character after <%c>"), *--s); errmsg = errbuf; @@ -2939,7 +3008,7 @@ ambw_end: } } else if (varp == &curwin->w_p_fdc || varp == &curwin->w_allbuf_opt.wo_fdc) { // 'foldcolumn' - if (check_opt_strings(*varp, p_fdc_values, false) != OK) { + if (**varp == NUL || check_opt_strings(*varp, p_fdc_values, false) != OK) { errmsg = e_invarg; } } else if (varp == &p_pt) { @@ -3032,14 +3101,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) { @@ -3098,10 +3180,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)) { @@ -3126,10 +3205,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)) { @@ -3318,6 +3394,9 @@ static int int_cmp(const void *a, const void *b) /// @return OK when the value is valid, FAIL otherwise int check_signcolumn(char_u *val) { + if (*val == NUL) { + return FAIL; + } // check for basic match if (check_opt_strings(val, p_scl_values, false) == OK) { return OK; @@ -3477,16 +3556,22 @@ static char *set_chars_option(win_T *wp, char_u **varp, bool set) struct chars_tab *tab; struct chars_tab fcs_tab[] = { - { &wp->w_p_fcs_chars.stl, "stl", ' ' }, - { &wp->w_p_fcs_chars.stlnc, "stlnc", ' ' }, - { &wp->w_p_fcs_chars.vert, "vert", 9474 }, // │ - { &wp->w_p_fcs_chars.fold, "fold", 183 }, // · - { &wp->w_p_fcs_chars.foldopen, "foldopen", '-' }, - { &wp->w_p_fcs_chars.foldclosed, "foldclose", '+' }, - { &wp->w_p_fcs_chars.foldsep, "foldsep", 9474 }, // │ - { &wp->w_p_fcs_chars.diff, "diff", '-' }, - { &wp->w_p_fcs_chars.msgsep, "msgsep", ' ' }, - { &wp->w_p_fcs_chars.eob, "eob", '~' }, + { &wp->w_p_fcs_chars.stl, "stl", ' ' }, + { &wp->w_p_fcs_chars.stlnc, "stlnc", ' ' }, + { &wp->w_p_fcs_chars.horiz, "horiz", 9472 }, // ─ + { &wp->w_p_fcs_chars.horizup, "horizup", 9524 }, // ┴ + { &wp->w_p_fcs_chars.horizdown, "horizdown", 9516 }, // ┬ + { &wp->w_p_fcs_chars.vert, "vert", 9474 }, // │ + { &wp->w_p_fcs_chars.vertleft, "vertleft", 9508 }, // ┤ + { &wp->w_p_fcs_chars.vertright, "vertright", 9500 }, // ├ + { &wp->w_p_fcs_chars.verthoriz, "verthoriz", 9532 }, // ┼ + { &wp->w_p_fcs_chars.fold, "fold", 183 }, // · + { &wp->w_p_fcs_chars.foldopen, "foldopen", '-' }, + { &wp->w_p_fcs_chars.foldclosed, "foldclose", '+' }, + { &wp->w_p_fcs_chars.foldsep, "foldsep", 9474 }, // │ + { &wp->w_p_fcs_chars.diff, "diff", '-' }, + { &wp->w_p_fcs_chars.msgsep, "msgsep", ' ' }, + { &wp->w_p_fcs_chars.eob, "eob", '~' }, }; struct chars_tab lcs_tab[] = { { &wp->w_p_lcs_chars.eol, "eol", NUL }, @@ -3513,15 +3598,17 @@ static char *set_chars_option(win_T *wp, char_u **varp, bool set) varp = &p_fcs; } if (*p_ambw == 'd') { - // XXX: If ambiwidth=double then "|" and "·" take 2 columns, which is - // forbidden (TUI limitation?). Set old defaults. - fcs_tab[2].def = '|'; - fcs_tab[6].def = '|'; - fcs_tab[3].def = '-'; - } else { - fcs_tab[2].def = 9474; // │ - fcs_tab[6].def = 9474; // │ - fcs_tab[3].def = 183; // · + // XXX: If ambiwidth=double then some characters take 2 columns, + // which is forbidden (TUI limitation?). Set old defaults. + fcs_tab[2].def = '-'; + fcs_tab[3].def = '-'; + fcs_tab[4].def = '-'; + fcs_tab[5].def = '|'; + fcs_tab[6].def = '|'; + fcs_tab[7].def = '|'; + fcs_tab[8].def = '+'; + fcs_tab[9].def = '-'; + fcs_tab[12].def = '|'; } } @@ -3558,7 +3645,7 @@ static char *set_chars_option(win_T *wp, char_u **varp, bool set) c2 = c3 = 0; s = p + len + 1; c1 = get_encoded_char_adv(&s); - if (c1 == 0 || utf_char2cells(c1) > 1) { + if (c1 == 0 || char2cells(c1) > 1) { return e_invarg; } if (tab[i].cp == &wp->w_p_lcs_chars.tab2) { @@ -3566,12 +3653,12 @@ static char *set_chars_option(win_T *wp, char_u **varp, bool set) return e_invarg; } c2 = get_encoded_char_adv(&s); - if (c2 == 0 || utf_char2cells(c2) > 1) { + if (c2 == 0 || char2cells(c2) > 1) { return e_invarg; } if (!(*s == ',' || *s == NUL)) { c3 = get_encoded_char_adv(&s); - if (c3 == 0 || utf_char2cells(c3) > 1) { + if (c3 == 0 || char2cells(c3) > 1) { return e_invarg; } } @@ -3605,7 +3692,7 @@ static char *set_chars_option(win_T *wp, char_u **varp, bool set) multispace_len = 0; while (*s != NUL && *s != ',') { c1 = get_encoded_char_adv(&s); - if (c1 == 0 || utf_char2cells(c1) > 1) { + if (c1 == 0 || char2cells(c1) > 1) { return e_invarg; } multispace_len++; @@ -3769,13 +3856,13 @@ static bool parse_winhl_opt(win_T *wp) size_t nlen = (size_t)(colon-p); char *hi = colon+1; char *commap = xstrchrnul(hi, ','); - int len = (int)(commap-hi); + size_t len = (size_t)(commap-hi); int hl_id = len ? syn_check_group(hi, len) : -1; if (strncmp("Normal", p, nlen) == 0) { w_hl_id_normal = hl_id; } else { - for (hlf = 0; hlf < (int)HLF_COUNT; hlf++) { + for (hlf = 0; hlf < HLF_COUNT; hlf++) { if (strlen(hlf_names[hlf]) == nlen && strncmp(hlf_names[hlf], p, nlen) == 0) { w_hl_ids[hlf] = hl_id; @@ -3802,6 +3889,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, @@ -3837,6 +3925,7 @@ static char *set_bool_option(const int opt_idx, char_u *const varp, const int va const int opt_flags) { int old_value = *(int *)varp; + int old_global_value = 0; // Disallow changing some options from secure mode if ((secure || sandbox != 0) @@ -3844,6 +3933,13 @@ static char *set_bool_option(const int opt_idx, char_u *const varp, const int va return (char *)e_secure; } + // Save the global value before changing anything. This is needed as for + // 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 = *(int *)get_varp_scope(&(options[opt_idx]), OPT_GLOBAL); + } + *(int *)varp = value; // set the new value // Remember where the option was set. set_option_sctx_idx(opt_idx, opt_flags, current_sctx); @@ -3868,11 +3964,8 @@ static char *set_bool_option(const int opt_idx, char_u *const varp, const int va } else if ((int *)varp == &p_lnr) { // 'langnoremap' -> !'langremap' p_lrm = !p_lnr; - } else if ((int *)varp == &curwin->w_p_cul && !value && old_value) { - // 'cursorline' - reset_cursorline(); - // 'undofile' } else if ((int *)varp == &curbuf->b_p_udf || (int *)varp == &p_udf) { + // 'undofile' // Only take action when the option was set. When reset we do not // delete the undo file, the option may be set again without making // any changes in between. @@ -4069,7 +4162,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'"); @@ -4120,20 +4213,35 @@ static char *set_bool_option(const int opt_idx, char_u *const varp, const int va // Don't do this while starting up or recursively. if (!starting && *get_vim_var_str(VV_OPTION_TYPE) == NUL) { char buf_old[2]; + char buf_old_global[2]; char buf_new[2]; char buf_type[7]; - vim_snprintf(buf_old, ARRAY_SIZE(buf_old), "%d", - old_value ? true: false); - vim_snprintf(buf_new, ARRAY_SIZE(buf_new), "%d", - value ? true: false); + vim_snprintf(buf_old, ARRAY_SIZE(buf_old), "%d", old_value ? true : false); + vim_snprintf(buf_old_global, ARRAY_SIZE(buf_old_global), "%d", old_global_value ? true : false); + vim_snprintf(buf_new, ARRAY_SIZE(buf_new), "%d", value ? true : false); vim_snprintf(buf_type, ARRAY_SIZE(buf_type), "%s", (opt_flags & OPT_LOCAL) ? "local" : "global"); set_vim_var_string(VV_OPTION_NEW, buf_new, -1); set_vim_var_string(VV_OPTION_OLD, buf_old, -1); set_vim_var_string(VV_OPTION_TYPE, buf_type, -1); - apply_autocmds(EVENT_OPTIONSET, - (char_u *)options[opt_idx].fullname, - NULL, false, NULL); + if (opt_flags & OPT_LOCAL) { + set_vim_var_string(VV_OPTION_COMMAND, "setlocal", -1); + set_vim_var_string(VV_OPTION_OLDLOCAL, buf_old, -1); + } + if (opt_flags & OPT_GLOBAL) { + set_vim_var_string(VV_OPTION_COMMAND, "setglobal", -1); + set_vim_var_string(VV_OPTION_OLDGLOBAL, buf_old, -1); + } + if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0) { + set_vim_var_string(VV_OPTION_COMMAND, "set", -1); + set_vim_var_string(VV_OPTION_OLDLOCAL, buf_old, -1); + set_vim_var_string(VV_OPTION_OLDGLOBAL, buf_old_global, -1); + } + if (opt_flags & OPT_MODELINE) { + set_vim_var_string(VV_OPTION_COMMAND, "modeline", -1); + set_vim_var_string(VV_OPTION_OLDLOCAL, buf_old, -1); + } + apply_autocmds(EVENT_OPTIONSET, (char_u *)options[opt_idx].fullname, NULL, false, NULL); reset_v_option_vars(); } @@ -4167,7 +4275,8 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, char *errbuf, { char *errmsg = NULL; long old_value = *(long *)varp; - long old_Rows = Rows; // remember old Rows + long old_global_value = 0; // only used when setting a local and global option + long old_Rows = Rows; // remember old Rows long *pp = (long *)varp; // Disallow changing some options from secure mode. @@ -4176,6 +4285,13 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, char *errbuf, return e_secure; } + // Save the global value before changing anything. This is needed as for + // 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); + } + // Many number options assume their value is in the signed int range. if (value < INT_MIN || value > INT_MAX) { return e_invarg; @@ -4235,6 +4351,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; @@ -4300,6 +4422,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) { @@ -4313,7 +4437,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; @@ -4359,6 +4483,20 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, char *errbuf, // 'winminwidth' win_setminwidth(); } else if (pp == &p_ls) { + // When switching to global statusline, decrease topframe height + // Also clear the cmdline to remove the ruler if there is one + if (value == 3 && old_value != 3) { + frame_new_height(topframe, topframe->fr_height - STATUS_HEIGHT, false, false); + (void)win_comp_pos(); + clear_cmdline = true; + } + // When switching from global statusline, increase height of topframe by STATUS_HEIGHT + // in order to to re-add the space that was previously taken by the global statusline + if (old_value == 3 && value != 3) { + frame_new_height(topframe, topframe->fr_height + STATUS_HEIGHT, false, false); + (void)win_comp_pos(); + } + last_status(false); // (re)set last window status line. } else if (pp == &p_stal) { // (re)set tab page line @@ -4409,10 +4547,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 @@ -4441,7 +4575,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; } @@ -4449,7 +4583,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; } @@ -4520,19 +4654,36 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, char *errbuf, // Don't do this while starting up, failure or recursively. if (!starting && errmsg == NULL && *get_vim_var_str(VV_OPTION_TYPE) == NUL) { char buf_old[NUMBUFLEN]; + char buf_old_global[NUMBUFLEN]; char buf_new[NUMBUFLEN]; char buf_type[7]; vim_snprintf(buf_old, ARRAY_SIZE(buf_old), "%ld", old_value); + vim_snprintf(buf_old_global, ARRAY_SIZE(buf_old_global), "%ld", old_global_value); vim_snprintf(buf_new, ARRAY_SIZE(buf_new), "%ld", value); vim_snprintf(buf_type, ARRAY_SIZE(buf_type), "%s", (opt_flags & OPT_LOCAL) ? "local" : "global"); set_vim_var_string(VV_OPTION_NEW, buf_new, -1); set_vim_var_string(VV_OPTION_OLD, buf_old, -1); set_vim_var_string(VV_OPTION_TYPE, buf_type, -1); - apply_autocmds(EVENT_OPTIONSET, - (char_u *)options[opt_idx].fullname, - NULL, false, NULL); + if (opt_flags & OPT_LOCAL) { + set_vim_var_string(VV_OPTION_COMMAND, "setlocal", -1); + set_vim_var_string(VV_OPTION_OLDLOCAL, buf_old, -1); + } + if (opt_flags & OPT_GLOBAL) { + set_vim_var_string(VV_OPTION_COMMAND, "setglobal", -1); + set_vim_var_string(VV_OPTION_OLDGLOBAL, buf_old, -1); + } + if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0) { + set_vim_var_string(VV_OPTION_COMMAND, "set", -1); + set_vim_var_string(VV_OPTION_OLDLOCAL, buf_old, -1); + set_vim_var_string(VV_OPTION_OLDGLOBAL, buf_old_global, -1); + } + if (opt_flags & OPT_MODELINE) { + set_vim_var_string(VV_OPTION_COMMAND, "modeline", -1); + set_vim_var_string(VV_OPTION_OLDLOCAL, buf_old, -1); + } + apply_autocmds(EVENT_OPTIONSET, (char_u *)options[opt_idx].fullname, NULL, false, NULL); reset_v_option_vars(); } @@ -4548,10 +4699,18 @@ 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; } -static void trigger_optionsset_string(int opt_idx, int opt_flags, char *oldval, char *newval) +/// Trigger the OptionSet autocommand. +/// "opt_idx" is the index of the option being set. +/// "opt_flags" can be OPT_LOCAL etc. +/// "oldval" the old value +/// "oldval_l" the old local value (only non-NULL if global and local value are set) +/// "oldval_g" the old global value (only non-NULL if global and local value are set) +/// "newval" the new value +static void trigger_optionsset_string(int opt_idx, int opt_flags, char *oldval, char *oldval_l, + char *oldval_g, char *newval) { // Don't do this recursively. if (oldval != NULL @@ -4564,8 +4723,24 @@ static void trigger_optionsset_string(int opt_idx, int opt_flags, char *oldval, set_vim_var_string(VV_OPTION_OLD, oldval, -1); set_vim_var_string(VV_OPTION_NEW, newval, -1); set_vim_var_string(VV_OPTION_TYPE, buf_type, -1); - apply_autocmds(EVENT_OPTIONSET, - (char_u *)options[opt_idx].fullname, NULL, false, NULL); + if (opt_flags & OPT_LOCAL) { + set_vim_var_string(VV_OPTION_COMMAND, "setlocal", -1); + set_vim_var_string(VV_OPTION_OLDLOCAL, oldval, -1); + } + if (opt_flags & OPT_GLOBAL) { + set_vim_var_string(VV_OPTION_COMMAND, "setglobal", -1); + set_vim_var_string(VV_OPTION_OLDGLOBAL, oldval, -1); + } + if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0) { + set_vim_var_string(VV_OPTION_COMMAND, "set", -1); + set_vim_var_string(VV_OPTION_OLDLOCAL, oldval_l, -1); + set_vim_var_string(VV_OPTION_OLDGLOBAL, oldval_g, -1); + } + if (opt_flags & OPT_MODELINE) { + set_vim_var_string(VV_OPTION_COMMAND, "modeline", -1); + set_vim_var_string(VV_OPTION_OLDLOCAL, oldval, -1); + } + apply_autocmds(EVENT_OPTIONSET, (char_u *)options[opt_idx].fullname, NULL, false, NULL); reset_v_option_vars(); } } @@ -4757,7 +4932,8 @@ static int findoption(const char *const arg) /// @param stringval NULL when only checking existence /// /// @returns: -/// Number or Toggle option: 1, *numval gets value. +/// Toggle option: 2, *numval gets value. +/// Number option: 1, *numval gets value. /// String option: 0, *stringval gets allocated string. /// Hidden Number or Toggle option: -1. /// hidden String option: -2. @@ -4790,16 +4966,18 @@ int get_option_value(const char *name, long *numval, char_u **stringval, int opt } if (options[opt_idx].flags & P_NUM) { *numval = *(long *)varp; + return 1; + } + + // Special case: 'modified' is b_changed, but we also want to consider + // it set when 'ff' or 'fenc' changed. + if ((int *)varp == &curbuf->b_changed) { + *numval = curbufIsChanged(); } else { - // Special case: 'modified' is b_changed, but we also want to consider - // it set when 'ff' or 'fenc' changed. - if ((int *)varp == &curbuf->b_changed) { - *numval = curbufIsChanged(); - } else { - *numval = (long)*(int *)varp; // NOLINT(whitespace/cast) - } + *numval = (long)*(int *)varp; // NOLINT(whitespace/cast) } - return 1; + + return 2; } // Returns the option attributes and its value. Unlike the above function it @@ -4895,7 +5073,7 @@ int get_option_value_strict(char *name, int64_t *numval, char **stringval, int o // only getting a pointer, no need to use aucmd_prepbuf() curbuf = (buf_T *)from; curwin->w_buffer = curbuf; - varp = get_varp(p); + varp = get_varp_scope(p, OPT_LOCAL); curbuf = save_curbuf; curwin->w_buffer = curbuf; } @@ -4903,7 +5081,7 @@ int get_option_value_strict(char *name, int64_t *numval, char **stringval, int o win_T *save_curwin = curwin; curwin = (win_T *)from; curbuf = curwin->w_buffer; - varp = get_varp(p); + varp = get_varp_scope(p, OPT_LOCAL); curwin = save_curwin; curbuf = curwin->w_buffer; } @@ -4932,6 +5110,9 @@ int get_option_value_strict(char *name, int64_t *numval, char **stringval, int o /// @param[in] number New value for the number or boolean option. /// @param[in] string New value for string option. /// @param[in] opt_flags Flags: OPT_LOCAL, OPT_GLOBAL, or 0 (both). +/// If OPT_CLEAR is set, the value of the option +/// is cleared (the exact semantics of this depend +/// on the option). /// /// @return NULL on success, error message on error. char *set_option_value(const char *const name, const long number, const char *const string, @@ -4957,34 +5138,47 @@ char *set_option_value(const char *const name, const long number, const char *co } if (flags & P_STRING) { const char *s = string; - if (s == NULL) { + if (s == NULL || opt_flags & OPT_CLEAR) { s = ""; } return set_string_option(opt_idx, s, opt_flags); - } else { - varp = get_varp_scope(&(options[opt_idx]), opt_flags); - if (varp != NULL) { // hidden option is not changed - if (number == 0 && string != NULL) { - int idx; - - // Either we are given a string or we are setting option - // to zero. - for (idx = 0; string[idx] == '0'; idx++) {} - if (string[idx] != NUL || idx == 0) { - // There's another character after zeros or the string - // is empty. In both cases, we are trying to set a - // num option using a string. - semsg(_("E521: Number required: &%s = '%s'"), - name, string); - return NULL; // do nothing as we hit an error - } + } + + varp = get_varp_scope(&(options[opt_idx]), opt_flags); + if (varp != NULL) { // hidden option is not changed + if (number == 0 && string != NULL) { + int idx; + + // Either we are given a string or we are setting option + // to zero. + for (idx = 0; string[idx] == '0'; idx++) {} + if (string[idx] != NUL || idx == 0) { + // There's another character after zeros or the string + // is empty. In both cases, we are trying to set a + // num option using a string. + semsg(_("E521: Number required: &%s = '%s'"), + name, string); + return NULL; // do nothing as we hit an error } - if (flags & P_NUM) { - return set_num_option(opt_idx, varp, number, NULL, 0, opt_flags); + } + long numval = number; + if (opt_flags & OPT_CLEAR) { + if ((int *)varp == &curbuf->b_p_ar) { + numval = -1; + } else if ((long *)varp == &curbuf->b_p_ul) { + numval = NO_LOCAL_UNDOLEVEL; + } else if ((long *)varp == &curwin->w_p_so || (long *)varp == &curwin->w_p_siso) { + numval = -1; } else { - return set_bool_option(opt_idx, varp, (int)number, opt_flags); + char *s = NULL; + (void)get_option_value(name, &numval, (char_u **)&s, OPT_GLOBAL); } } + if (flags & P_NUM) { + return set_num_option(opt_idx, varp, numval, NULL, 0, opt_flags); + } else { + return set_bool_option(opt_idx, varp, (int)numval, opt_flags); + } } } return NULL; @@ -5038,7 +5232,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) { @@ -5052,6 +5246,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; @@ -5062,7 +5257,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); } @@ -5071,8 +5266,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; @@ -5092,7 +5289,7 @@ static void showoptions(int all, int opt_flags) && Columns + GAP >= INT_MIN + 3 && (Columns + GAP - 3) / INC >= INT_MIN && (Columns + GAP - 3) / INC <= INT_MAX); - cols = (int)((Columns + GAP - 3) / INC); + cols = (Columns + GAP - 3) / INC; if (cols == 0) { cols = 1; } @@ -5471,7 +5668,7 @@ static int put_setbool(FILE *fd, char *cmd, char *name, int value) void comp_col(void) { - int last_has_status = (p_ls == 2 || (p_ls == 1 && !ONE_WINDOW)); + int last_has_status = (p_ls > 1 || (p_ls == 1 && !ONE_WINDOW)); sc_col = 0; ru_col = 0; @@ -5489,13 +5686,11 @@ void comp_col(void) } } assert(sc_col >= 0 - && INT_MIN + sc_col <= Columns - && Columns - sc_col <= INT_MAX); - sc_col = (int)(Columns - sc_col); + && INT_MIN + sc_col <= Columns); + sc_col = Columns - sc_col; assert(ru_col >= 0 - && INT_MIN + ru_col <= Columns - && Columns - ru_col <= INT_MAX); - ru_col = (int)(Columns - ru_col); + && INT_MIN + ru_col <= Columns); + ru_col = Columns - ru_col; if (sc_col <= 0) { // screen too narrow, will become a mess sc_col = 1; } @@ -5602,6 +5797,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; } } @@ -5668,6 +5867,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" } @@ -5762,6 +5963,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); @@ -5856,6 +6060,8 @@ static char_u *get_varp(vimoption_T *p) return (char_u *)&(curbuf->b_p_cink); case PV_CINO: return (char_u *)&(curbuf->b_p_cino); + case PV_CINSD: + return (char_u *)&(curbuf->b_p_cinsd); case PV_CINW: return (char_u *)&(curbuf->b_p_cinw); case PV_COM: @@ -5990,6 +6196,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. @@ -6002,6 +6209,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); @@ -6046,6 +6255,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 } @@ -6078,6 +6290,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. @@ -6102,6 +6315,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) @@ -6116,11 +6330,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. @@ -6156,11 +6389,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; @@ -6192,80 +6426,131 @@ 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); + buf->b_p_cinsd = vim_strsave(p_cinsd); + COPY_OPT_SCTX(buf, BV_CINSD); // 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 @@ -6285,11 +6570,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; @@ -6302,17 +6590,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; } @@ -6321,6 +6612,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); } } @@ -6961,10 +7253,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 @@ -7001,13 +7290,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; } } @@ -7323,6 +7610,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; @@ -7342,7 +7630,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; } @@ -7355,7 +7643,7 @@ bool tabstop_set(char_u *var, long **array) valcount++; continue; } - emsg(_(e_invarg)); + semsg(_(e_invarg2), var); return false; } @@ -7364,7 +7652,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++; } @@ -7669,6 +7965,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 @@ -7771,7 +8073,7 @@ void set_fileformat(int eol_style, int opt_flags) } // This may cause the buffer to become (un)modified. - check_status(curbuf); + redraw_buf_status_later(curbuf); redraw_tabline = true; need_maketitle = true; // Set window title later. } @@ -7848,7 +8150,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) { @@ -7861,7 +8162,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)) { @@ -7877,6 +8177,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'; @@ -7887,6 +8189,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 452494172f..9321dd5454 100644 --- a/src/nvim/option.h +++ b/src/nvim/option.h @@ -13,15 +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_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 19cb33a354..bf71b63cc8 100644 --- a/src/nvim/option_defs.h +++ b/src/nvim/option_defs.h @@ -57,7 +57,7 @@ #define ENC_DFLT "utf-8" // end-of-line style -#define EOL_UNKNOWN -1 // not defined yet +#define EOL_UNKNOWN (-1) // not defined yet #define EOL_UNIX 0 // NL #define EOL_DOS 1 // CR NL #define EOL_MAC 2 // CR @@ -523,11 +523,11 @@ EXTERN long p_mmd; // 'maxmapdepth' EXTERN long p_mmp; // 'maxmempattern' EXTERN long p_mis; // 'menuitems' EXTERN char_u *p_msm; // 'mkspellmem' -EXTERN long p_mle; // 'modelineexpr' +EXTERN int p_mle; // 'modelineexpr' EXTERN long p_mls; // 'modelines' EXTERN char_u *p_mouse; // 'mouse' EXTERN char_u *p_mousem; // 'mousemodel' -EXTERN long p_mousef; // 'mousefocus' +EXTERN int p_mousef; // 'mousefocus' EXTERN long p_mouset; // 'mousetime' EXTERN int p_more; // 'more' EXTERN char_u *p_opfunc; // 'operatorfunc' @@ -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 @@ -743,6 +745,7 @@ EXTERN int p_write; // 'write' EXTERN int p_wa; // 'writeany' EXTERN int p_wb; // 'writebackup' EXTERN long p_wd; // 'writedelay' +EXTERN int p_cdh; // 'cdhome' EXTERN int p_force_on; ///< options that cannot be turned off. EXTERN int p_force_off; ///< options that cannot be turned on. @@ -770,6 +773,7 @@ enum { BV_CINK, BV_CINO, BV_CINW, + BV_CINSD, BV_CM, BV_CMS, BV_COM, @@ -868,6 +872,7 @@ enum { WV_LBR, WV_NU, WV_RNU, + WV_VE, WV_NUW, WV_PVW, WV_RL, @@ -895,10 +900,12 @@ enum { }; // Value for b_p_ul indicating the global value must be used. -#define NO_LOCAL_UNDOLEVEL -123456 +#define NO_LOCAL_UNDOLEVEL (-123456) #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 71208dfc68..313cace4b2 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -275,6 +275,14 @@ return { defaults={if_true="internal,keepascii"} }, { + full_name='cdhome', abbreviation='cdh', + short_desc=N_(":cd without argument goes to the home directory"), + type='bool', scope={'global'}, + secure=true, + varname='p_cdh', + defaults={if_true=false} + }, + { full_name='cdpath', abbreviation='cd', short_desc=N_("list of directories searched with \":cd\""), type='string', list='comma', scope={'global'}, @@ -343,6 +351,15 @@ return { defaults={if_true="if,else,while,do,for,switch"} }, { + full_name='cinscopedecls', abbreviation='cinsd', + short_desc=N_("words that are recognized by 'cino-g'"), + type='string', list='onecomma', scope={'buffer'}, + deny_duplicates=true, + alloced=true, + varname='p_cinsd', + defaults={if_true="public,protected,private"} + }, + { full_name='clipboard', abbreviation='cb', short_desc=N_("use the clipboard as the unnamed register"), type='string', list='onecomma', scope={'global'}, @@ -403,7 +420,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', @@ -657,14 +674,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', @@ -1176,7 +1193,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', @@ -1838,7 +1855,7 @@ return { type='number', scope={'global'}, secure=true, varname='p_pyx', - defaults={if_true=0} + defaults={if_true=3} }, { full_name='quickfixtextfunc', abbreviation='qftf', @@ -2491,7 +2508,7 @@ return { }, { full_name='termencoding', abbreviation='tenc', - short_desc=N_("Terminal encodig"), + short_desc=N_("Terminal encoding"), type='string', scope={'global'}, defaults={if_true=""} }, @@ -2614,7 +2631,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', @@ -2728,7 +2745,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..b738d36234 100644 --- a/src/nvim/os/env.c +++ b/src/nvim/os/env.c @@ -720,7 +720,7 @@ void expand_env_esc(char_u *restrict srcp, char_u *restrict dst, int dstlen, boo && dst[-1] != ':' #endif && vim_ispathsep(*tail)) { - ++tail; + tail++; } dst += c; src = tail; @@ -738,7 +738,7 @@ void expand_env_esc(char_u *restrict srcp, char_u *restrict dst, int dstlen, boo at_start = false; if (src[0] == '\\' && src[1] != NUL) { *dst++ = *src++; - --dstlen; + dstlen--; } else if ((src[0] == ' ' || src[0] == ',') && !one) { at_start = true; } @@ -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 e9963516fc..daf974ee74 100644 --- a/src/nvim/os/fs.c +++ b/src/nvim/os/fs.c @@ -21,7 +21,6 @@ #include "nvim/assert.h" #include "nvim/memory.h" #include "nvim/message.h" -#include "nvim/misc1.h" #include "nvim/option.h" #include "nvim/os/os.h" #include "nvim/os/os_defs.h" @@ -41,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; \ @@ -53,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); } @@ -99,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 { @@ -739,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; } @@ -936,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; @@ -963,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); } @@ -1024,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; } @@ -1042,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, @@ -1050,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; } @@ -1166,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) { @@ -1174,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 5b231f205b..745b888b5e 100644 --- a/src/nvim/os/input.c +++ b/src/nvim/os/input.c @@ -17,7 +17,6 @@ #include "nvim/main.h" #include "nvim/mbyte.h" #include "nvim/memory.h" -#include "nvim/misc1.h" #include "nvim/msgpack_rpc/channel.h" #include "nvim/os/input.h" #include "nvim/state.h" @@ -183,6 +182,40 @@ void os_breakcheck(void) updating_screen = save_us; } +#define BREAKCHECK_SKIP 1000 +static int breakcheck_count = 0; + +/// Check for CTRL-C pressed, but only once in a while. +/// +/// Should be used instead of os_breakcheck() for functions that check for +/// each line in the file. Calling os_breakcheck() each time takes too much +/// time, because it will use system calls to check for input. +void line_breakcheck(void) +{ + if (++breakcheck_count >= BREAKCHECK_SKIP) { + breakcheck_count = 0; + os_breakcheck(); + } +} + +/// Like line_breakcheck() but check 10 times less often. +void fast_breakcheck(void) +{ + if (++breakcheck_count >= BREAKCHECK_SKIP * 10) { + breakcheck_count = 0; + os_breakcheck(); + } +} + +/// Like line_breakcheck() but check 100 times less often. +void veryfast_breakcheck(void) +{ + if (++breakcheck_count >= BREAKCHECK_SKIP * 100) { + breakcheck_count = 0; + os_breakcheck(); + } +} + /// Test whether a file descriptor refers to a terminal. /// @@ -201,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, @@ -230,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); @@ -259,8 +288,13 @@ static uint8_t check_multiclick(int code, int grid, int row, int col) static int orig_mouse_row = 0; static uint64_t orig_mouse_time = 0; // time of previous mouse click - if (code == KE_LEFTRELEASE || code == KE_RIGHTRELEASE - || code == KE_MIDDLERELEASE) { + if (code == KE_LEFTRELEASE + || code == KE_RIGHTRELEASE + || code == KE_MIDDLERELEASE + || code == KE_MOUSEDOWN + || code == KE_MOUSEUP + || code == KE_MOUSELEFT + || code == KE_MOUSERIGHT) { return 0; } uint64_t mouse_time = os_hrtime(); // time of current mouse click (ns) @@ -493,6 +527,7 @@ static bool input_ready(MultiQueue *events) // Exit because of an input read error. static void read_error_exit(void) + FUNC_ATTR_NORETURN { if (silent_mode) { // Normal way to exit for "nvim -es". getout(0); diff --git a/src/nvim/os/os_defs.h b/src/nvim/os/os_defs.h index 8049b3b80e..a4361859ec 100644 --- a/src/nvim/os/os_defs.h +++ b/src/nvim/os/os_defs.h @@ -13,6 +13,10 @@ # include "nvim/os/unix_defs.h" #endif +#if !defined(NAME_MAX) && defined(_XOPEN_NAME_MAX) +# define NAME_MAX _XOPEN_NAME_MAX +#endif + #define BASENAMELEN (NAME_MAX - 5) // Use the system path length if it makes sense. diff --git a/src/nvim/os/pty_process_unix.c b/src/nvim/os/pty_process_unix.c index 24ecf5c24f..4a49c0b162 100644 --- a/src/nvim/os/pty_process_unix.c +++ b/src/nvim/os/pty_process_unix.c @@ -15,6 +15,12 @@ # include <libutil.h> #elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__) # include <util.h> +#elif defined(__sun) +# include <fcntl.h> +# include <signal.h> +# include <sys/stream.h> +# include <sys/syscall.h> +# include <unistd.h> #else # include <pty.h> #endif @@ -38,6 +44,117 @@ # include "os/pty_process_unix.c.generated.h" #endif +#if defined(__sun) && !defined(HAVE_FORKPTY) + +// this header defines STR, just as nvim.h, but it is defined as ('S'<<8), +// to avoid #undef STR, #undef STR, #define STR ('S'<<8) just delay the +// inclusion of the header even though it gets include out of order. +# include <sys/stropts.h> + +static int openpty(int *amaster, int *aslave, char *name, struct termios *termp, + struct winsize *winp) +{ + int slave = -1; + int master = open("/dev/ptmx", O_RDWR); + if (master == -1) { + goto error; + } + + // grantpt will invoke a setuid program to change permissions + // and might fail if SIGCHLD handler is set, temporarily reset + // while running + void (*sig_saved)(int) = signal(SIGCHLD, SIG_DFL); + int res = grantpt(master); + signal(SIGCHLD, sig_saved); + + if (res == -1 || unlockpt(master) == -1) { + goto error; + } + + char *slave_name = ptsname(master); + if (slave_name == NULL) { + goto error; + } + + slave = open(slave_name, O_RDWR|O_NOCTTY); + if (slave == -1) { + goto error; + } + + // ptem emulates a terminal when used on a pseudo terminal driver, + // must be pushed before ldterm + ioctl(slave, I_PUSH, "ptem"); + // ldterm provides most of the termio terminal interface + ioctl(slave, I_PUSH, "ldterm"); + // ttcompat compatibility with older terminal ioctls + ioctl(slave, I_PUSH, "ttcompat"); + + if (termp) { + tcsetattr(slave, TCSAFLUSH, termp); + } + if (winp) { + ioctl(slave, TIOCSWINSZ, winp); + } + + *amaster = master; + *aslave = slave; + // ignoring name, not passed and size is unknown in the API + + return 0; + +error: + if (slave != -1) { + close(slave); + } + if (master != -1) { + close(master); + } + return -1; +} + +static int login_tty(int fd) +{ + setsid(); + if (ioctl(fd, TIOCSCTTY, NULL) == -1) { + return -1; + } + + dup2(fd, STDIN_FILENO); + dup2(fd, STDOUT_FILENO); + dup2(fd, STDERR_FILENO); + if (fd > STDERR_FILENO) { + close(fd); + } + + return 0; +} + +static pid_t forkpty(int *amaster, char *name, struct termios *termp, struct winsize *winp) +{ + int master, slave; + if (openpty(&master, &slave, name, termp, winp) == -1) { + return -1; + } + + pid_t pid = fork(); + switch (pid) { + case -1: + close(master); + close(slave); + return -1; + case 0: + close(master); + login_tty(slave); + return 0; + default: + close(slave); + *amaster = master; + return pid; + } +} + +#endif + /// termios saved at startup (for TUI) or initialized by pty_process_spawn(). static struct termios termios_default; @@ -198,7 +315,9 @@ static void init_termios(struct termios *termios) FUNC_ATTR_NONNULL_ALL termios->c_cflag = CS8|CREAD; termios->c_lflag = ISIG|ICANON|IEXTEN|ECHO|ECHOE|ECHOK; - cfsetspeed(termios, 38400); + // not using cfsetspeed, not available on all platforms + cfsetispeed(termios, 38400); + cfsetospeed(termios, 38400); #ifdef IUTF8 termios->c_iflag |= IUTF8; diff --git a/src/nvim/os/pty_process_win.c b/src/nvim/os/pty_process_win.c index f78f3e66f5..4fb9e30a96 100644 --- a/src/nvim/os/pty_process_win.c +++ b/src/nvim/os/pty_process_win.c @@ -58,8 +58,7 @@ int pty_process_spawn(PtyProcess *ptyproc) if (os_has_conpty_working()) { if ((conpty_object = - os_conpty_init(&in_name, &out_name, - ptyproc->width, ptyproc->height)) != NULL) { + os_conpty_init(&in_name, &out_name, ptyproc->width, ptyproc->height)) != NULL) { ptyproc->type = kConpty; } } @@ -281,7 +280,7 @@ static void wait_eof_timer_cb(uv_timer_t *wait_eof_timer) PtyProcess *ptyproc = wait_eof_timer->data; Process *proc = (Process *)ptyproc; - if (proc->out.closed || !uv_is_readable(proc->out.uvstream)) { + if (proc->out.closed || proc->out.did_eof || !uv_is_readable(proc->out.uvstream)) { uv_timer_stop(&ptyproc->wait_eof_timer); pty_process_finish2(ptyproc); } @@ -308,7 +307,7 @@ static void pty_process_finish2(PtyProcess *ptyproc) /// Build the command line to pass to CreateProcessW. /// /// @param[in] argv Array with string arguments. -/// @param[out] cmd_line Location where saved builded cmd line. +/// @param[out] cmd_line Location where saved built cmd line. /// /// @returns zero on success, or error code of MultiByteToWideChar function. /// diff --git a/src/nvim/os/shell.c b/src/nvim/os/shell.c index 6ef0aa1091..5680cdbe42 100644 --- a/src/nvim/os/shell.c +++ b/src/nvim/os/shell.c @@ -9,6 +9,7 @@ #include "nvim/ascii.h" #include "nvim/charset.h" +#include "nvim/eval.h" #include "nvim/event/libuv_process.h" #include "nvim/event/loop.h" #include "nvim/event/rstream.h" @@ -20,13 +21,13 @@ #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/message.h" -#include "nvim/misc1.h" #include "nvim/option_defs.h" #include "nvim/os/shell.h" #include "nvim/os/signal.h" #include "nvim/path.h" #include "nvim/screen.h" #include "nvim/strings.h" +#include "nvim/tag.h" #include "nvim/types.h" #include "nvim/ui.h" #include "nvim/vim.h" @@ -681,6 +682,116 @@ int os_call_shell(char_u *cmd, ShellOpts opts, char_u *extra_args) return exitcode; } +/// os_call_shell() wrapper. Handles 'verbose', :profile, and v:shell_error. +/// Invalidates cached tags. +/// +/// @return shell command exit code +int call_shell(char_u *cmd, ShellOpts opts, char_u *extra_shell_arg) +{ + int retval; + proftime_T wait_time; + + if (p_verbose > 3) { + verbose_enter(); + smsg(_("Executing command: \"%s\""), cmd == NULL ? p_sh : cmd); + msg_putchar('\n'); + verbose_leave(); + } + + if (do_profiling == PROF_YES) { + prof_child_enter(&wait_time); + } + + if (*p_sh == NUL) { + emsg(_(e_shellempty)); + retval = -1; + } else { + // The external command may update a tags file, clear cached tags. + tag_freematch(); + + retval = os_call_shell(cmd, opts, extra_shell_arg); + } + + set_vim_var_nr(VV_SHELL_ERROR, (varnumber_T)retval); + if (do_profiling == PROF_YES) { + prof_child_exit(&wait_time); + } + + return retval; +} + +/// Get the stdout of an external command. +/// If "ret_len" is NULL replace NUL characters with NL. When "ret_len" is not +/// NULL store the length there. +/// +/// @param cmd command to execute +/// @param infile optional input file name +/// @param flags can be kShellOptSilent or 0 +/// @param ret_len length of the stdout +/// +/// @return an allocated string, or NULL for error. +char_u *get_cmd_output(char_u *cmd, char_u *infile, ShellOpts flags, size_t *ret_len) +{ + char_u *buffer = NULL; + + if (check_secure()) { + return NULL; + } + + // get a name for the temp file + char_u *tempname = vim_tempname(); + if (tempname == NULL) { + emsg(_(e_notmp)); + return NULL; + } + + // Add the redirection stuff + char_u *command = make_filter_cmd(cmd, infile, tempname); + + // Call the shell to execute the command (errors are ignored). + // Don't check timestamps here. + no_check_timestamps++; + call_shell(command, kShellOptDoOut | kShellOptExpand | flags, NULL); + no_check_timestamps--; + + xfree(command); + + // read the names from the file into memory + FILE *fd = os_fopen((char *)tempname, READBIN); + + if (fd == NULL) { + semsg(_(e_notopen), tempname); + goto done; + } + + fseek(fd, 0L, SEEK_END); + size_t len = (size_t)ftell(fd); // get size of temp file + fseek(fd, 0L, SEEK_SET); + + buffer = xmalloc(len + 1); + size_t i = fread((char *)buffer, 1, len, fd); + fclose(fd); + os_remove((char *)tempname); + if (i != len) { + semsg(_(e_notread), tempname); + XFREE_CLEAR(buffer); + } else if (ret_len == NULL) { + // Change NUL into SOH, otherwise the string is truncated. + for (i = 0; i < len; i++) { + if (buffer[i] == NUL) { + buffer[i] = 1; + } + } + + buffer[len] = NUL; // make sure the buffer is terminated + } else { + *ret_len = len; + } + +done: + xfree(tempname); + return buffer; +} /// os_system - synchronously execute a command in the shell /// /// example: @@ -1118,7 +1229,7 @@ static void read_input(DynamicBuffer *buf) dynamic_buffer_ensure(buf, buf->len + 1); buf->data[buf->len++] = NL; } - ++lnum; + lnum++; if (lnum > curbuf->b_op_end.lnum) { break; } diff --git a/src/nvim/os/signal.c b/src/nvim/os/signal.c index 0d125ec964..79e042b8a5 100644 --- a/src/nvim/os/signal.c +++ b/src/nvim/os/signal.c @@ -18,11 +18,10 @@ #include "nvim/main.h" #include "nvim/memline.h" #include "nvim/memory.h" -#include "nvim/misc1.h" #include "nvim/os/signal.h" #include "nvim/vim.h" -static SignalWatcher spipe, shup, squit, sterm, susr1; +static SignalWatcher spipe, shup, squit, sterm, susr1, swinch; #ifdef SIGPWR static SignalWatcher spwr; #endif @@ -55,6 +54,9 @@ void signal_init(void) #ifdef SIGUSR1 signal_watcher_init(&main_loop, &susr1, NULL); #endif +#ifdef SIGWINCH + signal_watcher_init(&main_loop, &swinch, NULL); +#endif signal_start(); } @@ -71,6 +73,9 @@ void signal_teardown(void) #ifdef SIGUSR1 signal_watcher_close(&susr1, NULL); #endif +#ifdef SIGWINCH + signal_watcher_close(&swinch, NULL); +#endif } void signal_start(void) @@ -89,6 +94,9 @@ void signal_start(void) #ifdef SIGUSR1 signal_watcher_start(&susr1, on_signal, SIGUSR1); #endif +#ifdef SIGWINCH + signal_watcher_start(&swinch, on_signal, SIGWINCH); +#endif } void signal_stop(void) @@ -107,6 +115,9 @@ void signal_stop(void) #ifdef SIGUSR1 signal_watcher_stop(&susr1); #endif +#ifdef SIGWINCH + signal_watcher_stop(&swinch); +#endif } void signal_reject_deadly(void) @@ -142,6 +153,10 @@ static char *signal_name(int signum) case SIGUSR1: return "SIGUSR1"; #endif +#ifdef SIGWINCH + case SIGWINCH: + return "SIGWINCH"; +#endif default: return "Unknown"; } @@ -153,6 +168,7 @@ static char *signal_name(int signum) // NOTE: Avoid unsafe functions, such as allocating memory, they can result in // a deadlock. static void deadly_signal(int signum) + FUNC_ATTR_NORETURN { // Set the v:dying variable. set_vim_var_nr(VV_DYING, 1); @@ -198,6 +214,12 @@ static void on_signal(SignalWatcher *handle, int signum, void *data) curbuf); break; #endif +#ifdef SIGWINCH + case SIGWINCH: + apply_autocmds(EVENT_SIGNAL, (char_u *)"SIGWINCH", curbuf->b_fname, true, + curbuf); + break; +#endif default: ELOG("invalid signal: %d", signum); break; diff --git a/src/nvim/os/users.c b/src/nvim/os/users.c index 9952e2b387..e0ce3fec31 100644 --- a/src/nvim/os/users.c +++ b/src/nvim/os/users.c @@ -18,6 +18,9 @@ # include <lm.h> #endif +// All user names (for ~user completion as done by shell). +static garray_T ga_users = GA_EMPTY_INIT_VALUE; + // Add a user name to the list of users in garray_T *users. // Do nothing if user name is NULL or empty. static void add_user(garray_T *users, char *user, bool need_copy) @@ -157,3 +160,60 @@ char *os_get_user_directory(const char *name) return NULL; } + +#if defined(EXITFREE) + +void free_users(void) +{ + ga_clear_strings(&ga_users); +} + +#endif + +/// Find all user names for user completion. +/// +/// Done only once and then cached. +static void init_users(void) +{ + static int lazy_init_done = false; + + if (lazy_init_done) { + return; + } + + lazy_init_done = true; + + os_get_usernames(&ga_users); +} + +/// Given to ExpandGeneric() to obtain an user names. +char_u *get_users(expand_T *xp, int idx) +{ + init_users(); + if (idx < ga_users.ga_len) { + return ((char_u **)ga_users.ga_data)[idx]; + } + return NULL; +} + +/// Check whether name matches a user name. +/// +/// @return 0 if name does not match any user name. +/// 1 if name partially matches the beginning of a user name. +/// 2 is name fully matches a user name. +int match_user(char_u *name) +{ + int n = (int)STRLEN(name); + int result = 0; + + init_users(); + for (int i = 0; i < ga_users.ga_len; i++) { + if (STRCMP(((char_u **)ga_users.ga_data)[i], name) == 0) { + return 2; // full match + } + if (STRNCMP(((char_u **)ga_users.ga_data)[i], name, n) == 0) { + result = 1; // partial match + } + } + return result; +} diff --git a/src/nvim/os/win_defs.h b/src/nvim/os/win_defs.h index efef77be7b..1ae86d6bbe 100644 --- a/src/nvim/os/win_defs.h +++ b/src/nvim/os/win_defs.h @@ -36,7 +36,7 @@ // Windows defines a RGB macro that produces 0x00bbggrr color values for use // with GDI. Our macro is different, and we don't use GDI. // Duplicated from macros.h to avoid include-order sensitivity. -#define RGB_(r, g, b) ((r << 16) | (g << 8) | b) +#define RGB_(r, g, b) (((r) << 16) | ((g) << 8) | (b)) #ifdef _MSC_VER # ifndef inline diff --git a/src/nvim/os_unix.c b/src/nvim/os_unix.c index 9396a5896a..1398dba0e4 100644 --- a/src/nvim/os_unix.c +++ b/src/nvim/os_unix.c @@ -20,7 +20,6 @@ #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/message.h" -#include "nvim/misc1.h" #include "nvim/mouse.h" #include "nvim/msgpack_rpc/helpers.h" #include "nvim/os/input.h" diff --git a/src/nvim/path.c b/src/nvim/path.c index 19c820e4dd..75624778e3 100644 --- a/src/nvim/path.c +++ b/src/nvim/path.c @@ -18,7 +18,6 @@ #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/message.h" -#include "nvim/misc1.h" #include "nvim/option.h" #include "nvim/os/input.h" #include "nvim/os/os.h" @@ -270,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) { @@ -288,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) { @@ -299,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); } /* @@ -630,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; @@ -1337,6 +1348,17 @@ int gen_expand_wildcards(int num_pat, char_u **pat, int *num_file, char_u ***fil return ((flags & EW_EMPTYOK) || ga.ga_data != NULL) ? OK : FAIL; } +/// Free the list of files returned by expand_wildcards() or other expansion functions. +void FreeWild(int count, char_u **files) +{ + if (count <= 0 || files == NULL) { + return; + } + while (count--) { + xfree(files[count]); + } + xfree(files); +} /* * Return TRUE if we can expand this backtick thing here. @@ -1498,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 @@ -1672,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) { @@ -1733,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 + 1; (isalpha(*p) || (*p == '-')); p++) {} + + // check last char is not a dash + if (p[-1] == '-') { + return 0; + } + + // "://" or ":\\" must follow return path_is_url(p); } @@ -2245,11 +2289,17 @@ int path_full_dir_name(char *directory, char *buffer, size_t len) } if (os_chdir(directory) != SUCCESS) { - // Do not return immediately since we may be in the wrong directory. - retval = FAIL; - } - - if (retval == FAIL || os_dirname((char_u *)buffer, len) == FAIL) { + // Path does not exist (yet). For a full path fail, + // will use the path as-is. For a relative path use + // the current directory and append the file name. + if (path_is_absolute((const char_u *)directory)) { + // Do not return immediately since we may be in the wrong directory. + retval = FAIL; + } else { + xstrlcpy(buffer, old_dir, len); + append_path(buffer, directory, len); + } + } else if (os_dirname((char_u *)buffer, len) == FAIL) { // Do not return immediately since we are in the wrong directory. retval = FAIL; } @@ -2351,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/po/ru.po b/src/nvim/po/ru.po index 3a96ece2fb..5d3e51b7e2 100644 --- a/src/nvim/po/ru.po +++ b/src/nvim/po/ru.po @@ -959,7 +959,6 @@ msgstr "E129: Требуется имя функции" #, c-format msgid "E128: Function name must start with a capital or \"s:\": %s" msgstr "E128: Имя функции должно начинаться с заглавной буквы или \"s:\": %s" -"двоеточие: %s" #: ../eval.c:17833 #, c-format @@ -1578,7 +1577,7 @@ msgstr "E494: Используйте w или w>>" #: ../ex_docmd.c:3454 msgid "E319: The command is not available in this version" -msgstr "E319: Извините, эта команда недоступна в данной версии" +msgstr "E319: Эта команда недоступна в данной версии" #: ../ex_docmd.c:3752 msgid "E172: Only one file name allowed" @@ -2055,7 +2054,7 @@ msgstr "E201: Автокоманды *ReadPre не должны изменять #: ../fileio.c:672 msgid "Nvim: Reading from stdin...\n" -msgstr "Vim: Чтение из стандартного потока ввода stdin...\n" +msgstr "Nvim: Чтение из стандартного потока ввода stdin...\n" #. Re-opening the original file failed! #: ../fileio.c:909 @@ -2427,7 +2426,7 @@ msgstr "--Удалено--" #: ../fileio.c:5732 #, c-format msgid "auto-removing autocommand: %s <buffer=%d>" -msgstr "авто-удаление автокоманды: %s <буффер=%d>" +msgstr "авто-удаление автокоманды: %s <буфер=%d>" #. the group doesn't exist #: ../fileio.c:5772 @@ -2666,11 +2665,11 @@ msgstr "E17: \"%s\" является каталогом" #: ../globals.h:1020 #, fuzzy msgid "E900: Invalid job id" -msgstr "E49: Недопустимый размер прокрутки" +msgstr "E900: Недопустимый идентификатор задания" #: ../globals.h:1021 msgid "E901: Job table is full" -msgstr "" +msgstr "E901: Таблица заданий переполнена" #: ../globals.h:1024 #, c-format @@ -2707,9 +2706,7 @@ msgstr "E477: ! не допускается" #: ../globals.h:1035 msgid "E25: Nvim does not have a built-in GUI" -msgstr "" -"E25: Возможность использования графического интерфейса выключена при " -"компиляции" +msgstr "E25: У Nvim нет встроенного графического интерфейса" #: ../globals.h:1036 #, c-format @@ -3351,10 +3348,10 @@ msgstr "E282: Невозможно выполнить чтение из \"%s\"" #: ../main.c:2149 msgid "" "\n" -"More info with: \"vim -h\"\n" +"More info with: \"" msgstr "" "\n" -"Дополнительная информация: \"vim -h\"\n" +"Дополнительная информация: \"" #: ../main.c:2178 msgid "[file ..] edit specified file(s)" @@ -4413,7 +4410,7 @@ msgstr "E663: В конце списка изменений" #: ../normal.c:7053 msgid "Type :quit<Enter> to exit Nvim" -msgstr "Введите :quit<Enter> для выхода из Vim" +msgstr "Введите :quit<Enter> для выхода из Nvim" #: ../ops.c:248 #, c-format @@ -4521,6 +4518,7 @@ msgid "" "lines" msgstr "" "E883: шаблон поиска и регистр выражения не могут содержать двух или более " +"строк" #: ../ops.c:5089 #, c-format @@ -6137,7 +6135,7 @@ msgstr "Vim: Ошибка чтения ввода, выход...\n" #: ../undo.c:379 #, fuzzy msgid "E881: Line count changed unexpectedly" -msgstr "E834: Неожиданно изменился счётчик строк" +msgstr "E881: Неожиданно изменился счётчик строк" #: ../undo.c:627 #, c-format diff --git a/src/nvim/po/uk.po b/src/nvim/po/uk.po index f0ae154648..7f0fe6a197 100644 --- a/src/nvim/po/uk.po +++ b/src/nvim/po/uk.po @@ -14,7 +14,7 @@ msgid "" msgstr "" "Project-Id-Version: vim 7.4\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2021-01-18 17:46+0200\n" +"POT-Creation-Date: 2022-04-13 10:28+0300\n" "PO-Revision-Date: 2020-08-23 20:19+0300\n" "Last-Translator: Анатолій Сахнік <sakhnik@gmail.com>\n" "Language-Team: Ukrainian\n" @@ -40,18 +40,6 @@ msgstr "E936: Не вдалося знищити цю групу" msgid "W19: Deleting augroup that is still in use" msgstr "W19: Знищення автогрупи, яка все ще використовується" -#, c-format -msgid "E215: Illegal character after *: %s" -msgstr "E215: Недозволений символ після *: %s" - -#, c-format -msgid "E216: No such event: %s" -msgstr "E216: Немає такої події: %s" - -#, c-format -msgid "E216: No such group or event: %s" -msgstr "E216: Немає такої групи чи події: %s" - msgid "" "\n" "--- Autocommands ---" @@ -84,6 +72,18 @@ msgstr "Виконується %s" msgid "autocommand %s" msgstr "автокоманда %s" +#, c-format +msgid "E215: Illegal character after *: %s" +msgstr "E215: Недозволений символ після *: %s" + +#, c-format +msgid "E216: No such event: %s" +msgstr "E216: Немає такої події: %s" + +#, c-format +msgid "E216: No such group or event: %s" +msgstr "E216: Немає такої групи чи події: %s" + msgid "[Location List]" msgstr "[Список місць]" @@ -111,27 +111,6 @@ msgstr "E516: Жоден з буферів не знищено" msgid "E517: No buffers were wiped out" msgstr "E517: Жоден з буферів не витерто" -msgid "1 buffer unloaded" -msgstr "Вивантажено один буфер" - -#, c-format -msgid "%d buffers unloaded" -msgstr "Вивантажено %d буфери(ів)" - -msgid "1 buffer deleted" -msgstr "Знищено один буфер" - -#, c-format -msgid "%d buffers deleted" -msgstr "Знищено %d буфери(ів)" - -msgid "1 buffer wiped out" -msgstr "Витерто один буфер" - -#, c-format -msgid "%d buffers wiped out" -msgstr "Витерто %d буфери(ів)" - msgid "E90: Cannot unload last buffer" msgstr "E90: Не можу вивантажити останній буфер" @@ -148,14 +127,14 @@ msgid "E88: Cannot go before first buffer" msgstr "E88: Це вже найперший буфер" #, c-format -msgid "E89: %s will be killed (add ! to override)" -msgstr "E89: «%s» буде вбито (! щоб не зважати)" - -#, c-format msgid "" "E89: No write since last change for buffer %<PRId64> (add ! to override)" msgstr "E89: Буфер %<PRId64> має зміни (! щоб не зважати)" +#, c-format +msgid "E89: %s will be killed (add ! to override)" +msgstr "E89: «%s» буде вбито (! щоб не зважати)" + msgid "E948: Job still running (add ! to end the job)" msgstr "E948: Задача все ще виконується (! щоб закінчити)" @@ -206,14 +185,6 @@ msgid "[readonly]" msgstr "[лише читати]" #, c-format -msgid "1 line --%d%%--" -msgstr "один рядок --%d%%--" - -#, c-format -msgid "%<PRId64> lines --%d%%--" -msgstr "%<PRId64> рядки(ів) --%d%%--" - -#, c-format msgid "line %<PRId64> of %<PRId64> --%d%%-- col " msgstr "рядок %<PRId64> з %<PRId64> --%d%%-- колонка " @@ -277,6 +248,51 @@ msgstr "E548: Потрібна цифра" msgid "E549: Illegal percentage" msgstr "E549: Неправильний відсоток" +msgid "Entering Debug mode. Type \"cont\" to continue." +msgstr "Режим налагодження. Щоб продовжити введіть «cont»." + +#, c-format +msgid "Oldval = \"%s\"" +msgstr "Oldval = «%s»" + +#, c-format +msgid "Newval = \"%s\"" +msgstr "Newval = «%s»" + +#, c-format +msgid "line %<PRId64>: %s" +msgstr "рядок %<PRId64>: %s" + +#, c-format +msgid "cmd: %s" +msgstr "команда: %s" + +msgid "frame is zero" +msgstr "кадр нульовий" + +#, c-format +msgid "frame at highest level: %d" +msgstr "кадр на найвищому рівні: %d" + +#, c-format +msgid "Breakpoint in \"%s%s\" line %<PRId64>" +msgstr "Точка зупинки в «%s%s» рядок %<PRId64>" + +#, c-format +msgid "E161: Breakpoint not found: %s" +msgstr "E161: Точку зупинки не знайдено: %s" + +msgid "No breakpoints defined" +msgstr "Не визначено жодної точки зупинки" + +#, c-format +msgid "%3d %s %s line %<PRId64>" +msgstr "%3d %s %s рядок %<PRId64>" + +#, c-format +msgid "%3d expr %s" +msgstr "%3d вираз %s" + #, c-format msgid "E96: Cannot diff more than %<PRId64> buffers" msgstr "E96: Не можна порівнювати понад %<PRId64> буфери(ів)" @@ -325,6 +341,19 @@ msgstr "E103: Буфер «%s» не в режимі порівняння" msgid "E787: Buffer changed unexpectedly" msgstr "E787: Буфер несподівано змінився" +#, c-format +msgid "E1214: Digraph must be just two characters: %s" +msgstr "E1214: Диграф має бути з двох символів: %s" + +#, c-format +msgid "E1215: Digraph must be one character: %s" +msgstr "E1215: Диграф має бути одним символом: %s" + +msgid "" +"E1216: digraph_setlist() argument must be a list of lists with two items" +msgstr "" +"E1216: аргумент digraph_setlist() має бути списком списків з двох елементів" + msgid "E104: Escape not allowed in digraph" msgstr "E104: У диграфах не може міститися escape" @@ -528,14 +557,21 @@ msgstr "E461: Неприпустима назва змінної: %s" msgid "E995: Cannot modify existing variable" msgstr "E995: Неможливо змінити наявну змінну" -msgid "E957: Invalid window number" -msgstr "E957: Некоректний номер вікна" +msgid "E274: No white space allowed before parenthesis" +msgstr "E274: Перед дужками не має бути пробілу" #, c-format msgid "E940: Cannot lock or unlock variable %s" msgstr "E940: Неможливо заблокувати чи розблокувати змінну %s" #, c-format +msgid "E80: Error while writing: %s" +msgstr "E80: Помилка під час запису: %s" + +msgid "E1098: String, List or Blob required" +msgstr "E1098: Потрібен String, List чи Blob" + +#, c-format msgid "E734: Wrong variable type for %s=" msgstr "E734: Неправильний тип змінної для %s=" @@ -564,8 +600,8 @@ msgstr "E687: Цілей менше, ніж елементів списку" msgid "E688: More targets than List items" msgstr "E688: Цілей більше, ніж елементів списку" -msgid "Double ; in list of variables" -msgstr "Друга ; у списку змінних" +msgid "E452: Double ; in list of variables" +msgstr "E452: Друга ; у списку змінних" #, c-format msgid "E738: Can't list variables for %s" @@ -584,8 +620,8 @@ msgstr "E996: Неможливо заблокувати регістр" msgid "E121: Undefined variable: %.*s" msgstr "E121: Невизначена змінна: %.*s" -msgid "E689: Can only index a List or Dictionary" -msgstr "E689: Індексний доступ може бути тільки до списку чи словника" +msgid "E689: Can only index a List, Dictionary or Blob" +msgstr "E689: Індексний доступ може бути тільки до List, Dictionary чи Blob" msgid "E708: [:] must come last" msgstr "E708: [:] має бути останньою" @@ -593,8 +629,11 @@ msgstr "E708: [:] має бути останньою" msgid "E713: Cannot use empty key after ." msgstr "E713: Неможливо вжити порожній ключ після ." -msgid "E709: [:] requires a List value" -msgstr "E709: [:] вимагає список" +msgid "E709: [:] requires a List or Blob value" +msgstr "E709: [:] вимагає List чи Blob" + +msgid "E972: Blob value does not have the right number of bytes" +msgstr "E972: неправильна кількість байтів у значенні Blob" msgid "E996: Cannot lock a range" msgstr "E996: Неможливо заблокувати діапазон" @@ -618,27 +657,18 @@ msgstr "E108: Змінної немає: «%s»" msgid "E109: Missing ':' after '?'" msgstr "E109: Бракує ':' після '?'" -msgid "E691: Can only compare List with List" -msgstr "E691: Список можна порівняти тільки зі списком" - -msgid "E692: Invalid operation for List" -msgstr "E692: Некоректна операція над списком" - -msgid "E735: Can only compare Dictionary with Dictionary" -msgstr "E735: Словник можна порівняти тільки із словником" - -msgid "E736: Invalid operation for Dictionary" -msgstr "E736: Некоректна операція над словником" - -msgid "E694: Invalid operation for Funcrefs" -msgstr "E694: Некоректна операція над функцією" - msgid "E804: Cannot use '%' with Float" msgstr "E804: Не можна виконати '%' над Float" +msgid "E973: Blob literal should have an even number of hex characters" +msgstr "E973: Запис Blob повинен мати парну кількість шістнадцяткових символів" + msgid "E110: Missing ')'" msgstr "E110: Пропущено ')'" +msgid "E260: Missing name after ->" +msgstr "E260: Після -> бракує імені" + msgid "E695: Cannot index a Funcref" msgstr "E695: Функція не має індексації" @@ -716,10 +746,6 @@ msgid "E921: Invalid callback argument" msgstr "E921: Некоректний аргумент функції зворотнього виклику" #, c-format -msgid "E80: Error while writing: %s" -msgstr "E80: Помилка під час запису: %s" - -#, c-format msgid "E963: setting %s to value with wrong type" msgstr "E963: встановлення %s до значення з неправильним типом" @@ -759,6 +785,24 @@ msgstr "E5009: Некоректна $VIMRUNTIME: %s" msgid "E5009: Invalid 'runtimepath'" msgstr "E5009: Некоректний 'runtimepath'" +msgid "E977: Can only compare Blob with Blob" +msgstr "E977: Блоб можна порівняти тільки із блобом" + +msgid "E691: Can only compare List with List" +msgstr "E691: Список можна порівняти тільки зі списком" + +msgid "E692: Invalid operation for List" +msgstr "E692: Некоректна операція над списком" + +msgid "E735: Can only compare Dictionary with Dictionary" +msgstr "E735: Словник можна порівняти тільки із словником" + +msgid "E736: Invalid operation for Dictionary" +msgstr "E736: Некоректна операція над словником" + +msgid "E694: Invalid operation for Funcrefs" +msgstr "E694: Некоректна операція над функцією" + #, c-format msgid "E474: Expected comma before list item: %s" msgstr "E474: Очікується кома перед елементом списка: %s" @@ -1014,8 +1058,15 @@ msgid "E684: list index out of range: %<PRId64>" msgstr "E684: Індекс списку поза межами: %<PRId64>" #, c-format -msgid "E686: Argument of %s must be a List" -msgstr "E686: Аргумент у %s має бути списком" +msgid "E899: Argument of %s must be a List or Blob" +msgstr "E899: Аргумент у %s має бути списком чи блобом" + +msgid "E957: Invalid window number" +msgstr "E957: Некоректний номер вікна" + +#, c-format +msgid "E998: Reduce of an empty %s with no initial value" +msgstr "E998: Скорочення порожнього %s без початкового значення" #, c-format msgid "Error converting the call result: %s" @@ -1082,14 +1133,6 @@ msgid "E701: Invalid type for len()" msgstr "E701: Некоректний тип для len()" #, c-format -msgid "E798: ID is reserved for \":match\": %<PRId64>" -msgstr "E798: ID зарезервовано для \":match\": %<PRId64>" - -#, c-format -msgid "E798: ID is reserved for \"match\": %<PRId64>" -msgstr "E798: ID зарезервовано для \"match\": %<PRId64>" - -#, c-format msgid "msgpackdump() argument, index %i" msgstr "аргумент msgpackdump(), індекс %i" @@ -1127,14 +1170,6 @@ msgid "E927: Invalid action: '%s'" msgstr "E927: Неправильна дія: «%s»" #, c-format -msgid "E474: List item %d is either not a dictionary or an empty one" -msgstr "E474: Елемент списку %d або не словник або порожній" - -#, c-format -msgid "E474: List item %d is missing one of the required keys" -msgstr "E474: Елемент списку %d немає одного з обов’язкових ключів" - -#, c-format msgid "E962: Invalid action: '%s'" msgstr "E962: Неправильна дія: «%s»" @@ -1168,6 +1203,9 @@ msgstr "E935: неправильний номер групи співпадін msgid "Can only call this function in an unmodified buffer" msgstr "Цю функцію можна викликати тільки у незміненому буфері" +msgid "writefile() first argument must be a List or a Blob" +msgstr "перший аргумент writefile() має бути List або Blob" + #, c-format msgid "E5060: Unknown flag: %s" msgstr "E5060: Невідомий прапорець: %s" @@ -1228,6 +1266,9 @@ msgstr "E745: Очікується Number чи String, трапився List" msgid "E728: Expected a Number or a String, Dictionary found" msgstr "E728: Очікується Number чи String, трапився Dictionary" +msgid "E974: Expected a Number or a String, Blob found" +msgstr "E974: Очікується Number чи String, трапився Blob" + msgid "E5299: Expected a Number or a String, Boolean found" msgstr "E5299: Очікується Number чи String, трапився Boolean" @@ -1243,6 +1284,9 @@ msgstr "E728: Dictionary вжито як Number" msgid "E805: Using a Float as a Number" msgstr "E805: Float вжито як Number" +msgid "E974: Using a Blob as a Number" +msgstr "E974: Blob вжито як Number" + msgid "E685: using an invalid value as a Number" msgstr "E685: некоректне значення вжито як Number" @@ -1252,6 +1296,9 @@ msgstr "E730: List вжито як String" msgid "E731: using Dictionary as a String" msgstr "E731: Dictionary вжито як String" +msgid "E976: using Blob as a String" +msgstr "E976: Blob вжито як String" + msgid "E908: using an invalid value as a String" msgstr "E908: некоректне значення вжито як String" @@ -1273,6 +1320,9 @@ msgstr "E362: Використано логічне значення як Float" msgid "E907: Using a special value as a Float" msgstr "E907: Використано спеціальне значення як Float" +msgid "E975: Using a Blob as a Float" +msgstr "E975: Blob вжито як Float" + msgid "E808: Number or Float required" msgstr "E808: Треба вказати Number чи Float" @@ -1298,6 +1348,9 @@ msgstr "E125: Недозволений аргумент: %s" msgid "E853: Duplicate argument name: %s" msgstr "E853: Назва аргументу повторюється: %s" +msgid "E989: Non-default argument follows default argument" +msgstr "E989: Аргумент без домовленого значення після аргументу з домовленим значенням" + #, c-format msgid "E740: Too many arguments for function %s" msgstr "E740: Забагато аргументів для функції %s" @@ -1337,6 +1390,10 @@ msgid "E117: Unknown function: %s" msgstr "E117: Невідома функція: %s" #, c-format +msgid "E276: Cannot use function as a method: %s" +msgstr "E276: Не можна вжити функцію як метод: %s" + +#, c-format msgid "E933: Function was deleted: %s" msgstr "E933: Функцію було видалено: %s" @@ -1408,10 +1465,6 @@ msgstr "Не вдалося знищити функцію %s: Вона вико msgid "E133: :return not inside a function" msgstr "E133: :return поза межами функції" -#, c-format -msgid "E107: Missing parentheses: %s" -msgstr "E107: Пропущено дужки: %s" - msgid "tcp address must be host:port" msgstr "адреса tcp має бути вузол:порт" @@ -1448,13 +1501,6 @@ msgstr "> %d, шіст %08x, віс %o" msgid "E134: Cannot move a range of lines into itself" msgstr "E134: Неможливо перемістити діапазон рядків сам у себе" -msgid "1 line moved" -msgstr "Переміщено один рядок" - -#, c-format -msgid "%<PRId64> lines moved" -msgstr "Переміщено %<PRId64> рядки(ів)" - #, c-format msgid "E482: Can't create file %s" msgstr "E482: Не вдалося створити файл %s" @@ -1533,27 +1579,6 @@ msgstr "Замінити на %s (y/n/a/q/l/^E/^Y)?" msgid "(Interrupted) " msgstr "(Перервано) " -msgid "1 match" -msgstr "Один збіг" - -msgid "1 substitution" -msgstr "Одна заміна" - -#, c-format -msgid "%<PRId64> matches" -msgstr "%<PRId64> збіги(ів)" - -#, c-format -msgid "%<PRId64> substitutions" -msgstr "%<PRId64> замін(и)" - -msgid " on 1 line" -msgstr " в одному рядку" - -#, c-format -msgid " on %<PRId64> lines" -msgstr " в %<PRId64> рядках" - msgid "E147: Cannot do :global recursive with a range" msgstr "E147: :global не можна рекурсивно з діапазоном" @@ -1610,39 +1635,6 @@ msgstr "E150: Не є каталогом: %s" msgid "No old files" msgstr "Жодного старого файлу" -msgid "Entering Debug mode. Type \"cont\" to continue." -msgstr "Режим налагодження. Щоб продовжити введіть «cont»." - -#, c-format -msgid "line %<PRId64>: %s" -msgstr "рядок %<PRId64>: %s" - -#, c-format -msgid "cmd: %s" -msgstr "команда: %s" - -msgid "frame is zero" -msgstr "кадр нульовий" - -#, c-format -msgid "frame at highest level: %d" -msgstr "кадр на найвищому рівні: %d" - -#, c-format -msgid "Breakpoint in \"%s%s\" line %<PRId64>" -msgstr "Точка зупинки в «%s%s» рядок %<PRId64>" - -#, c-format -msgid "E161: Breakpoint not found: %s" -msgstr "E161: Точку зупинки не знайдено: %s" - -msgid "No breakpoints defined" -msgstr "Не визначено жодної точки зупинки" - -#, c-format -msgid "%3d %s %s line %<PRId64>" -msgstr "%3d %s %s рядок %<PRId64>" - msgid "E750: First use \":profile start {fname}\"" msgstr "E750: Спочатку зробіть «:profile start {файл}»" @@ -1683,10 +1675,6 @@ msgid "E666: compiler not supported: %s" msgstr "E666: Компілятор не підтримується: %s" #, c-format -msgid ":source error parsing command %s" -msgstr ":source помилка розбору команди %s" - -#, c-format msgid "Cannot source a directory: \"%s\"" msgstr "Не вдалося прочитати каталог: «%s»" @@ -1725,6 +1713,9 @@ msgstr "змінна оточення" msgid "error handler" msgstr "обробник помилки" +msgid "changed window size" +msgstr "змінено розмір вікна" + msgid "Lua" msgstr "Lua" @@ -1735,6 +1726,10 @@ msgstr "Клієнт API (канал «%<PRIu64>»)" msgid "anonymous :source" msgstr "анонімний :source" +#, c-format +msgid "anonymous :source (script id %d)" +msgstr "анонімний :source (ід. скрипта %d)" + msgid "W15: Warning: Wrong line separator, ^M may be missing" msgstr "W15: Застереження: Неправильний роздільник рядків, можливо, бракує ^M" @@ -1752,6 +1747,14 @@ msgstr "Мова (%s): «%s»" msgid "E197: Cannot set language to \"%s\"" msgstr "E197: Не вдалося встановити мову «%s»" +#, c-format +msgid "E184: No such user-defined command: %s" +msgstr "E184: Команду користувача не знайдено: %s" + +#, c-format +msgid "E1237: No such user-defined command in current buffer: %s" +msgstr "E1237: Немає такої команди користувача у цьому буфері: %s" + msgid "Entering Ex mode. Type \"visual\" to go to Normal mode." msgstr "Режим Ex. Для повернення до нормального режиму виконайте «visual»" @@ -1796,7 +1799,8 @@ msgstr "E494: Спробуйте w або w>>" msgid "" "INTERNAL: Cannot use EX_DFLALL with ADDR_NONE, ADDR_UNSIGNED or ADDR_QUICKFIX" msgstr "" -"ВНУТРІШНЄ: Не можна вживати EX_DFLALL з ADDR_NONE, ADDR_UNSIGNED чи ADDR_QUICKFIX" +"ВНУТРІШНЄ: Не можна вживати EX_DFLALL з ADDR_NONE, ADDR_UNSIGNED чи " +"ADDR_QUICKFIX" msgid "E943: Command table needs to be updated, run 'make'" msgstr "E943: Потрібно поновити таблицю команд, запустіть 'make'" @@ -1804,20 +1808,6 @@ msgstr "E943: Потрібно поновити таблицю команд, з msgid "E319: The command is not available in this version" msgstr "E319: Вибачте, цієї команди немає у цій версії" -msgid "1 more file to edit. Quit anyway?" -msgstr "Залишилося відредагувати ще один файл. Все одно вийти?" - -#, c-format -msgid "%d more files to edit. Quit anyway?" -msgstr "Ще є %d не редагованих файлів. Все одно вийти?" - -msgid "E173: 1 more file to edit" -msgstr "E173: Залишилося відредагувати ще один файл" - -#, c-format -msgid "E173: %<PRId64> more files to edit" -msgstr "E173: Залишилося %<PRId64> не редагованих файлів" - #, c-format msgid "E174: Command already exists: add ! to replace it: %s" msgstr "E174: Команда вже існує, ! щоб замінити її: %s" @@ -1854,6 +1844,9 @@ msgstr "E179: для -addr потрібний аргумент" msgid "E181: Invalid attribute: %s" msgstr "E181: Неправильний атрибут: %s" +msgid "E1208: -complete used without -nargs" +msgstr "E1208: -complete вжито без without -nargs" + msgid "E182: Invalid command name" msgstr "E182: Неправильна назва команди" @@ -1865,10 +1858,6 @@ msgstr "" "E841: Зарезервована назва, не можна використати для користувацької команди" #, c-format -msgid "E184: No such user-defined command: %s" -msgstr "E184: Команду користувача не знайдено: %s" - -#, c-format msgid "E180: Invalid address type value: %s" msgstr "E180: Неправильне значення типу адреси: %s" @@ -1949,7 +1938,7 @@ msgstr "E842: немає номера рядка, щоб використати msgid "E961: no line number to use for \"<sflnum>\"" msgstr "E961: немає номера рядка, щоб використати з «<sflnum>»" -#, c-format +#, no-c-format msgid "E499: Empty file name for '%' or '#', only works with \":p:h\"" msgstr "E499: Назва файлу для '%' чи '#' порожня, працює лише з «:p:h»" @@ -2106,6 +2095,9 @@ msgstr " тип файлу\n" msgid "'history' option is zero" msgstr "Опція 'history' порожня" +msgid "[Command Line]" +msgstr "[Рядок Команд]" + msgid "E199: Active window or buffer deleted" msgstr "E199: Активне вікно або буфер було знищено" @@ -2228,6 +2220,10 @@ msgstr "Не придатний для запису" msgid "is read-only (add ! to override)" msgstr "лише для читання (! щоб не зважати)" +#, c-format +msgid "E303: Unable to create directory \"%s\" for backup file: %s" +msgstr "E303: Не вдалося створити каталог «%s» для резервного файлу: %s" + msgid "E506: Can't write to backup file (add ! to override)" msgstr "E506: Не вдалося записати резервний файл (! щоб не зважати)" @@ -2322,20 +2318,6 @@ msgstr "[формат unix]" msgid "[unix]" msgstr "[unix]" -msgid "1 line, " -msgstr "один рядок, " - -#, c-format -msgid "%<PRId64> lines, " -msgstr "%<PRId64> рядків, " - -msgid "1 character" -msgstr "один символ" - -#, c-format -msgid "%<PRId64> characters" -msgstr "%<PRId64> символів" - msgid "[Incomplete last line]" msgstr "[Неповний останній рядок]" @@ -2399,10 +2381,12 @@ msgstr "Застереження" msgid "" "&OK\n" -"&Load File" +"&Load File\n" +"Load File &and Options" msgstr "" -"&O:Гаразд\n" -"&L:Завантажити" +"[&O]Гаразд\n" +"[&L]Завантажити файл\n" +"[&a]Завантажити файл і опції" #, c-format msgid "E462: Could not prepare for reloading \"%s\"" @@ -2550,6 +2534,9 @@ msgstr "E476: Некоректна команда" msgid "E17: \"%s\" is a directory" msgstr "E17: «%s» — це каталог" +msgid "E756: Spell checking is not possible" +msgstr "E756: Перевірка орфографії неможлива" + msgid "E900: Invalid channel id" msgstr "E900: Некоректний канал" @@ -2718,7 +2705,14 @@ msgid "E928: String required" msgstr "E928: Потрібен String" msgid "E715: Dictionary required" -msgstr "E715: Потрібен словник" +msgstr "E715: Потрібен Dictionary" + +#, c-format +msgid "E979: Blob index out of range: %<PRId64>" +msgstr "E979: Індекс Blob поза межами: %<PRId64>" + +msgid "E978: Invalid operation for Blob" +msgstr "E978: Некоректна операція над Blob" #, c-format msgid "E118: Too many arguments for function: %s" @@ -2731,10 +2725,17 @@ msgstr "E716: Немає такого ключа у словнику: «%s»" msgid "E714: List required" msgstr "E714: Потрібен список" +msgid "E897: List or Blob required" +msgstr "E897: Потрібен List або Blob" + #, c-format msgid "E712: Argument of %s must be a List or Dictionary" msgstr "E712: Аргумент у %s має бути списком чи словником" +#, c-format +msgid "E896: Argument of %s must be a List, Dictionary or Blob" +msgstr "E896: Аргумент у %s має бути List, Dictionary чи Blob" + msgid "E47: Error while reading errorfile" msgstr "E47: Помилка читання файлу помилок" @@ -2802,6 +2803,10 @@ msgstr "E939: Потрібна додана кількість" msgid "E81: Using <SID> not in a script context" msgstr "E81: <SID> використовується не в контексті скрипту" +#, c-format +msgid "E107: Missing parentheses: %s" +msgstr "E107: Пропущено дужки: %s" + msgid "E363: pattern uses more memory than 'maxmempattern'" msgstr "E363: Зразок використовує більше, ніж 'maxmempattern', пам'яті" @@ -2832,6 +2837,13 @@ msgstr "E919: Каталог не знайдено у '%s': «%s»" msgid "E952: Autocommand caused recursive behavior" msgstr "E952: Автокоманди призвели до рекурсії" +msgid "E813: Cannot close autocmd window" +msgstr "E813: Не вдалося закрити вікно autocmd" + +#, c-format +msgid "E686: Argument of %s must be a List" +msgstr "E686: Аргумент у %s має бути списком" + msgid "E519: Option not supported" msgstr "E519: Опція не підтримується" @@ -2869,6 +2881,21 @@ msgstr "E5601: Не вдалося закрити вікно, залишилос msgid "E5602: Cannot exchange or rotate float" msgstr "E5602: Не можна обміняти чи покрутити плавуче вікно" +msgid "E1142: Non-empty string required" +msgstr "E1142: Потрібен непорожній String" + +msgid "E1155: Cannot define autocommands for ALL events" +msgstr "E1155: Не можу визначити автокоманди для УСІХ подій" + +msgid "E1240: Resulting text too long" +msgstr "E1240: Текст результату задовгий" + +msgid "E1247: Line number out of range" +msgstr "E1247: Номер рядка вийшов поза межами" + +msgid "E1249: Highlight group name too long" +msgstr "E1249: Назва групи підсвічування задовга" + msgid "search hit TOP, continuing at BOTTOM" msgstr "Пошук дійшов до ПОЧАТКУ, продовжується з КІНЦЯ" @@ -2975,6 +3002,60 @@ msgstr "Завдання друку відіслано." msgid "E424: Too many different highlighting attributes in use" msgstr "E424: Використано забагато різних атрибутів кольору" +#, c-format +msgid "E411: highlight group not found: %s" +msgstr "E411: Групу підсвічування не знайдено: %s" + +#, c-format +msgid "E412: Not enough arguments: \":highlight link %s\"" +msgstr "E412: Недостатньо аргументів: «:highlight link %s»" + +#, c-format +msgid "E413: Too many arguments: \":highlight link %s\"" +msgstr "E413: Забагато аргументів: «:highlight link %s»" + +msgid "E414: group has settings, highlight link ignored" +msgstr "E414: Грума має settings, highlight link проігноровано" + +#, c-format +msgid "E415: unexpected equal sign: %s" +msgstr "E415: Несподіваний знак рівності: %s" + +#, c-format +msgid "E416: missing equal sign: %s" +msgstr "E416: Пропущено знак рівності: %s" + +#, c-format +msgid "E417: missing argument: %s" +msgstr "E417: Пропущено аргумент: %s" + +#, c-format +msgid "E418: Illegal value: %s" +msgstr "E418: Неправильне значення: %s" + +msgid "E419: FG color unknown" +msgstr "E419: Невідомий колір тексту" + +msgid "E420: BG color unknown" +msgstr "E420: Невідомий колір фону" + +#, c-format +msgid "E421: Color name or number not recognized: %s" +msgstr "E421: Нерозпізнана назва або номер кольору: %s" + +#, c-format +msgid "E423: Illegal argument: %s" +msgstr "E423: Неправильний аргумент: %s" + +msgid "E669: Unprintable character in group name" +msgstr "E669: Недруковний символ у назві групи" + +msgid "W18: Invalid character in group name" +msgstr "W18: Некоректний символ у назві групи" + +msgid "E849: Too many highlight and syntax groups" +msgstr "E849: Забагато груп підсвічування і синтаксису" + msgid "Add a new database" msgstr "Додати нову базу даних" @@ -3129,6 +3210,12 @@ msgstr "Жодного з'єднання з cscope\n" msgid " # pid database name prepend path\n" msgstr " # pid назва бази даних шлях\n" +msgid "Type number and <Enter> or click with the mouse (q or empty cancels): " +msgstr "Наберіть число й <Enter> чи клацніть мишкою (q чи порожнє скасовує): " + +msgid "Type number and <Enter> (q or empty cancels): " +msgstr "Наберіть число й <Enter> (q чи порожнє скасовує): " + #, c-format msgid "E1502: Lua failed to grow stack to %i" msgstr "E1502: Lua не вдалося збільшити стек до %i" @@ -3151,20 +3238,11 @@ msgstr "E5102: Lua не вдалося збільшити стек до %i" msgid "Error executing vim.schedule lua callback: %.*s" msgstr "Помилка виконання обробника lua vim.schedule: %.*s" -#, c-format -msgid "E5106: Error while creating shared module: %.*s" -msgstr "E5106: Помилка створення розділюваного модуля: %.*s" - -#, c-format -msgid "E5106: Error while creating inspect module: %.*s" -msgstr "E5106: Помилка створення модуля inspect: %.*s" - -#, c-format -msgid "E5106: Error while creating vim module: %.*s" -msgstr "E5106: Помилка створення модуля vim: %.*s" +msgid "E970: Failed to initialize lua interpreter\n" +msgstr "E970: Не вдалося ініціалізувати інтерпретатор lua\n" -msgid "E970: Failed to initialize lua interpreter" -msgstr "E970: Не вдалося ініціалізувати інтерпретатор lua" +msgid "E970: Failed to initialize builtin lua modules\n" +msgstr "E970: Не вдалося ініціалізувати інтерпретатор lua\n" #, c-format msgid "E5114: Error while converting print argument #%i: %.*s" @@ -3179,6 +3257,10 @@ msgid "E5116: Error while calling debug string: %.*s" msgstr "E5116: Помилка виклику налагодження: %.*s" #, c-format +msgid "E5108: Error executing Lua function: %.*s" +msgstr "E5108: Помилка виконання функції lua: %.*s" + +#, c-format msgid "E5107: Error loading lua %.*s" msgstr "E5107: Помилка завантаження lua %.*s" @@ -3214,8 +3296,16 @@ msgid "E5113: Error while calling lua chunk: %.*s" msgstr "E5113: Помилка виклику шматку lua: %.*s" #, c-format -msgid "Error executing vim.log_keystroke lua callback: %.*s" -msgstr "Помилка виконання обробника lua vim.log_keystroke: %.*s" +msgid "Error executing vim._expand_pat: %.*s" +msgstr "Помилка виконання vim._expand_pat: %.*s" + +#, c-format +msgid "Error executing vim.on_key Lua callback: %.*s" +msgstr "Помилка виконання обробника Lua vim.on_key: %.*s" + +#, c-format +msgid "Error executing Lua callback: %.*s" +msgstr "Помилка виконання обробника Lua: %.*s" msgid "Argument missing after" msgstr "Пропущено аргумент після" @@ -3254,8 +3344,8 @@ msgid "pre-vimrc command line" msgstr "команди перед vimrc" #, c-format -msgid "Conflicting configs: \"%s\" \"%s\"" -msgstr "Суперечливі конфігурації: «%s» «%s»" +msgid "E5422: Conflicting configs: \"%s\" \"%s\"" +msgstr "E5422: Суперечливі конфігурації: «%s» «%s»" #, c-format msgid "E282: Cannot read from \"%s\"" @@ -3387,6 +3477,12 @@ msgstr " --listen <адреса> Обслуговувати RPC API за ц msgid " --noplugin Don't load plugins\n" msgstr " --noplugin Не завантажувати доповнення\n" +msgid " --remote[-subcommand] Execute commands remotely on a server\n" +msgstr " --remote[-subcommand] Виконати команди на віддаленому сервері\n" + +msgid " --server <address> Specify RPC server to send commands to\n" +msgstr " --server <адреса> Задати сервер RPC, куди слати команди\n" + msgid " --startuptime <file> Write startup timing messages to <file>\n" msgstr " --startuptime <файл> Записати профіль запуску до <файлу>\n" @@ -3425,6 +3521,46 @@ msgstr "" "\n" "змінити ряд. стовп. текст" +#, c-format +msgid "E799: Invalid ID: %<PRId64> (must be greater than or equal to 1)" +msgstr "E799: Неправильний ID: %<PRId64> (має бути не меншим 1)" + +#, c-format +msgid "E801: ID already taken: %<PRId64>" +msgstr "E801: ID вже зайнято: %<PRId64>" + +#, c-format +msgid "E5030: Empty list at position %d" +msgstr "E5030: Порожній список у позиції %d" + +#, c-format +msgid "E5031: List or number required at position %d" +msgstr "E5031: Очікується список чи число у позиції %d" + +#, c-format +msgid "E802: Invalid ID: %<PRId64> (must be greater than or equal to 1)" +msgstr "E802: Неправильний ID: %<PRId64> (має бути не меншим 1)" + +#, c-format +msgid "E803: ID not found: %<PRId64>" +msgstr "E803: ID не знайдено: %<PRId64>" + +#, c-format +msgid "E474: List item %d is either not a dictionary or an empty one" +msgstr "E474: Елемент списку %d або не словник або порожній" + +#, c-format +msgid "E474: List item %d is missing one of the required keys" +msgstr "E474: Елемент списку %d немає одного з обов’язкових ключів" + +#, c-format +msgid "E798: ID is reserved for \":match\": %<PRId64>" +msgstr "E798: ID зарезервовано для \":match\": %<PRId64>" + +#, c-format +msgid "E798: ID is reserved for \"match\": %<PRId64>" +msgstr "E798: ID зарезервовано для \"match\": %<PRId64>" + msgid "E293: block was not locked" msgstr "E293: Блок не було зафіксовано" @@ -3910,6 +4046,9 @@ msgstr "Перервано: " msgid "Press ENTER or type command to continue" msgstr "Натисніть ENTER або введіть команду для продовження" +msgid " (Interrupted)" +msgstr " (Перервано)" + #, c-format msgid "%s line %<PRId64>" msgstr "%s рядок %<PRId64>" @@ -3952,38 +4091,9 @@ msgstr "" "&D:Жодного\n" "&C:Скасувати" -msgid "Type number and <Enter> or click with mouse (empty cancels): " -msgstr "Наберіть число й <Enter> чи клацніть мишкою (порожнє скасовує): " - -msgid "Type number and <Enter> (empty cancels): " -msgstr "Наберіть число й <Enter> (порожнє скасовує): " - -msgid "1 more line" -msgstr "додано один рядок" - -msgid "1 line less" -msgstr "знищено один рядок" - -#, c-format -msgid "%<PRId64> more lines" -msgstr "додано рядків: %<PRId64>" - -#, c-format -msgid "%<PRId64> fewer lines" -msgstr "знищено рядків: %<PRId64>" - -msgid " (Interrupted)" -msgstr " (Перервано)" - -msgid "Beep!" -msgstr "Дзень!" - msgid "E349: No identifier under cursor" msgstr "E349: Немає ідентифікатора над курсором" -msgid "E774: 'operatorfunc' is empty" -msgstr "E774: 'operatorfunc' порожня" - msgid "E348: No string under cursor" msgstr "E348: Немає рядка на курсорі" @@ -4007,63 +4117,17 @@ msgid "Type :qa and press <Enter> to exit Nvim" msgstr "Введіть :qa і натисність <Enter> щоб вийти з Nvim" #, c-format -msgid "1 line %sed 1 time" -msgstr "Один рядок %s-но" - -#, c-format -msgid "1 line %sed %d times" -msgstr "Один рядок %s-но %d разів" - -#, c-format -msgid "%<PRId64> lines %sed 1 time" -msgstr "%<PRId64> рядків %s-но" - -#, c-format -msgid "%<PRId64> lines %sed %d times" -msgstr "%<PRId64> рядків %s-но %d разів" - -#, c-format msgid "%<PRId64> lines to indent... " msgstr "Залишилося вирівняти %<PRId64> рядків..." -msgid "1 line indented " -msgstr "Вирівняно один рядок" - -#, c-format -msgid "%<PRId64> lines indented " -msgstr "Вирівняно рядків: %<PRId64>" - msgid "E748: No previously used register" msgstr "E748: Регістри перед цим не вживались" -msgid "1 line changed" -msgstr "Один рядок змінено" - -#, c-format -msgid "%<PRId64> lines changed" -msgstr "Змінено рядків: %<PRId64>" - #, c-format msgid " into \"%c" msgstr " у \"%c" #, c-format -msgid "block of 1 line yanked%s" -msgstr "Запам'ятав блок з одного рядка%s" - -#, c-format -msgid "1 line yanked%s" -msgstr "Запам'ятав один рядок%s" - -#, c-format -msgid "block of %<PRId64> lines yanked%s" -msgstr "Запам'ятав блок із %<PRId64> рядків%s" - -#, c-format -msgid "%<PRId64> lines yanked%s" -msgstr "Запам'ятав рядків: %<PRId64>%s" - -#, c-format msgid "E353: Nothing in register %s" msgstr "E353: У регістрі %s нічого немає" @@ -4120,6 +4184,9 @@ msgstr "" msgid "(+%<PRId64> for BOM)" msgstr "(+%<PRId64> для BOM)" +msgid "E774: 'operatorfunc' is empty" +msgstr "E774: 'operatorfunc' порожня" + msgid "E518: Unknown option" msgstr "E518: Невідома опція" @@ -4168,8 +4235,8 @@ msgstr "E527: Бракує коми" msgid "E528: Must specify a ' value" msgstr "E528: Потрібно вказати значення '" -msgid "E595: contains unprintable or wide character" -msgstr "E595: Містить недруковні або розширені символи" +msgid "E595: 'showbreak' contains unprintable or wide character" +msgstr "E595: 'showbreak' містить недруковні або розширені символи" #, c-format msgid "E535: Illegal character after <%c>" @@ -4399,67 +4466,18 @@ msgstr "E70: %s%%[] порожній" msgid "E956: Cannot use pattern recursively" msgstr "E956: Не можна рекурсивно використати шаблон" -msgid "E65: Illegal back reference" -msgstr "E65: Некоректне зворотнє посилання" - -msgid "E339: Pattern too long" -msgstr "E339: Зразок занадто довгий" - -msgid "E50: Too many \\z(" -msgstr "E50: Забагато \\z(" - #, c-format -msgid "E51: Too many %s(" -msgstr "E51: Забагато %s(" - -msgid "E52: Unmatched \\z(" -msgstr "E52: Немає пари \\z(" +msgid "E1204: No Number allowed after .: '\\%%%c'" +msgstr "E1204: Number не можна після .: '\\%%%c'" #, c-format -msgid "E59: invalid character after %s@" -msgstr "E59: Недозволений символ після %s@" - -#, c-format -msgid "E60: Too many complex %s{...}s" -msgstr "E60: Забагато складних %s{...}" - -#, c-format -msgid "E61: Nested %s*" -msgstr "E61: Вкладені %s*" - -#, c-format -msgid "E62: Nested %s%c" -msgstr "E62: Вкладені %s%c" - -msgid "E63: invalid use of \\_" -msgstr "E63: Некоректно вжито \\_" - -#, c-format -msgid "E64: %s%c follows nothing" -msgstr "E64: Після %s%c нічого немає" - -msgid "E68: Invalid character after \\z" -msgstr "E68: Неправильний символ після \\z" - -#, c-format -msgid "E678: Invalid character after %s%%[dxouU]" -msgstr "E678: Недозволений символ після %s%%[dxouU]" - -#, c-format -msgid "E71: Invalid character after %s%%" -msgstr "E71: Недозволений символ після %s%%" +msgid "E554: Syntax error in %s{...}" +msgstr "E554: Синтаксична помилка в %s{...}" #, c-format msgid "E888: (NFA regexp) cannot repeat %s" msgstr "E888: (NFA regexp) неможливо повторити %s" -#, c-format -msgid "E554: Syntax error in %s{...}" -msgstr "E554: Синтаксична помилка в %s{...}" - -msgid "External submatches:\n" -msgstr "Зовнішні під-збіги:\n" - msgid "" "E864: \\%#= can only be followed by 0, 1, or 2. The automatic engine will be " "used " @@ -4482,6 +4500,14 @@ msgstr "Пошук «%s»" msgid "not found in '%s': \"%s\"" msgstr "не знайдено в '%s': «%s»" +#, c-format +msgid "Searching for \"%s\" in runtime path" +msgstr "Пошук «%s» в шляху виконання" + +#, c-format +msgid "not found in runtime path: \"%s\"" +msgstr "не знайдено в шляху виконання: «%s»" + msgid " TERMINAL" msgstr " ТЕРМІНАЛ" @@ -4850,9 +4876,6 @@ msgstr " (не підтримується)" msgid "E759: Format error in spell file" msgstr "E759: Помилка формату у файлі орфографії" -msgid "E756: Spell checking is not enabled" -msgstr "E756: Перевірка орфографії не дозволена" - #, c-format msgid "Warning: Cannot find word list \"%s.%s.spl\" or \"%s.ascii.spl\"" msgstr "" @@ -5193,6 +5216,9 @@ msgstr "E765: 'spellfile' не містить %<PRId64> елементів" msgid "Word '%.*s' removed from %s" msgstr "Слово '%.*s' знищено з %s" +msgid "Seek error in spellfile" +msgstr "Помилка зміни позиції у файлі орфографії" + #, c-format msgid "Word '%.*s' added to %s" msgstr "Слово '%.*s' додано до %s" @@ -5222,36 +5248,6 @@ msgstr "Для буфера не визначено елементів синт msgid "'redrawtime' exceeded, syntax highlighting disabled" msgstr "'redrawtime' перевищено, підсвічування синтаксису вимкнено" -msgid "syntax conceal on" -msgstr "синтаксичне приховування увімк" - -msgid "syntax conceal off" -msgstr "синтаксичне приховування вимк" - -msgid "syntax case ignore" -msgstr "синтаксис ігнорувати регістр" - -msgid "syntax case match" -msgstr "синтаксис дотримуватися регістру" - -msgid "syntax foldlevel start" -msgstr "рівень згортки синтаксису початок" - -msgid "syntax foldlevel minimum" -msgstr "рівень згортки синтаксису мінімум" - -msgid "syntax spell toplevel" -msgstr "синтаксис перевіряти всюди" - -msgid "syntax spell notoplevel" -msgstr "синтаксис не перевіряти" - -msgid "syntax spell default" -msgstr "синтаксис початково" - -msgid "syntax iskeyword " -msgstr "синтаксис iskeyword " - msgid "syntax iskeyword not set" msgstr "не встановлено синтаксис iskeyword" @@ -5400,63 +5396,6 @@ msgid "" msgstr "" " ВСЬОГО К-ТЬ СПІВП. НАЙПОВІЛ. СЕРЕДН. НАЗВА ШАБЛОН" -msgid "E679: recursive loop loading syncolor.vim" -msgstr "E679: Рекурсивний цикл читання syncolor.vim" - -#, c-format -msgid "E411: highlight group not found: %s" -msgstr "E411: Групу підсвічування не знайдено: %s" - -#, c-format -msgid "E412: Not enough arguments: \":highlight link %s\"" -msgstr "E412: Недостатньо аргументів: «:highlight link %s»" - -#, c-format -msgid "E413: Too many arguments: \":highlight link %s\"" -msgstr "E413: Забагато аргументів: «:highlight link %s»" - -msgid "E414: group has settings, highlight link ignored" -msgstr "E414: Грума має settings, highlight link проігноровано" - -#, c-format -msgid "E415: unexpected equal sign: %s" -msgstr "E415: Несподіваний знак рівності: %s" - -#, c-format -msgid "E416: missing equal sign: %s" -msgstr "E416: Пропущено знак рівності: %s" - -#, c-format -msgid "E417: missing argument: %s" -msgstr "E417: Пропущено аргумент: %s" - -#, c-format -msgid "E418: Illegal value: %s" -msgstr "E418: Неправильне значення: %s" - -msgid "E419: FG color unknown" -msgstr "E419: Невідомий колір тексту" - -msgid "E420: BG color unknown" -msgstr "E420: Невідомий колір фону" - -#, c-format -msgid "E421: Color name or number not recognized: %s" -msgstr "E421: Нерозпізнана назва або номер кольору: %s" - -#, c-format -msgid "E423: Illegal argument: %s" -msgstr "E423: Неправильний аргумент: %s" - -msgid "E669: Unprintable character in group name" -msgstr "E669: Недруковний символ у назві групи" - -msgid "W18: Invalid character in group name" -msgstr "W18: Некоректний символ у назві групи" - -msgid "E849: Too many highlight and syntax groups" -msgstr "E849: Забагато груп підсвічування і синтаксису" - msgid "E555: at bottom of tag stack" msgstr "E555: Кінець стеку міток" @@ -5542,6 +5481,9 @@ msgstr "E435: Не вдалося знайти мітку, тільки прип msgid "Duplicate field name: %s" msgstr "Назва поля повторюється: %s" +msgid "Beep!" +msgstr "Дзень!" + msgid "E881: Line count changed unexpectedly" msgstr "E881: Кількість рядків несподівано змінилася" @@ -5911,9 +5853,6 @@ msgstr "E443: Не вдалося перемістити вікно, заваж msgid "E444: Cannot close last window" msgstr "E444: Не вдалося закрити останнє вікно" -msgid "E813: Cannot close autocmd window" -msgstr "E813: Не вдалося закрити вікно autocmd" - msgid "E814: Cannot close window, only autocmd window would remain" msgstr "E814: Не вдалося закрити вікно, залишилося б тільки вікно autocmd" @@ -5923,26 +5862,4 @@ msgstr "E445: У іншому вікні є зміни" msgid "E446: No file name under cursor" msgstr "E446: Немає назви файлу над курсором" -#, c-format -msgid "E799: Invalid ID: %<PRId64> (must be greater than or equal to 1)" -msgstr "E799: Неправильний ID: %<PRId64> (має бути не меншим 1)" - -#, c-format -msgid "E801: ID already taken: %<PRId64>" -msgstr "E801: ID вже зайнято: %<PRId64>" - -#, c-format -msgid "E5030: Empty list at position %d" -msgstr "E5030: Порожній список у позиції %d" -#, c-format -msgid "E5031: List or number required at position %d" -msgstr "E5031: Очікується список чи число у позиції %d" - -#, c-format -msgid "E802: Invalid ID: %<PRId64> (must be greater than or equal to 1)" -msgstr "E802: Неправильний ID: %<PRId64> (має бути не меншим 1)" - -#, c-format -msgid "E803: ID not found: %<PRId64>" -msgstr "E803: ID не знайдено: %<PRId64>" diff --git a/src/nvim/popupmnu.c b/src/nvim/popupmnu.c index 606c03f838..e354a589a5 100644 --- a/src/nvim/popupmnu.c +++ b/src/nvim/popupmnu.c @@ -289,9 +289,8 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, i if (pum_rl) { pum_width = pum_col - pum_scrollbar + 1; } else { - assert(Columns - pum_col - pum_scrollbar >= INT_MIN - && Columns - pum_col - pum_scrollbar <= INT_MAX); - pum_width = (int)(Columns - pum_col - pum_scrollbar); + assert(Columns - pum_col - pum_scrollbar >= 0); + pum_width = Columns - pum_col - pum_scrollbar; } if ((pum_width > max_width + pum_kind_width + pum_extra_width + 1) @@ -352,12 +351,11 @@ 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 @@ -367,9 +365,8 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, i if (pum_rl) { pum_col = max_width - 1; } else { - assert(Columns - max_width >= INT_MIN - && Columns - max_width <= INT_MAX); - pum_col = (int)(Columns - max_width); + assert(Columns - max_width >= 0); + pum_col = Columns - max_width; } pum_width = max_width - pum_scrollbar; } @@ -386,7 +383,7 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, i void pum_redraw(void) { int row = 0; - int col; + int grid_col; int attr_norm = win_hl_attr(curwin, HLF_PNI); int attr_select = win_hl_attr(curwin, HLF_PSI); int attr_scroll = win_hl_attr(curwin, HLF_PSB); @@ -479,7 +476,7 @@ void pum_redraw(void) // Display each entry, use two spaces for a Tab. // Do this 3 times: For the main text, kind and extra info - col = col_off; + grid_col = col_off; totwidth = 0; for (round = 1; round <= 3; ++round) { @@ -537,24 +534,15 @@ void pum_redraw(void) } } grid_puts_len(&pum_grid, rt, (int)STRLEN(rt), row, - col - size + 1, attr); + grid_col - size + 1, attr); xfree(rt_start); xfree(st); - col -= width; + grid_col -= width; } else { - int size = (int)STRLEN(st); - int cells = (int)mb_string2cells(st); - - // only draw the text that fits - while (size > 0 && col + cells > pum_width + pum_col) { - size--; - size -= utf_head_off(st, st + size); - cells -= utf_ptr2cells(st + size); - } - - grid_puts_len(&pum_grid, st, size, row, col, attr); + // use grid_puts_len() to truncate the text + grid_puts(&pum_grid, st, row, grid_col, attr); xfree(st); - col += width; + grid_col += width; } if (*p != TAB) { @@ -563,12 +551,12 @@ void pum_redraw(void) // Display two spaces for a Tab. if (pum_rl) { - grid_puts_len(&pum_grid, (char_u *)" ", 2, row, col - 1, + grid_puts_len(&pum_grid, (char_u *)" ", 2, row, grid_col - 1, attr); - col -= 2; + grid_col -= 2; } else { - grid_puts_len(&pum_grid, (char_u *)" ", 2, row, col, attr); - col += 2; + grid_puts_len(&pum_grid, (char_u *)" ", 2, row, grid_col, attr); + grid_col += 2; } totwidth += 2; // start text at next char @@ -599,21 +587,21 @@ void pum_redraw(void) if (pum_rl) { grid_fill(&pum_grid, row, row + 1, col_off - pum_base_width - n + 1, - col + 1, ' ', ' ', attr); - col = col_off - pum_base_width - n + 1; + grid_col + 1, ' ', ' ', attr); + grid_col = col_off - pum_base_width - n + 1; } else { - grid_fill(&pum_grid, row, row + 1, col, + grid_fill(&pum_grid, row, row + 1, grid_col, col_off + pum_base_width + n, ' ', ' ', attr); - col = col_off + pum_base_width + n; + grid_col = col_off + pum_base_width + n; } totwidth = pum_base_width + n; } if (pum_rl) { - grid_fill(&pum_grid, row, row + 1, col_off - pum_width + 1, col + 1, + grid_fill(&pum_grid, row, row + 1, col_off - pum_width + 1, grid_col + 1, ' ', ' ', attr); } else { - grid_fill(&pum_grid, row, row + 1, col, col_off + pum_width, ' ', ' ', + grid_fill(&pum_grid, row, row + 1, grid_col, col_off + pum_width, ' ', ' ', attr); } diff --git a/src/nvim/pos.h b/src/nvim/pos.h index d17e27906e..c60b008696 100644 --- a/src/nvim/pos.h +++ b/src/nvim/pos.h @@ -1,10 +1,8 @@ #ifndef NVIM_POS_H #define NVIM_POS_H -// for INT_MAX, LONG_MAX et al. -#include <limits.h> - -typedef long linenr_T; // line number type +/// Line number type +typedef long linenr_T; /// Format used to print values which have linenr_T type #define PRIdLINENR "ld" @@ -15,29 +13,29 @@ typedef int colnr_T; /// Maximal (invalid) line number enum { MAXLNUM = 0x7fffffff, }; + /// Maximal column number -enum { MAXCOL = INT_MAX, }; -// Minimum line number +/// 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 + +/// Minimum column number enum { MINCOL = 1, }; -/* - * position in file or buffer - */ +/// position in file or buffer typedef struct { - linenr_T lnum; // line number - colnr_T col; // column number + linenr_T lnum; ///< line number + colnr_T col; ///< column number colnr_T coladd; } pos_T; - -/* - * Same, but without coladd. - */ +/// position in file or buffer, but without coladd typedef struct { - linenr_T lnum; // line number - colnr_T col; // column number + linenr_T lnum; ///< line number + colnr_T col; ///< column number } lpos_T; #endif // NVIM_POS_H diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index 6a192d148f..4ad5e40fee 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -22,12 +22,12 @@ #include "nvim/ex_getln.h" #include "nvim/fileio.h" #include "nvim/fold.h" +#include "nvim/highlight_group.h" #include "nvim/mark.h" #include "nvim/mbyte.h" #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/message.h" -#include "nvim/misc1.h" #include "nvim/move.h" #include "nvim/normal.h" #include "nvim/option.h" @@ -40,7 +40,6 @@ #include "nvim/screen.h" #include "nvim/search.h" #include "nvim/strings.h" -#include "nvim/syntax.h" #include "nvim/ui.h" #include "nvim/vim.h" #include "nvim/window.h" @@ -76,6 +75,7 @@ struct qfline_S { // There is a stack of error lists. #define LISTCOUNT 10 #define INVALID_QFIDX (-1) +#define INVALID_QFBUFNR (0) /// Quickfix list type. typedef enum @@ -127,12 +127,13 @@ struct qf_info_S { int qf_curlist; // current error list qf_list_T qf_lists[LISTCOUNT]; qfltype_T qfl_type; // type of list + int qf_bufnr; // quickfix window buffer number }; 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; @@ -210,6 +211,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 @@ -217,28 +229,28 @@ typedef struct { static char_u *e_no_more_items = (char_u *)N_("E553: No more items"); // Quickfix window check helper macro -#define IS_QF_WINDOW(wp) (bt_quickfix(wp->w_buffer) && wp->w_llist_ref == NULL) +#define IS_QF_WINDOW(wp) (bt_quickfix((wp)->w_buffer) && (wp)->w_llist_ref == NULL) // Location list window check helper macro -#define IS_LL_WINDOW(wp) (bt_quickfix(wp->w_buffer) && wp->w_llist_ref != NULL) +#define IS_LL_WINDOW(wp) (bt_quickfix((wp)->w_buffer) && (wp)->w_llist_ref != NULL) // Quickfix and location list stack check helper macros -#define IS_QF_STACK(qi) (qi->qfl_type == QFLT_QUICKFIX) -#define IS_LL_STACK(qi) (qi->qfl_type == QFLT_LOCATION) -#define IS_QF_LIST(qfl) (qfl->qfl_type == QFLT_QUICKFIX) -#define IS_LL_LIST(qfl) (qfl->qfl_type == QFLT_LOCATION) +#define IS_QF_STACK(qi) ((qi)->qfl_type == QFLT_QUICKFIX) +#define IS_LL_STACK(qi) ((qi)->qfl_type == QFLT_LOCATION) +#define IS_QF_LIST(qfl) ((qfl)->qfl_type == QFLT_QUICKFIX) +#define IS_LL_LIST(qfl) ((qfl)->qfl_type == QFLT_LOCATION) // // Return location list for window 'wp' // For location list window, return the referenced location list // -#define GET_LOC_LIST(wp) (IS_LL_WINDOW(wp) ? wp->w_llist_ref : wp->w_llist) +#define GET_LOC_LIST(wp) (IS_LL_WINDOW(wp) ? (wp)->w_llist_ref : (wp)->w_llist) // Macro to loop through all the items in a quickfix list // Quickfix item index starts from 1, so i below starts at 1 #define FOR_ALL_QFL_ITEMS(qfl, qfp, i) \ - for (i = 1, qfp = qfl->qf_start; /* NOLINT(readability/braces) */ \ - !got_int && i <= qfl->qf_count && qfp != NULL; \ - i++, qfp = qfp->qf_next) + for ((i) = 1, (qfp) = (qfl)->qf_start; /* NOLINT(readability/braces) */ \ + !got_int && (i) <= (qfl)->qf_count && (qfp) != NULL; \ + (i)++, (qfp) = (qfp)->qf_next) // Looking up a buffer can be slow if there are many. Remember the last one @@ -322,22 +334,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. @@ -353,9 +370,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; @@ -658,7 +675,8 @@ static int qf_get_next_str_line(qfstate_T *state) state->linebuf = IObuff; state->linelen = len; } - STRLCPY(state->linebuf, p_str, state->linelen + 1); + memcpy(state->linebuf, p_str, state->linelen); + state->linebuf[state->linelen] = '\0'; // Increment using len in order to discard the rest of the line if it // exceeds LINE_MAXLEN. @@ -1277,7 +1295,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 +1306,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 +1328,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 +1471,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 +1517,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 +1668,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 @@ -1656,6 +1705,28 @@ static void locstack_queue_delreq(qf_info_T *qi) qf_delq_head = q; } +/// Return the global quickfix stack window buffer number. +int qf_stack_get_bufnr(void) +{ + return ql_info.qf_bufnr; +} + +/// Wipe the quickfix window buffer (if present) for the specified +/// quickfix/location list. +static void wipe_qf_buffer(qf_info_T *qi) + FUNC_ATTR_NONNULL_ALL +{ + if (qi->qf_bufnr != INVALID_QFBUFNR) { + buf_T *const qfbuf = buflist_findnr(qi->qf_bufnr); + if (qfbuf != NULL && qfbuf->b_nwindows == 0) { + // If the quickfix buffer is not loaded in any window, then + // wipe the buffer. + close_buffer(NULL, qfbuf, DOBUF_WIPE, false, false); + qi->qf_bufnr = INVALID_QFBUFNR; + } + } +} + /// Free a location list stack static void ll_free_all(qf_info_T **pqi) { @@ -1668,19 +1739,23 @@ static void ll_free_all(qf_info_T **pqi) } *pqi = NULL; // Remove reference to this list + // If the location list is still in use, then queue the delete request + // to be processed later. + if (quickfix_busy > 0) { + locstack_queue_delreq(qi); + return; + } + qi->qf_refcount--; if (qi->qf_refcount < 1) { // No references to this location list. - // If the location list is still in use, then queue the delete request - // to be processed later. - if (quickfix_busy > 0) { - locstack_queue_delreq(qi); - } else { - for (i = 0; i < qi->qf_listcount; i++) { - qf_free(qf_get_list(qi, i)); - } - xfree(qi); + // If the quickfix window buffer is loaded, then wipe it + wipe_qf_buffer(qi); + + for (i = 0; i < qi->qf_listcount; i++) { + qf_free(qf_get_list(qi, i)); } + xfree(qi); } } @@ -1838,6 +1913,7 @@ static qf_info_T *qf_alloc_stack(qfltype_T qfltype) qf_info_T *qi = xcalloc(1, sizeof(qf_info_T)); qi->qf_refcount++; qi->qfl_type = qfltype; + qi->qf_bufnr = INVALID_QFBUFNR; return qi; } @@ -2399,7 +2475,7 @@ static qfline_T *qf_get_entry(qf_list_T *qfl, int errornr, int dir, int *new_qfi return qf_ptr; } -// Find a window displaying a Vim help file. +// Find a window displaying a Vim help file in the current tab page. static win_T *qf_find_help_win(void) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { @@ -2473,8 +2549,9 @@ static int jump_to_help_window(qf_info_T *qi, bool newwin, int *opened_window) return OK; } -// Find a non-quickfix window using the given location list. -// Returns NULL if a matching window is not found. +/// Find a non-quickfix window using the given location list stack in the +/// current tabpage. +/// Returns NULL if a matching window is not found. static win_T *qf_find_win_with_loclist(const qf_info_T *ll) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { @@ -2486,7 +2563,7 @@ static win_T *qf_find_win_with_loclist(const qf_info_T *ll) return NULL; } -// Find a window containing a normal buffer +/// Find a window containing a normal buffer in the current tab page. static win_T *qf_find_win_with_normal_buf(void) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { @@ -2542,7 +2619,7 @@ static void qf_goto_win_with_ll_file(win_T *use_win, int qf_fnum, qf_info_T *ll_ win_T *win = use_win; if (win == NULL) { - // Find the window showing the selected file + // Find the window showing the selected file in the current tab page. FOR_ALL_WINDOWS_IN_TAB(win2, curtab) { if (win2->w_buffer->b_fnum == qf_fnum) { win = win2; @@ -2674,7 +2751,7 @@ static int qf_jump_to_usable_window(int qf_fnum, bool newwin, int *opened_window } /// Edit the selected file or help file. -static int qf_jump_edit_buffer(qf_info_T *qi, qfline_T *qf_ptr, int forceit, win_T *oldwin, +static int qf_jump_edit_buffer(qf_info_T *qi, qfline_T *qf_ptr, int forceit, int prev_winid, int *opened_window) { qf_list_T *qfl = qf_get_curlist(qi); @@ -2693,7 +2770,7 @@ static int qf_jump_edit_buffer(qf_info_T *qi, qfline_T *qf_ptr, int forceit, win } else { retval = do_ecmd(qf_ptr->qf_fnum, NULL, NULL, NULL, (linenr_T)1, ECMD_HIDE + ECMD_SET_HELP, - oldwin == curwin ? curwin : NULL); + prev_winid == curwin->handle ? curwin : NULL); } } else { retval = buflist_getfile(qf_ptr->qf_fnum, (linenr_T)1, @@ -2701,10 +2778,14 @@ static int qf_jump_edit_buffer(qf_info_T *qi, qfline_T *qf_ptr, int forceit, win } // If a location list, check whether the associated window is still // present. - if (qfl_type == QFLT_LOCATION && !win_valid_any_tab(oldwin)) { - emsg(_("E924: Current window was closed")); - *opened_window = false; - return NOTDONE; + if (qfl_type == QFLT_LOCATION) { + win_T *wp = win_id2wp(prev_winid); + + if (wp == NULL && curwin->w_llist != qi) { + emsg(_("E924: Current window was closed")); + *opened_window = false; + return NOTDONE; + } } if (qfl_type == QFLT_QUICKFIX && !qflist_valid(NULL, save_qfid)) { @@ -2712,8 +2793,8 @@ static int qf_jump_edit_buffer(qf_info_T *qi, qfline_T *qf_ptr, int forceit, win return NOTDONE; } - if (old_qf_curlist != qi->qf_curlist - || old_changetick != qfl->qf_changedtick + if (old_qf_curlist != qi->qf_curlist // -V560 + || old_changetick != qfl->qf_changedtick // -V560 || !is_qf_entry_present(qfl, qf_ptr)) { if (qfl_type == QFLT_QUICKFIX) { emsg(_(e_current_quickfix_list_was_changed)); @@ -2814,7 +2895,7 @@ static int qf_jump_open_window(qf_info_T *qi, qfline_T *qf_ptr, bool newwin, int } } if (old_qf_curlist != qi->qf_curlist - || old_changetick != qfl->qf_changedtick + || old_changetick != qfl->qf_changedtick // -V560 || !is_qf_entry_present(qfl, qf_ptr)) { if (qfl_type == QFLT_QUICKFIX) { emsg(_(e_current_quickfix_list_was_changed)); @@ -2859,7 +2940,7 @@ static int qf_jump_open_window(qf_info_T *qi, qfline_T *qf_ptr, bool newwin, int /// NOTDONE if the quickfix/location list is freed by an autocmd when opening /// the file. static int qf_jump_to_buffer(qf_info_T *qi, int qf_index, qfline_T *qf_ptr, int forceit, - win_T *oldwin, int *opened_window, int openfold, int print_message) + int prev_winid, int *opened_window, int openfold, int print_message) { buf_T *old_curbuf; linenr_T old_lnum; @@ -2871,7 +2952,7 @@ static int qf_jump_to_buffer(qf_info_T *qi, int qf_index, qfline_T *qf_ptr, int old_lnum = curwin->w_cursor.lnum; if (qf_ptr->qf_fnum != 0) { - retval = qf_jump_edit_buffer(qi, qf_ptr, forceit, oldwin, + retval = qf_jump_edit_buffer(qi, qf_ptr, forceit, prev_winid, opened_window); if (retval != OK) { return retval; @@ -2920,8 +3001,8 @@ static void qf_jump_newwin(qf_info_T *qi, int dir, int errornr, int forceit, boo int old_qf_index; char_u *old_swb = p_swb; unsigned old_swb_flags = swb_flags; + int prev_winid; int opened_window = false; - win_T *oldwin = curwin; int print_message = true; const bool old_KeyTyped = KeyTyped; // getting file may reset it int retval = OK; @@ -2935,6 +3016,8 @@ static void qf_jump_newwin(qf_info_T *qi, int dir, int errornr, int forceit, boo return; } + incr_quickfix_busy(); + qfl = qf_get_curlist(qi); qf_ptr = qfl->qf_ptr; @@ -2957,6 +3040,8 @@ static void qf_jump_newwin(qf_info_T *qi, int dir, int errornr, int forceit, boo print_message = false; } + prev_winid = curwin->handle; + retval = qf_jump_open_window(qi, qf_ptr, newwin, &opened_window); if (retval == FAIL) { goto failed; @@ -2965,7 +3050,7 @@ static void qf_jump_newwin(qf_info_T *qi, int dir, int errornr, int forceit, boo goto theend; } - retval = qf_jump_to_buffer(qi, qf_index, qf_ptr, forceit, oldwin, + retval = qf_jump_to_buffer(qi, qf_index, qf_ptr, forceit, prev_winid, &opened_window, old_KeyTyped, print_message); if (retval == NOTDONE) { // Quickfix/location list is freed by an autocmd @@ -2975,7 +3060,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 @@ -2990,12 +3075,13 @@ theend: qfl->qf_ptr = qf_ptr; qfl->qf_index = qf_index; } - if (p_swb != old_swb && p_swb == empty_option && opened_window) { + if (p_swb != old_swb && p_swb == empty_option) { // Restore old 'switchbuf' value, but not when an autocommand or // modeline has changed the value. p_swb = old_swb; swb_flags = old_swb_flags; } + decr_quickfix_busy(); } @@ -3530,7 +3616,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); } } @@ -3550,7 +3636,7 @@ static int qf_goto_cwindow(const qf_info_T *qi, bool resize, int sz, bool vertsp win_setwidth(sz); } } else if (sz != win->w_height - && (win->w_height + win->w_status_height + tabline_height() + && (win->w_height + win->w_hsep_height + win->w_status_height + tabline_height() < cmdline_row)) { win_setheight(sz); } @@ -3565,7 +3651,7 @@ static void qf_set_cwindow_options(void) // switch off 'swapfile' set_option_value("swf", 0L, NULL, OPT_LOCAL); set_option_value("bt", 0L, "quickfix", OPT_LOCAL); - set_option_value("bh", 0L, "wipe", OPT_LOCAL); + set_option_value("bh", 0L, "hide", OPT_LOCAL); RESET_BINDING(curwin); curwin->w_p_diff = false; set_option_value("fdm", 0L, "manual", OPT_LOCAL); @@ -3599,22 +3685,13 @@ static int qf_open_new_cwindow(qf_info_T *qi, int height) if (win_split(height, flags) == FAIL) { return FAIL; // not enough room for window } - - // User autocommands may have invalidated the previous window after calling - // win_split, so add a check to ensure that the win is still here - if (IS_LL_STACK(qi) && !win_valid(win)) { - // close the window that was supposed to be for the loclist - win_close(curwin, false); - return FAIL; - } - RESET_BINDING(curwin); if (IS_LL_STACK(qi)) { // For the location list window, create a reference to the - // location list from the window 'win'. - curwin->w_llist_ref = win->w_llist; - win->w_llist->qf_refcount++; + // location list stack from the window 'win'. + curwin->w_llist_ref = qi; + qi->qf_refcount++; } if (oldwin != curwin) { @@ -3631,6 +3708,8 @@ static int qf_open_new_cwindow(qf_info_T *qi, int height) if (do_ecmd(0, NULL, NULL, NULL, ECMD_ONE, ECMD_HIDE + ECMD_NOWINENTER, oldwin) == FAIL) { return FAIL; } + // save the number of the new buffer + qi->qf_bufnr = curbuf->b_fnum; } // Set the options for the quickfix buffer/window (if not already done) @@ -3809,8 +3888,8 @@ static int is_qf_win(const win_T *win, const qf_info_T *qi) return false; } -/// Find a window displaying the quickfix/location stack 'qi' -/// Only searches in the current tabpage. +/// Find a window displaying the quickfix/location stack 'qi' in the current tab +/// page. static win_T *qf_find_win(const qf_info_T *qi) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { @@ -3823,11 +3902,20 @@ static win_T *qf_find_win(const qf_info_T *qi) return NULL; } -// Find a quickfix buffer. -// Searches in windows opened in all the tabs. +/// Find a quickfix buffer. +/// Searches in windows opened in all the tab pages. static buf_T *qf_find_buf(qf_info_T *qi) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT { + if (qi->qf_bufnr != INVALID_QFBUFNR) { + buf_T *const qfbuf = buflist_findnr(qi->qf_bufnr); + if (qfbuf != NULL) { + return qfbuf; + } + // buffer is no longer present + qi->qf_bufnr = INVALID_QFBUFNR; + } + FOR_ALL_TAB_WINDOWS(tp, win) { if (is_qf_win(win, qi)) { return win->w_buffer; @@ -3947,7 +4035,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 +4937,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 +4953,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 +5280,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 +5379,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 +5414,71 @@ 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; + memset(args, 0, sizeof(*args)); - 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; - } - } - - 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 +5486,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 +5501,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 +5514,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 +5545,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 +5561,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 +5581,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 +5640,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 +5673,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 +5807,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; @@ -5692,7 +5843,7 @@ static void wipe_dummy_buffer(buf_T *buf, char_u *dirname_start) static void unload_dummy_buffer(buf_T *buf, char_u *dirname_start) { if (curbuf != buf) { // safety check - close_buffer(NULL, buf, DOBUF_UNLOAD, false); + close_buffer(NULL, buf, DOBUF_UNLOAD, false, true); // When autocommands/'autochdir' option changed directory: go back. restore_start_dir(dirname_start); @@ -5869,6 +6020,21 @@ static int qf_winid(qf_info_T *qi) return 0; } +/// Returns the number of the buffer displayed in the quickfix/location list +/// window. If there is no buffer associated with the list or the buffer is +/// wiped out, then returns 0. +static int qf_getprop_qfbufnr(const qf_info_T *qi, dict_T *retdict) + FUNC_ATTR_NONNULL_ARG(2) +{ + int bufnum = 0; + + if (qi != NULL && buflist_findnr(qi->qf_bufnr) != NULL) { + bufnum = qi->qf_bufnr; + } + + return tv_dict_add_nr(retdict, S_LEN("qfbufnr"), bufnum); +} + /// Convert the keys in 'what' to quickfix list property flags. static int qf_getprop_keys2flags(const dict_T *what, bool loclist) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT @@ -5912,6 +6078,9 @@ static int qf_getprop_keys2flags(const dict_T *what, bool loclist) if (loclist && tv_dict_find(what, S_LEN("filewinid")) != NULL) { flags |= QF_GETLIST_FILEWINID; } + if (tv_dict_find(what, S_LEN("qfbufnr")) != NULL) { + flags |= QF_GETLIST_QFBUFNR; + } if (tv_dict_find(what, S_LEN("quickfixtextfunc")) != NULL) { flags |= QF_GETLIST_QFTF; } @@ -6003,6 +6172,9 @@ static int qf_getprop_defaults(qf_info_T *qi, int flags, int locstack, dict_T *r if ((status == OK) && locstack && (flags & QF_GETLIST_FILEWINID)) { status = tv_dict_add_nr(retdict, S_LEN("filewinid"), 0); } + if ((status == OK) && (flags & QF_GETLIST_QFBUFNR)) { + status = qf_getprop_qfbufnr(qi, retdict); + } if ((status == OK) && (flags & QF_GETLIST_QFTF)) { status = tv_dict_add_str(retdict, S_LEN("quickfixtextfunc"), ""); } @@ -6172,6 +6344,9 @@ int qf_get_properties(win_T *wp, dict_T *what, dict_T *retdict) if ((status == OK) && (wp != NULL) && (flags & QF_GETLIST_FILEWINID)) { status = qf_getprop_filewinid(wp, qi, retdict); } + if ((status == OK) && (flags & QF_GETLIST_QFBUFNR)) { + status = qf_getprop_qfbufnr(qi, retdict); + } if ((status == OK) && (flags & QF_GETLIST_QFTF)) { status = qf_getprop_qftf(qfl, retdict); } @@ -6560,20 +6735,6 @@ static int qf_set_properties(qf_info_T *qi, const dict_T *what, int action, char return retval; } -/// Find the non-location list window with the specified location list stack in -/// the current tabpage. -static win_T *find_win_with_ll(const qf_info_T *qi) - FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT -{ - FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - if ((wp->w_llist == qi) && !bt_quickfix(wp->w_buffer)) { - return wp; - } - } - - return NULL; -} - // Free the entire quickfix/location list stack. // If the quickfix/location list window is open, then clear it. static void qf_free_stack(win_T *wp, qf_info_T *qi) @@ -6588,12 +6749,10 @@ static void qf_free_stack(win_T *wp, qf_info_T *qi) qf_update_buffer(qi, NULL); } - win_T *llwin = NULL; - win_T *orig_wp = wp; if (wp != NULL && IS_LL_WINDOW(wp)) { // If in the location list window, then use the non-location list // window with this location list (if present) - llwin = find_win_with_ll(qi); + win_T *const llwin = qf_find_win_with_loclist(qi); if (llwin != NULL) { wp = llwin; } @@ -6604,16 +6763,17 @@ static void qf_free_stack(win_T *wp, qf_info_T *qi) // quickfix list qi->qf_curlist = 0; qi->qf_listcount = 0; - } else if (IS_LL_WINDOW(orig_wp)) { + } else if (qfwin != NULL) { // If the location list window is open, then create a new empty location // list qf_info_T *new_ll = qf_alloc_stack(QFLT_LOCATION); + new_ll->qf_bufnr = qfwin->w_buffer->b_fnum; // first free the list reference in the location list window - ll_free_all(&orig_wp->w_llist_ref); + ll_free_all(&qfwin->w_llist_ref); - orig_wp->w_llist_ref = new_ll; - if (llwin != NULL) { + qfwin->w_llist_ref = new_ll; + if (wp != qfwin) { win_set_loclist(wp, new_ll); } } 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/rbuffer.h b/src/nvim/rbuffer.h index cc690050ab..e86765a4ad 100644 --- a/src/nvim/rbuffer.h +++ b/src/nvim/rbuffer.h @@ -38,17 +38,15 @@ // create infinite loops #define RBUFFER_UNTIL_EMPTY(buf, rptr, rcnt) \ for (size_t rcnt = 0, _r = 1; _r; _r = 0) /* NOLINT(readability/braces) */ \ - for ( /* NOLINT(readability/braces) */ \ - char *rptr = rbuffer_read_ptr(buf, &rcnt); \ - buf->size; \ - rptr = rbuffer_read_ptr(buf, &rcnt)) + for (char *rptr = rbuffer_read_ptr(buf, &rcnt); /* NOLINT(readability/braces) */ \ + buf->size; \ + rptr = rbuffer_read_ptr(buf, &rcnt)) #define RBUFFER_UNTIL_FULL(buf, wptr, wcnt) \ for (size_t wcnt = 0, _r = 1; _r; _r = 0) /* NOLINT(readability/braces) */ \ - for ( /* NOLINT(readability/braces) */ \ - char *wptr = rbuffer_write_ptr(buf, &wcnt); \ - rbuffer_space(buf); \ - wptr = rbuffer_write_ptr(buf, &wcnt)) + for (char *wptr = rbuffer_write_ptr(buf, &wcnt); /* NOLINT(readability/braces) */ \ + rbuffer_space(buf); \ + wptr = rbuffer_write_ptr(buf, &wcnt)) // Iteration diff --git a/src/nvim/regexp.c b/src/nvim/regexp.c index bec3bc9648..b61c9a2cf5 100644 --- a/src/nvim/regexp.c +++ b/src/nvim/regexp.c @@ -5,41 +5,6 @@ /* * Handling of regular expressions: vim_regcomp(), vim_regexec(), vim_regsub() - * - * NOTICE: - * - * This is NOT the original regular expression code as written by Henry - * Spencer. This code has been modified specifically for use with the VIM - * editor, and should not be used separately from Vim. If you want a good - * regular expression library, get the original code. The copyright notice - * that follows is from the original. - * - * END NOTICE - * - * Copyright (c) 1986 by University of Toronto. - * Written by Henry Spencer. Not derived from licensed software. - * - * Permission is granted to anyone to use this software for any - * purpose on any computer system, and to redistribute it freely, - * subject to the following restrictions: - * - * 1. The author is not responsible for the consequences of use of - * this software, no matter how awful, even if they arise - * from defects in it. - * - * 2. The origin of this software must not be misrepresented, either - * by explicit claim or by omission. - * - * 3. Altered versions must be plainly marked as such, and must not - * be misrepresented as being the original software. - * - * Beware that some of this code is subtly aware of the way operator - * precedence is structured in regular expressions. Serious changes in - * regular-expression syntax might require a total rethink. - * - * Changes have been made by Tony Andrews, Olaf 'Rhialto' Seibert, Robert - * Webb, Ciaran McCreesh and Bram Moolenaar. - * Named character class support added by Walter Briscoe (1998 Jul 01) */ // By default: do not create debugging logs or files related to regular @@ -64,211 +29,21 @@ #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/message.h" -#include "nvim/misc1.h" +#include "nvim/os/input.h" #include "nvim/plines.h" #include "nvim/garray.h" #include "nvim/strings.h" #ifdef REGEXP_DEBUG -/* show/save debugging data when BT engine is used */ +// show/save debugging data when BT engine is used # define BT_REGEXP_DUMP -/* save the debugging data to a file instead of displaying it */ +// save the debugging data to a file instead of displaying it # define BT_REGEXP_LOG # define BT_REGEXP_DEBUG_LOG # define BT_REGEXP_DEBUG_LOG_NAME "bt_regexp_debug.log" #endif /* - * The "internal use only" fields in regexp_defs.h are present to pass info from - * compile to execute that permits the execute phase to run lots faster on - * simple cases. They are: - * - * regstart char that must begin a match; NUL if none obvious; Can be a - * multi-byte character. - * reganch is the match anchored (at beginning-of-line only)? - * regmust string (pointer into program) that match must include, or NULL - * regmlen length of regmust string - * regflags RF_ values or'ed together - * - * Regstart and reganch permit very fast decisions on suitable starting points - * for a match, cutting down the work a lot. Regmust permits fast rejection - * of lines that cannot possibly match. The regmust tests are costly enough - * that vim_regcomp() supplies a regmust only if the r.e. contains something - * potentially expensive (at present, the only such thing detected is * or + - * at the start of the r.e., which can involve a lot of backup). Regmlen is - * supplied because the test in vim_regexec() needs it and vim_regcomp() is - * computing it anyway. - */ - -/* - * Structure for regexp "program". This is essentially a linear encoding - * of a nondeterministic finite-state machine (aka syntax charts or - * "railroad normal form" in parsing technology). Each node is an opcode - * plus a "next" pointer, possibly plus an operand. "Next" pointers of - * all nodes except BRANCH and BRACES_COMPLEX implement concatenation; a "next" - * pointer with a BRANCH on both ends of it is connecting two alternatives. - * (Here we have one of the subtle syntax dependencies: an individual BRANCH - * (as opposed to a collection of them) is never concatenated with anything - * because of operator precedence). The "next" pointer of a BRACES_COMPLEX - * node points to the node after the stuff to be repeated. - * The operand of some types of node is a literal string; for others, it is a - * node leading into a sub-FSM. In particular, the operand of a BRANCH node - * is the first node of the branch. - * (NB this is *not* a tree structure: the tail of the branch connects to the - * thing following the set of BRANCHes.) - * - * pattern is coded like: - * - * +-----------------+ - * | V - * <aa>\|<bb> BRANCH <aa> BRANCH <bb> --> END - * | ^ | ^ - * +------+ +----------+ - * - * - * +------------------+ - * V | - * <aa>* BRANCH BRANCH <aa> --> BACK BRANCH --> NOTHING --> END - * | | ^ ^ - * | +---------------+ | - * +---------------------------------------------+ - * - * - * +----------------------+ - * V | - * <aa>\+ BRANCH <aa> --> BRANCH --> BACK BRANCH --> NOTHING --> END - * | | ^ ^ - * | +-----------+ | - * +--------------------------------------------------+ - * - * - * +-------------------------+ - * V | - * <aa>\{} BRANCH BRACE_LIMITS --> BRACE_COMPLEX <aa> --> BACK END - * | | ^ - * | +----------------+ - * +-----------------------------------------------+ - * - * - * <aa>\@!<bb> BRANCH NOMATCH <aa> --> END <bb> --> END - * | | ^ ^ - * | +----------------+ | - * +--------------------------------+ - * - * +---------+ - * | V - * \z[abc] BRANCH BRANCH a BRANCH b BRANCH c BRANCH NOTHING --> END - * | | | | ^ ^ - * | | | +-----+ | - * | | +----------------+ | - * | +---------------------------+ | - * +------------------------------------------------------+ - * - * They all start with a BRANCH for "\|" alternatives, even when there is only - * one alternative. - */ - -/* - * The opcodes are: - */ - -/* definition number opnd? meaning */ -#define END 0 /* End of program or NOMATCH operand. */ -#define BOL 1 /* Match "" at beginning of line. */ -#define EOL 2 /* Match "" at end of line. */ -#define BRANCH 3 /* node Match this alternative, or the - * next... */ -#define BACK 4 /* Match "", "next" ptr points backward. */ -#define EXACTLY 5 /* str Match this string. */ -#define NOTHING 6 /* Match empty string. */ -#define STAR 7 /* node Match this (simple) thing 0 or more - * times. */ -#define PLUS 8 /* node Match this (simple) thing 1 or more - * times. */ -#define MATCH 9 /* node match the operand zero-width */ -#define NOMATCH 10 /* node check for no match with operand */ -#define BEHIND 11 /* node look behind for a match with operand */ -#define NOBEHIND 12 /* node look behind for no match with operand */ -#define SUBPAT 13 /* node match the operand here */ -#define BRACE_SIMPLE 14 /* node Match this (simple) thing between m and - * n times (\{m,n\}). */ -#define BOW 15 /* Match "" after [^a-zA-Z0-9_] */ -#define EOW 16 /* Match "" at [^a-zA-Z0-9_] */ -#define BRACE_LIMITS 17 /* nr nr define the min & max for BRACE_SIMPLE - * and BRACE_COMPLEX. */ -#define NEWL 18 /* Match line-break */ -#define BHPOS 19 /* End position for BEHIND or NOBEHIND */ - - -/* character classes: 20-48 normal, 50-78 include a line-break */ -#define ADD_NL 30 -#define FIRST_NL ANY + ADD_NL -#define ANY 20 /* Match any one character. */ -#define ANYOF 21 /* str Match any character in this string. */ -#define ANYBUT 22 /* str Match any character not in this - * string. */ -#define IDENT 23 /* Match identifier char */ -#define SIDENT 24 /* Match identifier char but no digit */ -#define KWORD 25 /* Match keyword char */ -#define SKWORD 26 /* Match word char but no digit */ -#define FNAME 27 /* Match file name char */ -#define SFNAME 28 /* Match file name char but no digit */ -#define PRINT 29 /* Match printable char */ -#define SPRINT 30 /* Match printable char but no digit */ -#define WHITE 31 /* Match whitespace char */ -#define NWHITE 32 /* Match non-whitespace char */ -#define DIGIT 33 /* Match digit char */ -#define NDIGIT 34 /* Match non-digit char */ -#define HEX 35 /* Match hex char */ -#define NHEX 36 /* Match non-hex char */ -#define OCTAL 37 /* Match octal char */ -#define NOCTAL 38 /* Match non-octal char */ -#define WORD 39 /* Match word char */ -#define NWORD 40 /* Match non-word char */ -#define HEAD 41 /* Match head char */ -#define NHEAD 42 /* Match non-head char */ -#define ALPHA 43 /* Match alpha char */ -#define NALPHA 44 /* Match non-alpha char */ -#define LOWER 45 /* Match lowercase char */ -#define NLOWER 46 /* Match non-lowercase char */ -#define UPPER 47 /* Match uppercase char */ -#define NUPPER 48 /* Match non-uppercase char */ -#define LAST_NL NUPPER + ADD_NL -// -V:WITH_NL:560 -#define WITH_NL(op) ((op) >= FIRST_NL && (op) <= LAST_NL) - -#define MOPEN 80 // -89 Mark this point in input as start of - // \( … \) subexpr. MOPEN + 0 marks start of - // match. -#define MCLOSE 90 // -99 Analogous to MOPEN. MCLOSE + 0 marks - // end of match. -#define BACKREF 100 // -109 node Match same string again \1-\9. - -# define ZOPEN 110 // -119 Mark this point in input as start of - // \z( … \) subexpr. -# define ZCLOSE 120 // -129 Analogous to ZOPEN. -# define ZREF 130 // -139 node Match external submatch \z1-\z9 - -#define BRACE_COMPLEX 140 /* -149 node Match nodes between m & n times */ - -#define NOPEN 150 // Mark this point in input as start of - // \%( subexpr. -#define NCLOSE 151 // Analogous to NOPEN. - -#define MULTIBYTECODE 200 /* mbc Match one multi-byte character */ -#define RE_BOF 201 /* Match "" at beginning of file. */ -#define RE_EOF 202 /* Match "" at end of file. */ -#define CURSOR 203 /* Match location of cursor. */ - -#define RE_LNUM 204 /* nr cmp Match line number */ -#define RE_COL 205 /* nr cmp Match column number */ -#define RE_VCOL 206 /* nr cmp Match virtual column number */ - -#define RE_MARK 207 /* mark cmp Match mark position */ -#define RE_VISUAL 208 /* Match Visual area */ -#define RE_COMPOSING 209 // any composing characters - -/* * Magic characters have a special meaning, they don't match literally. * Magic characters are negative. This separates them from literal characters * (possibly multi-byte). Only ASCII characters can be Magic. @@ -285,107 +60,6 @@ */ typedef void (*(*fptr_T)(int *, int))(void); -typedef struct { - char_u *regparse; - int prevchr_len; - int curchr; - int prevchr; - int prevprevchr; - int nextchr; - int at_start; - int prev_at_start; - int regnpar; -} parse_state_T; - -/* - * Structure used to save the current input state, when it needs to be - * restored after trying a match. Used by reg_save() and reg_restore(). - * Also stores the length of "backpos". - */ -typedef struct { - union { - char_u *ptr; ///< rex.input pointer, for single-line regexp - lpos_T pos; ///< rex.input pos, for multi-line regexp - } rs_u; - int rs_len; -} regsave_T; - -/* struct to save start/end pointer/position in for \(\) */ -typedef struct { - union { - char_u *ptr; - lpos_T pos; - } se_u; -} save_se_T; - -/* used for BEHIND and NOBEHIND matching */ -typedef struct regbehind_S { - regsave_T save_after; - regsave_T save_behind; - int save_need_clear_subexpr; - save_se_T save_start[NSUBEXP]; - save_se_T save_end[NSUBEXP]; -} regbehind_T; - -/* Values for rs_state in regitem_T. */ -typedef enum regstate_E { - RS_NOPEN = 0 /* NOPEN and NCLOSE */ - , RS_MOPEN /* MOPEN + [0-9] */ - , RS_MCLOSE /* MCLOSE + [0-9] */ - , RS_ZOPEN /* ZOPEN + [0-9] */ - , RS_ZCLOSE /* ZCLOSE + [0-9] */ - , RS_BRANCH /* BRANCH */ - , RS_BRCPLX_MORE /* BRACE_COMPLEX and trying one more match */ - , RS_BRCPLX_LONG /* BRACE_COMPLEX and trying longest match */ - , RS_BRCPLX_SHORT /* BRACE_COMPLEX and trying shortest match */ - , RS_NOMATCH /* NOMATCH */ - , RS_BEHIND1 /* BEHIND / NOBEHIND matching rest */ - , RS_BEHIND2 /* BEHIND / NOBEHIND matching behind part */ - , RS_STAR_LONG /* STAR/PLUS/BRACE_SIMPLE longest match */ - , RS_STAR_SHORT /* STAR/PLUS/BRACE_SIMPLE shortest match */ -} regstate_T; - -/* - * When there are alternatives a regstate_T is put on the regstack to remember - * what we are doing. - * Before it may be another type of item, depending on rs_state, to remember - * more things. - */ -typedef struct regitem_S { - regstate_T rs_state; // what we are doing, one of RS_ above - uint16_t rs_no; // submatch nr or BEHIND/NOBEHIND - char_u *rs_scan; // current node in program - union { - save_se_T sesave; - regsave_T regsave; - } rs_un; ///< room for saving rex.input -} regitem_T; - - -/* used for STAR, PLUS and BRACE_SIMPLE matching */ -typedef struct regstar_S { - int nextb; /* next byte */ - int nextb_ic; /* next byte reverse case */ - long count; - long minval; - long maxval; -} regstar_T; - -/* used to store input position when a BACK was encountered, so that we now if - * we made any progress since the last time. */ -typedef struct backpos_S { - char_u *bp_scan; /* "scan" where BACK was encountered */ - regsave_T bp_pos; /* last input position */ -} backpos_T; - -typedef struct { - int a, b, c; -} decomp_T; - - -#ifdef INCLUDE_GENERATED_DECLARATIONS -# include "regexp.c.generated.h" -#endif static int no_Magic(int x) { if (is_Magic(x)) @@ -400,66 +74,13 @@ static int toggle_Magic(int x) return Magic(x); } -/* - * The first byte of the regexp internal "program" is actually this magic - * number; the start node begins in the second byte. It's used to catch the - * most severe mutilation of the program by the caller. - */ - +// The first byte of the BT regexp internal "program" is actually this magic +// number; the start node begins in the second byte. It's used to catch the +// most severe mutilation of the program by the caller. #define REGMAGIC 0234 -/* - * Opcode notes: - * - * BRANCH The set of branches constituting a single choice are hooked - * together with their "next" pointers, since precedence prevents - * anything being concatenated to any individual branch. The - * "next" pointer of the last BRANCH in a choice points to the - * thing following the whole choice. This is also where the - * final "next" pointer of each individual branch points; each - * branch starts with the operand node of a BRANCH node. - * - * BACK Normal "next" pointers all implicitly point forward; BACK - * exists to make loop structures possible. - * - * STAR,PLUS '=', and complex '*' and '+', are implemented as circular - * BRANCH structures using BACK. Simple cases (one character - * per match) are implemented with STAR and PLUS for speed - * and to minimize recursive plunges. - * - * BRACE_LIMITS This is always followed by a BRACE_SIMPLE or BRACE_COMPLEX - * node, and defines the min and max limits to be used for that - * node. - * - * MOPEN,MCLOSE ...are numbered at compile time. - * ZOPEN,ZCLOSE ...ditto - */ - -/* - * A node is one char of opcode followed by two chars of "next" pointer. - * "Next" pointers are stored as two 8-bit bytes, high order first. The - * value is a positive offset from the opcode of the node containing it. - * An operand, if any, simply follows the node. (Note that much of the - * code generation knows about this implicit relationship.) - * - * Using two bytes for the "next" pointer is vast overkill for most things, - * but allows patterns to get big without disasters. - */ -#define OP(p) ((int)*(p)) -#define NEXT(p) (((*((p) + 1) & 0377) << 8) + (*((p) + 2) & 0377)) -#define OPERAND(p) ((p) + 3) -/* Obtain an operand that was stored as four bytes, MSB first. */ -#define OPERAND_MIN(p) (((long)(p)[3] << 24) + ((long)(p)[4] << 16) \ - + ((long)(p)[5] << 8) + (long)(p)[6]) -/* Obtain a second operand stored as four bytes. */ -#define OPERAND_MAX(p) OPERAND_MIN((p) + 4) -/* Obtain a second single-byte operand stored after a four bytes operand. */ -#define OPERAND_CMP(p) (p)[7] - -/* - * Utility definitions. - */ -#define UCHARAT(p) ((int)*(char_u *)(p)) +// Utility definitions. +#define UCHARAT(p) ((int)(*(char_u *)(p))) // Used for an error (down from) vim_regcomp(): give the error message, set // rc_did_emsg and return NULL @@ -468,6 +89,8 @@ static int toggle_Magic(int x) #define EMSG_RET_FAIL(m) return (emsg(m), rc_did_emsg = true, FAIL) #define EMSG2_RET_NULL(m, c) \ return (semsg((m), (c) ? "" : "\\"), rc_did_emsg = true, (void *)NULL) +#define EMSG3_RET_NULL(m, c, a) \ + return (semsg((const char *)(m), (c) ? "" : "\\", (a)), rc_did_emsg = true, (void *)NULL) #define EMSG2_RET_FAIL(m, c) \ return (semsg((m), (c) ? "" : "\\"), rc_did_emsg = true, FAIL) #define EMSG_ONE_RET_NULL EMSG2_RET_NULL(_( \ @@ -475,14 +98,6 @@ static int toggle_Magic(int x) #define MAX_LIMIT (32767L << 16L) - -#ifdef BT_REGEXP_DUMP -static void regdump(char_u *, bt_regprog_T *); -#endif -#ifdef REGEXP_DEBUG -static char_u *regprop(char_u *); -#endif - static char_u e_missingbracket[] = N_("E769: Missing ] after %s["); static char_u e_reverse_range[] = N_("E944: Reverse range in character class"); static char_u e_large_class[] = N_("E945: Range too large in character class"); @@ -492,17 +107,25 @@ static char_u e_unmatchedpar[] = N_("E55: Unmatched %s)"); static char_u e_z_not_allowed[] = N_("E66: \\z( not allowed here"); static char_u e_z1_not_allowed[] = N_("E67: \\z1 - \\z9 not allowed here"); static char_u e_missing_sb[] = N_("E69: Missing ] after %s%%["); -static char_u e_empty_sb[] = N_("E70: Empty %s%%[]"); -static char_u e_recursive[] = N_("E956: Cannot use pattern recursively"); +static char_u e_empty_sb[] = N_("E70: Empty %s%%[]"); +static char_u e_recursive[] = N_("E956: Cannot use pattern recursively"); +static char_u e_regexp_number_after_dot_pos_search[] + = N_("E1204: No Number allowed after .: '\\%%%c'"); #define NOT_MULTI 0 #define MULTI_ONE 1 #define MULTI_MULT 2 -/* - * Return NOT_MULTI if c is not a "multi" operator. - * Return MULTI_ONE if c is a single "multi" operator. - * Return MULTI_MULT if c is a multi "multi" operator. - */ + +// return values for regmatch() +#define RA_FAIL 1 // something failed, abort +#define RA_CONT 2 // continue in inner loop +#define RA_BREAK 3 // break inner loop +#define RA_MATCH 4 // successful match +#define RA_NOMATCH 5 // didn't match + +/// Return NOT_MULTI if c is not a "multi" operator. +/// Return MULTI_ONE if c is a single "multi" operator. +/// Return MULTI_MULT if c is a multi "multi" operator. static int re_multi_type(int c) { if (c == Magic('@') || c == Magic('=') || c == Magic('?')) @@ -512,22 +135,6 @@ static int re_multi_type(int c) return NOT_MULTI; } -/* - * Flags to be passed up and down. - */ -#define HASWIDTH 0x1 /* Known never to match null string. */ -#define SIMPLE 0x2 /* Simple enough to be STAR/PLUS operand. */ -#define SPSTART 0x4 /* Starts with * or +. */ -#define HASNL 0x8 /* Contains some \n. */ -#define HASLOOKBH 0x10 /* Contains "\@<=" or "\@<!". */ -#define WORST 0 /* Worst case. */ - -/* - * When regcode is set to this value, code is not emitted and size is computed - * instead. - */ -#define JUST_CALC_SIZE ((char_u *) -1) - static char_u *reg_prev_sub = NULL; /* @@ -672,48 +279,38 @@ static void init_class_tab(void) done = true; } -# define ri_digit(c) (c < 0x100 && (class_tab[c] & RI_DIGIT)) -# define ri_hex(c) (c < 0x100 && (class_tab[c] & RI_HEX)) -# define ri_octal(c) (c < 0x100 && (class_tab[c] & RI_OCTAL)) -# define ri_word(c) (c < 0x100 && (class_tab[c] & RI_WORD)) -# define ri_head(c) (c < 0x100 && (class_tab[c] & RI_HEAD)) -# define ri_alpha(c) (c < 0x100 && (class_tab[c] & RI_ALPHA)) -# define ri_lower(c) (c < 0x100 && (class_tab[c] & RI_LOWER)) -# define ri_upper(c) (c < 0x100 && (class_tab[c] & RI_UPPER)) -# define ri_white(c) (c < 0x100 && (class_tab[c] & RI_WHITE)) - -/* flags for regflags */ -#define RF_ICASE 1 /* ignore case */ -#define RF_NOICASE 2 /* don't ignore case */ -#define RF_HASNL 4 /* can match a NL */ -#define RF_ICOMBINE 8 /* ignore combining characters */ -#define RF_LOOKBH 16 /* uses "\@<=" or "\@<!" */ +# define ri_digit(c) ((c) < 0x100 && (class_tab[c] & RI_DIGIT)) +# define ri_hex(c) ((c) < 0x100 && (class_tab[c] & RI_HEX)) +# define ri_octal(c) ((c) < 0x100 && (class_tab[c] & RI_OCTAL)) +# define ri_word(c) ((c) < 0x100 && (class_tab[c] & RI_WORD)) +# define ri_head(c) ((c) < 0x100 && (class_tab[c] & RI_HEAD)) +# define ri_alpha(c) ((c) < 0x100 && (class_tab[c] & RI_ALPHA)) +# define ri_lower(c) ((c) < 0x100 && (class_tab[c] & RI_LOWER)) +# define ri_upper(c) ((c) < 0x100 && (class_tab[c] & RI_UPPER)) +# define ri_white(c) ((c) < 0x100 && (class_tab[c] & RI_WHITE)) + +// flags for regflags +#define RF_ICASE 1 // ignore case +#define RF_NOICASE 2 // don't ignore case +#define RF_HASNL 4 // can match a NL +#define RF_ICOMBINE 8 // ignore combining characters +#define RF_LOOKBH 16 // uses "\@<=" or "\@<!" // Global work variables for vim_regcomp(). static char_u *regparse; ///< Input-scan pointer. -static int prevchr_len; ///< byte length of previous char -static int num_complex_braces; ///< Complex \{...} count static int regnpar; ///< () count. static bool wants_nfa; ///< regex should use NFA engine static int regnzpar; ///< \z() count. static int re_has_z; ///< \z item detected -static char_u *regcode; ///< Code-emit pointer, or JUST_CALC_SIZE -static long regsize; ///< Code size. -static int reg_toolong; ///< true when offset out of range -static char_u had_endbrace[NSUBEXP]; ///< flags, true if end of () found -static unsigned regflags; ///< RF_ flags for prog -static long brace_min[10]; ///< Minimums for complex brace repeats -static long brace_max[10]; ///< Maximums for complex brace repeats -static int brace_count[10]; ///< Current counts for complex brace repeats -static int had_eol; ///< true when EOL found by vim_regcomp() -static int one_exactly = false; ///< only do one char for EXACTLY - -static int reg_magic; /* magicness of the pattern: */ -#define MAGIC_NONE 1 /* "\V" very unmagic */ -#define MAGIC_OFF 2 /* "\M" or 'magic' off */ -#define MAGIC_ON 3 /* "\m" or 'magic' */ -#define MAGIC_ALL 4 /* "\v" very magic */ +static unsigned regflags; ///< RF_ flags for prog +static int had_eol; ///< true when EOL found by vim_regcomp() + +static int reg_magic; // magicness of the pattern: +#define MAGIC_NONE 1 // "\V" very unmagic +#define MAGIC_OFF 2 // "\M" or 'magic' off +#define MAGIC_ON 3 // "\m" or 'magic' +#define MAGIC_ALL 4 // "\v" very magic static int reg_string; // matching with a string instead of a buffer // line @@ -723,22 +320,22 @@ static int reg_strict; // "[abc" is illegal * META contains all characters that may be magic, except '^' and '$'. */ -/* META[] is used often enough to justify turning it into a table. */ +// META[] is used often enough to justify turning it into a table. static char_u META_flags[] = { - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - /* % & ( ) * + . */ - 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 0, - /* 1 2 3 4 5 6 7 8 9 < = > ? */ - 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, - /* @ A C D F H I K L M O */ - 1, 1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, - /* P S U V W X Z [ _ */ - 1, 0, 0, 1, 0, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, - /* a c d f h i k l m n o */ - 0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, - /* p s u v w x z { | ~ */ - 1, 0, 0, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +// % & ( ) * + . + 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 0, +// 1 2 3 4 5 6 7 8 9 < = > ? + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, +// @ A C D F H I K L M O + 1, 1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, +// P S U V W X Z [ _ + 1, 0, 0, 1, 0, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, +// a c d f h i k l m n o + 0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, +// p s u v w x z { | ~ + 1, 0, 0, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1 }; static int curchr; // currently parsed character @@ -746,24 +343,36 @@ static int curchr; // currently parsed character // start, eg in /[ ^I]^ the pattern was never found even if it existed, // because ^ was taken to be magic -- webb static int prevchr; -static int prevprevchr; /* previous-previous character */ -static int nextchr; /* used for ungetchr() */ +static int prevprevchr; // previous-previous character +static int nextchr; // used for ungetchr() -/* arguments for reg() */ -#define REG_NOPAREN 0 /* toplevel reg() */ -#define REG_PAREN 1 /* \(\) */ -#define REG_ZPAREN 2 /* \z(\) */ -#define REG_NPAREN 3 /* \%(\) */ +// arguments for reg() +#define REG_NOPAREN 0 // toplevel reg() +#define REG_PAREN 1 // \(\) +#define REG_ZPAREN 2 // \z(\) +#define REG_NPAREN 3 // \%(\) + +typedef struct { + char_u *regparse; + int prevchr_len; + int curchr; + int prevchr; + int prevprevchr; + int nextchr; + int at_start; + int prev_at_start; + int regnpar; +} parse_state_T; -/* - * Forward declarations for vim_regcomp()'s friends. - */ -# define REGMBC(x) regmbc(x); -# define CASEMBC(x) case x: static regengine_T bt_regengine; static regengine_T nfa_regengine; +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "regexp.c.generated.h" +#endif + + // Return true if compiled regular expression "prog" can match a line break. int re_multiline(const regprog_T *prog) FUNC_ATTR_NONNULL_ALL @@ -795,312 +404,6 @@ static int get_equi_class(char_u **pp) /* - * Produce the bytes for equivalence class "c". - * Currently only handles latin1, latin9 and utf-8. - * NOTE: When changing this function, also change nfa_emit_equi_class() - */ -static void reg_equi_class(int c) -{ - { - switch (c) { - // Do not use '\300' style, it results in a negative number. - case 'A': case 0xc0: case 0xc1: case 0xc2: - case 0xc3: case 0xc4: case 0xc5: - CASEMBC(0x100) CASEMBC(0x102) CASEMBC(0x104) CASEMBC(0x1cd) - CASEMBC(0x1de) CASEMBC(0x1e0) CASEMBC(0x1ea2) - regmbc('A'); regmbc(0xc0); regmbc(0xc1); - regmbc(0xc2); regmbc(0xc3); regmbc(0xc4); - regmbc(0xc5); - REGMBC(0x100) REGMBC(0x102) REGMBC(0x104) - REGMBC(0x1cd) REGMBC(0x1de) REGMBC(0x1e0) - REGMBC(0x1ea2) - return; - case 'B': CASEMBC(0x1e02) CASEMBC(0x1e06) - regmbc('B'); REGMBC(0x1e02) REGMBC(0x1e06) - return; - case 'C': case 0xc7: - CASEMBC(0x106) CASEMBC(0x108) CASEMBC(0x10a) CASEMBC(0x10c) - regmbc('C'); regmbc(0xc7); - REGMBC(0x106) REGMBC(0x108) REGMBC(0x10a) - REGMBC(0x10c) - return; - case 'D': CASEMBC(0x10e) CASEMBC(0x110) CASEMBC(0x1e0a) - CASEMBC(0x1e0e) CASEMBC(0x1e10) - regmbc('D'); REGMBC(0x10e) REGMBC(0x110) - REGMBC(0x1e0a) REGMBC(0x1e0e) REGMBC(0x1e10) - return; - case 'E': case 0xc8: case 0xc9: case 0xca: case 0xcb: - CASEMBC(0x112) CASEMBC(0x114) CASEMBC(0x116) CASEMBC(0x118) - CASEMBC(0x11a) CASEMBC(0x1eba) CASEMBC(0x1ebc) - regmbc('E'); regmbc(0xc8); regmbc(0xc9); - regmbc(0xca); regmbc(0xcb); - REGMBC(0x112) REGMBC(0x114) REGMBC(0x116) - REGMBC(0x118) REGMBC(0x11a) REGMBC(0x1eba) - REGMBC(0x1ebc) - return; - case 'F': CASEMBC(0x1e1e) - regmbc('F'); REGMBC(0x1e1e) - return; - case 'G': CASEMBC(0x11c) CASEMBC(0x11e) CASEMBC(0x120) - CASEMBC(0x122) CASEMBC(0x1e4) CASEMBC(0x1e6) CASEMBC(0x1f4) - CASEMBC(0x1e20) - regmbc('G'); REGMBC(0x11c) REGMBC(0x11e) - REGMBC(0x120) REGMBC(0x122) REGMBC(0x1e4) - REGMBC(0x1e6) REGMBC(0x1f4) REGMBC(0x1e20) - return; - case 'H': CASEMBC(0x124) CASEMBC(0x126) CASEMBC(0x1e22) - CASEMBC(0x1e26) CASEMBC(0x1e28) - regmbc('H'); REGMBC(0x124) REGMBC(0x126) - REGMBC(0x1e22) REGMBC(0x1e26) REGMBC(0x1e28) - return; - case 'I': case 0xcc: case 0xcd: case 0xce: case 0xcf: - CASEMBC(0x128) CASEMBC(0x12a) CASEMBC(0x12c) CASEMBC(0x12e) - CASEMBC(0x130) CASEMBC(0x1cf) CASEMBC(0x1ec8) - regmbc('I'); regmbc(0xcc); regmbc(0xcd); - regmbc(0xce); regmbc(0xcf); - REGMBC(0x128) REGMBC(0x12a) REGMBC(0x12c) - REGMBC(0x12e) REGMBC(0x130) REGMBC(0x1cf) - REGMBC(0x1ec8) - return; - case 'J': CASEMBC(0x134) - regmbc('J'); REGMBC(0x134) - return; - case 'K': CASEMBC(0x136) CASEMBC(0x1e8) CASEMBC(0x1e30) - CASEMBC(0x1e34) - regmbc('K'); REGMBC(0x136) REGMBC(0x1e8) - REGMBC(0x1e30) REGMBC(0x1e34) - return; - case 'L': CASEMBC(0x139) CASEMBC(0x13b) CASEMBC(0x13d) - CASEMBC(0x13f) CASEMBC(0x141) CASEMBC(0x1e3a) - regmbc('L'); REGMBC(0x139) REGMBC(0x13b) - REGMBC(0x13d) REGMBC(0x13f) REGMBC(0x141) - REGMBC(0x1e3a) - return; - case 'M': CASEMBC(0x1e3e) CASEMBC(0x1e40) - regmbc('M'); REGMBC(0x1e3e) REGMBC(0x1e40) - return; - case 'N': case 0xd1: - CASEMBC(0x143) CASEMBC(0x145) CASEMBC(0x147) CASEMBC(0x1e44) - CASEMBC(0x1e48) - regmbc('N'); regmbc(0xd1); - REGMBC(0x143) REGMBC(0x145) REGMBC(0x147) - REGMBC(0x1e44) REGMBC(0x1e48) - return; - case 'O': case 0xd2: case 0xd3: case 0xd4: case 0xd5: - case 0xd6: case 0xd8: - CASEMBC(0x14c) CASEMBC(0x14e) CASEMBC(0x150) CASEMBC(0x1a0) - CASEMBC(0x1d1) CASEMBC(0x1ea) CASEMBC(0x1ec) CASEMBC(0x1ece) - regmbc('O'); regmbc(0xd2); regmbc(0xd3); - regmbc(0xd4); regmbc(0xd5); regmbc(0xd6); - regmbc(0xd8); - REGMBC(0x14c) REGMBC(0x14e) REGMBC(0x150) - REGMBC(0x1a0) REGMBC(0x1d1) REGMBC(0x1ea) - REGMBC(0x1ec) REGMBC(0x1ece) - return; - case 'P': case 0x1e54: case 0x1e56: - regmbc('P'); REGMBC(0x1e54) REGMBC(0x1e56) - return; - case 'R': CASEMBC(0x154) CASEMBC(0x156) CASEMBC(0x158) - CASEMBC(0x1e58) CASEMBC(0x1e5e) - regmbc('R'); REGMBC(0x154) REGMBC(0x156) REGMBC(0x158) - REGMBC(0x1e58) REGMBC(0x1e5e) - return; - case 'S': CASEMBC(0x15a) CASEMBC(0x15c) CASEMBC(0x15e) - CASEMBC(0x160) CASEMBC(0x1e60) - regmbc('S'); REGMBC(0x15a) REGMBC(0x15c) - REGMBC(0x15e) REGMBC(0x160) REGMBC(0x1e60) - return; - case 'T': CASEMBC(0x162) CASEMBC(0x164) CASEMBC(0x166) - CASEMBC(0x1e6a) CASEMBC(0x1e6e) - regmbc('T'); REGMBC(0x162) REGMBC(0x164) - REGMBC(0x166) REGMBC(0x1e6a) REGMBC(0x1e6e) - return; - case 'U': case 0xd9: case 0xda: case 0xdb: case 0xdc: - CASEMBC(0x168) CASEMBC(0x16a) CASEMBC(0x16c) CASEMBC(0x16e) - CASEMBC(0x170) CASEMBC(0x172) CASEMBC(0x1af) CASEMBC(0x1d3) - CASEMBC(0x1ee6) - regmbc('U'); regmbc(0xd9); regmbc(0xda); - regmbc(0xdb); regmbc(0xdc); - REGMBC(0x168) REGMBC(0x16a) REGMBC(0x16c) - REGMBC(0x16e) REGMBC(0x170) REGMBC(0x172) - REGMBC(0x1af) REGMBC(0x1d3) REGMBC(0x1ee6) - return; - case 'V': CASEMBC(0x1e7c) - regmbc('V'); REGMBC(0x1e7c) - return; - case 'W': CASEMBC(0x174) CASEMBC(0x1e80) CASEMBC(0x1e82) - CASEMBC(0x1e84) CASEMBC(0x1e86) - regmbc('W'); REGMBC(0x174) REGMBC(0x1e80) - REGMBC(0x1e82) REGMBC(0x1e84) REGMBC(0x1e86) - return; - case 'X': CASEMBC(0x1e8a) CASEMBC(0x1e8c) - regmbc('X'); REGMBC(0x1e8a) REGMBC(0x1e8c) - return; - case 'Y': case 0xdd: - CASEMBC(0x176) CASEMBC(0x178) CASEMBC(0x1e8e) CASEMBC(0x1ef2) - CASEMBC(0x1ef6) CASEMBC(0x1ef8) - regmbc('Y'); regmbc(0xdd); - REGMBC(0x176) REGMBC(0x178) REGMBC(0x1e8e) - REGMBC(0x1ef2) REGMBC(0x1ef6) REGMBC(0x1ef8) - return; - case 'Z': CASEMBC(0x179) CASEMBC(0x17b) CASEMBC(0x17d) - CASEMBC(0x1b5) CASEMBC(0x1e90) CASEMBC(0x1e94) - regmbc('Z'); REGMBC(0x179) REGMBC(0x17b) - REGMBC(0x17d) REGMBC(0x1b5) REGMBC(0x1e90) - REGMBC(0x1e94) - return; - case 'a': case 0xe0: case 0xe1: case 0xe2: - case 0xe3: case 0xe4: case 0xe5: - CASEMBC(0x101) CASEMBC(0x103) CASEMBC(0x105) CASEMBC(0x1ce) - CASEMBC(0x1df) CASEMBC(0x1e1) CASEMBC(0x1ea3) - regmbc('a'); regmbc(0xe0); regmbc(0xe1); - regmbc(0xe2); regmbc(0xe3); regmbc(0xe4); - regmbc(0xe5); - REGMBC(0x101) REGMBC(0x103) REGMBC(0x105) - REGMBC(0x1ce) REGMBC(0x1df) REGMBC(0x1e1) - REGMBC(0x1ea3) - return; - case 'b': CASEMBC(0x1e03) CASEMBC(0x1e07) - regmbc('b'); REGMBC(0x1e03) REGMBC(0x1e07) - return; - case 'c': case 0xe7: - CASEMBC(0x107) CASEMBC(0x109) CASEMBC(0x10b) CASEMBC(0x10d) - regmbc('c'); regmbc(0xe7); - REGMBC(0x107) REGMBC(0x109) REGMBC(0x10b) - REGMBC(0x10d) - return; - case 'd': CASEMBC(0x10f) CASEMBC(0x111) CASEMBC(0x1e0b) - CASEMBC(0x1e0f) CASEMBC(0x1e11) - regmbc('d'); REGMBC(0x10f) REGMBC(0x111) - REGMBC(0x1e0b) REGMBC(0x1e0f) REGMBC(0x1e11) - return; - case 'e': case 0xe8: case 0xe9: case 0xea: case 0xeb: - CASEMBC(0x113) CASEMBC(0x115) CASEMBC(0x117) CASEMBC(0x119) - CASEMBC(0x11b) CASEMBC(0x1ebb) CASEMBC(0x1ebd) - regmbc('e'); regmbc(0xe8); regmbc(0xe9); - regmbc(0xea); regmbc(0xeb); - REGMBC(0x113) REGMBC(0x115) REGMBC(0x117) - REGMBC(0x119) REGMBC(0x11b) REGMBC(0x1ebb) - REGMBC(0x1ebd) - return; - case 'f': CASEMBC(0x1e1f) - regmbc('f'); REGMBC(0x1e1f) - return; - case 'g': CASEMBC(0x11d) CASEMBC(0x11f) CASEMBC(0x121) - CASEMBC(0x123) CASEMBC(0x1e5) CASEMBC(0x1e7) CASEMBC(0x1f5) - CASEMBC(0x1e21) - regmbc('g'); REGMBC(0x11d) REGMBC(0x11f) - REGMBC(0x121) REGMBC(0x123) REGMBC(0x1e5) - REGMBC(0x1e7) REGMBC(0x1f5) REGMBC(0x1e21) - return; - case 'h': CASEMBC(0x125) CASEMBC(0x127) CASEMBC(0x1e23) - CASEMBC(0x1e27) CASEMBC(0x1e29) CASEMBC(0x1e96) - regmbc('h'); REGMBC(0x125) REGMBC(0x127) - REGMBC(0x1e23) REGMBC(0x1e27) REGMBC(0x1e29) - REGMBC(0x1e96) - return; - case 'i': case 0xec: case 0xed: case 0xee: case 0xef: - CASEMBC(0x129) CASEMBC(0x12b) CASEMBC(0x12d) CASEMBC(0x12f) - CASEMBC(0x1d0) CASEMBC(0x1ec9) - regmbc('i'); regmbc(0xec); regmbc(0xed); - regmbc(0xee); regmbc(0xef); - REGMBC(0x129) REGMBC(0x12b) REGMBC(0x12d) - REGMBC(0x12f) REGMBC(0x1d0) REGMBC(0x1ec9) - return; - case 'j': CASEMBC(0x135) CASEMBC(0x1f0) - regmbc('j'); REGMBC(0x135) REGMBC(0x1f0) - return; - case 'k': CASEMBC(0x137) CASEMBC(0x1e9) CASEMBC(0x1e31) - CASEMBC(0x1e35) - regmbc('k'); REGMBC(0x137) REGMBC(0x1e9) - REGMBC(0x1e31) REGMBC(0x1e35) - return; - case 'l': CASEMBC(0x13a) CASEMBC(0x13c) CASEMBC(0x13e) - CASEMBC(0x140) CASEMBC(0x142) CASEMBC(0x1e3b) - regmbc('l'); REGMBC(0x13a) REGMBC(0x13c) - REGMBC(0x13e) REGMBC(0x140) REGMBC(0x142) - REGMBC(0x1e3b) - return; - case 'm': CASEMBC(0x1e3f) CASEMBC(0x1e41) - regmbc('m'); REGMBC(0x1e3f) REGMBC(0x1e41) - return; - case 'n': case 0xf1: - CASEMBC(0x144) CASEMBC(0x146) CASEMBC(0x148) CASEMBC(0x149) - CASEMBC(0x1e45) CASEMBC(0x1e49) - regmbc('n'); regmbc(0xf1); - REGMBC(0x144) REGMBC(0x146) REGMBC(0x148) - REGMBC(0x149) REGMBC(0x1e45) REGMBC(0x1e49) - return; - case 'o': case 0xf2: case 0xf3: case 0xf4: case 0xf5: - case 0xf6: case 0xf8: - CASEMBC(0x14d) CASEMBC(0x14f) CASEMBC(0x151) CASEMBC(0x1a1) - CASEMBC(0x1d2) CASEMBC(0x1eb) CASEMBC(0x1ed) CASEMBC(0x1ecf) - regmbc('o'); regmbc(0xf2); regmbc(0xf3); - regmbc(0xf4); regmbc(0xf5); regmbc(0xf6); - regmbc(0xf8); - REGMBC(0x14d) REGMBC(0x14f) REGMBC(0x151) - REGMBC(0x1a1) REGMBC(0x1d2) REGMBC(0x1eb) - REGMBC(0x1ed) REGMBC(0x1ecf) - return; - case 'p': CASEMBC(0x1e55) CASEMBC(0x1e57) - regmbc('p'); REGMBC(0x1e55) REGMBC(0x1e57) - return; - case 'r': CASEMBC(0x155) CASEMBC(0x157) CASEMBC(0x159) - CASEMBC(0x1e59) CASEMBC(0x1e5f) - regmbc('r'); REGMBC(0x155) REGMBC(0x157) REGMBC(0x159) - REGMBC(0x1e59) REGMBC(0x1e5f) - return; - case 's': CASEMBC(0x15b) CASEMBC(0x15d) CASEMBC(0x15f) - CASEMBC(0x161) CASEMBC(0x1e61) - regmbc('s'); REGMBC(0x15b) REGMBC(0x15d) - REGMBC(0x15f) REGMBC(0x161) REGMBC(0x1e61) - return; - case 't': CASEMBC(0x163) CASEMBC(0x165) CASEMBC(0x167) - CASEMBC(0x1e6b) CASEMBC(0x1e6f) CASEMBC(0x1e97) - regmbc('t'); REGMBC(0x163) REGMBC(0x165) REGMBC(0x167) - REGMBC(0x1e6b) REGMBC(0x1e6f) REGMBC(0x1e97) - return; - case 'u': case 0xf9: case 0xfa: case 0xfb: case 0xfc: - CASEMBC(0x169) CASEMBC(0x16b) CASEMBC(0x16d) CASEMBC(0x16f) - CASEMBC(0x171) CASEMBC(0x173) CASEMBC(0x1b0) CASEMBC(0x1d4) - CASEMBC(0x1ee7) - regmbc('u'); regmbc(0xf9); regmbc(0xfa); - regmbc(0xfb); regmbc(0xfc); - REGMBC(0x169) REGMBC(0x16b) REGMBC(0x16d) - REGMBC(0x16f) REGMBC(0x171) REGMBC(0x173) - REGMBC(0x1b0) REGMBC(0x1d4) REGMBC(0x1ee7) - return; - case 'v': CASEMBC(0x1e7d) - regmbc('v'); REGMBC(0x1e7d) - return; - case 'w': CASEMBC(0x175) CASEMBC(0x1e81) CASEMBC(0x1e83) - CASEMBC(0x1e85) CASEMBC(0x1e87) CASEMBC(0x1e98) - regmbc('w'); REGMBC(0x175) REGMBC(0x1e81) - REGMBC(0x1e83) REGMBC(0x1e85) REGMBC(0x1e87) - REGMBC(0x1e98) - return; - case 'x': CASEMBC(0x1e8b) CASEMBC(0x1e8d) - regmbc('x'); REGMBC(0x1e8b) REGMBC(0x1e8d) - return; - case 'y': case 0xfd: case 0xff: - CASEMBC(0x177) CASEMBC(0x1e8f) CASEMBC(0x1e99) - CASEMBC(0x1ef3) CASEMBC(0x1ef7) CASEMBC(0x1ef9) - regmbc('y'); regmbc(0xfd); regmbc(0xff); - REGMBC(0x177) REGMBC(0x1e8f) REGMBC(0x1e99) - REGMBC(0x1ef3) REGMBC(0x1ef7) REGMBC(0x1ef9) - return; - case 'z': CASEMBC(0x17a) CASEMBC(0x17c) CASEMBC(0x17e) - CASEMBC(0x1b6) CASEMBC(0x1e91) CASEMBC(0x1e95) - regmbc('z'); REGMBC(0x17a) REGMBC(0x17c) - REGMBC(0x17e) REGMBC(0x1b6) REGMBC(0x1e91) - REGMBC(0x1e95) - return; - } - } - regmbc(c); -} - -/* * Check for a collating element "[.a.]". "pp" points to the '['. * Returns a character. Zero means that no item was recognized. Otherwise * "pp" is advanced to after the item. @@ -1123,7 +426,7 @@ static int get_coll_element(char_u **pp) return 0; } -static int reg_cpo_lit; /* 'cpoptions' contains 'l' flag */ +static int reg_cpo_lit; // 'cpoptions' contains 'l' flag static void get_cpo_flags(void) { @@ -1139,10 +442,12 @@ static char_u *skip_anyof(char_u *p) { int l; - if (*p == '^') /* Complement of range. */ - ++p; - if (*p == ']' || *p == '-') - ++p; + if (*p == '^') { // Complement of range. + p++; + } + if (*p == ']' || *p == '-') { + p++; + } while (*p != NUL && *p != ']') { if ((l = utfc_ptr2len(p)) > 1) { p += l; @@ -1202,1549 +507,32 @@ char_u *skip_regexp(char_u *startp, int dirc, int magic, char_u **newp) break; } else if (p[0] == '\\' && p[1] != NUL) { if (dirc == '?' && newp != NULL && p[1] == '?') { - /* change "\?" to "?", make a copy first. */ + // change "\?" to "?", make a copy first. if (*newp == NULL) { *newp = vim_strsave(startp); p = *newp + (p - startp); } STRMOVE(p, p + 1); - } else - ++p; /* skip next character */ - if (*p == 'v') + } else { + p++; // skip next character + } + if (*p == 'v') { mymagic = MAGIC_ALL; - else if (*p == 'V') + } else if (*p == 'V') { mymagic = MAGIC_NONE; - } - } - return p; -} - -/// Return true if the back reference is legal. We must have seen the close -/// brace. -/// TODO(vim): Should also check that we don't refer to something repeated -/// (+*=): what instance of the repetition should we match? -static int seen_endbrace(int refnum) -{ - if (!had_endbrace[refnum]) { - char_u *p; - - // Trick: check if "@<=" or "@<!" follows, in which case - // the \1 can appear before the referenced match. - for (p = regparse; *p != NUL; p++) { - if (p[0] == '@' && p[1] == '<' && (p[2] == '!' || p[2] == '=')) { - break; - } - } - - if (*p == NUL) { - emsg(_("E65: Illegal back reference")); - rc_did_emsg = true; - return false; - } - } - return true; -} - -/* - * bt_regcomp() - compile a regular expression into internal code for the - * traditional back track matcher. - * Returns the program in allocated space. Returns NULL for an error. - * - * We can't allocate space until we know how big the compiled form will be, - * but we can't compile it (and thus know how big it is) until we've got a - * place to put the code. So we cheat: we compile it twice, once with code - * generation turned off and size counting turned on, and once "for real". - * This also means that we don't allocate space until we are sure that the - * thing really will compile successfully, and we never have to move the - * code and thus invalidate pointers into it. (Note that it has to be in - * one piece because free() must be able to free it all.) - * - * Whether upper/lower case is to be ignored is decided when executing the - * program, it does not matter here. - * - * Beware that the optimization-preparation code in here knows about some - * of the structure of the compiled regexp. - * "re_flags": RE_MAGIC and/or RE_STRING. - */ -static regprog_T *bt_regcomp(char_u *expr, int re_flags) -{ - char_u *scan; - char_u *longest; - int len; - int flags; - - if (expr == NULL) { - IEMSG_RET_NULL(_(e_null)); - } - - init_class_tab(); - - /* - * First pass: determine size, legality. - */ - regcomp_start(expr, re_flags); - regcode = JUST_CALC_SIZE; - regc(REGMAGIC); - if (reg(REG_NOPAREN, &flags) == NULL) - return NULL; - - /* Allocate space. */ - bt_regprog_T *r = xmalloc(sizeof(bt_regprog_T) + regsize); - r->re_in_use = false; - - /* - * Second pass: emit code. - */ - regcomp_start(expr, re_flags); - regcode = r->program; - regc(REGMAGIC); - if (reg(REG_NOPAREN, &flags) == NULL || reg_toolong) { - xfree(r); - if (reg_toolong) - EMSG_RET_NULL(_("E339: Pattern too long")); - return NULL; - } - - /* Dig out information for optimizations. */ - r->regstart = NUL; /* Worst-case defaults. */ - r->reganch = 0; - r->regmust = NULL; - r->regmlen = 0; - r->regflags = regflags; - if (flags & HASNL) - r->regflags |= RF_HASNL; - if (flags & HASLOOKBH) - r->regflags |= RF_LOOKBH; - /* Remember whether this pattern has any \z specials in it. */ - r->reghasz = re_has_z; - scan = r->program + 1; /* First BRANCH. */ - if (OP(regnext(scan)) == END) { /* Only one top-level choice. */ - scan = OPERAND(scan); - - /* Starting-point info. */ - if (OP(scan) == BOL || OP(scan) == RE_BOF) { - r->reganch++; - scan = regnext(scan); - } - - if (OP(scan) == EXACTLY) { - r->regstart = utf_ptr2char(OPERAND(scan)); - } else if (OP(scan) == BOW - || OP(scan) == EOW - || OP(scan) == NOTHING - || OP(scan) == MOPEN + 0 || OP(scan) == NOPEN - || OP(scan) == MCLOSE + 0 || OP(scan) == NCLOSE) { - char_u *regnext_scan = regnext(scan); - if (OP(regnext_scan) == EXACTLY) { - r->regstart = utf_ptr2char(OPERAND(regnext_scan)); } } - - /* - * If there's something expensive in the r.e., find the longest - * literal string that must appear and make it the regmust. Resolve - * ties in favor of later strings, since the regstart check works - * with the beginning of the r.e. and avoiding duplication - * strengthens checking. Not a strong reason, but sufficient in the - * absence of others. - */ - /* - * When the r.e. starts with BOW, it is faster to look for a regmust - * first. Used a lot for "#" and "*" commands. (Added by mool). - */ - if ((flags & SPSTART || OP(scan) == BOW || OP(scan) == EOW) - && !(flags & HASNL)) { - longest = NULL; - len = 0; - for (; scan != NULL; scan = regnext(scan)) - if (OP(scan) == EXACTLY && STRLEN(OPERAND(scan)) >= (size_t)len) { - longest = OPERAND(scan); - len = (int)STRLEN(OPERAND(scan)); - } - r->regmust = longest; - r->regmlen = len; - } } -#ifdef BT_REGEXP_DUMP - regdump(expr, r); -#endif - r->engine = &bt_regengine; - return (regprog_T *)r; -} - -/* - * Free a compiled regexp program, returned by bt_regcomp(). - */ -static void bt_regfree(regprog_T *prog) -{ - xfree(prog); -} - -/* - * Setup to parse the regexp. Used once to get the length and once to do it. - */ -static void -regcomp_start ( - char_u *expr, - int re_flags /* see vim_regcomp() */ -) -{ - initchr(expr); - if (re_flags & RE_MAGIC) - reg_magic = MAGIC_ON; - else - reg_magic = MAGIC_OFF; - reg_string = (re_flags & RE_STRING); - reg_strict = (re_flags & RE_STRICT); - get_cpo_flags(); - - num_complex_braces = 0; - regnpar = 1; - memset(had_endbrace, 0, sizeof(had_endbrace)); - regnzpar = 1; - re_has_z = 0; - regsize = 0L; - reg_toolong = false; - regflags = 0; - had_eol = false; + return p; } -/* - * Check if during the previous call to vim_regcomp the EOL item "$" has been - * found. This is messy, but it works fine. - */ -int vim_regcomp_had_eol(void) -{ - return had_eol; -} // variables used for parsing +static int prevchr_len; // byte length of previous char static int at_start; // True when on the first character static int prev_at_start; // True when on the second character /* - * Parse regular expression, i.e. main body or parenthesized thing. - * - * Caller must absorb opening parenthesis. - * - * Combining parenthesis handling with the base level of regular expression - * is a trifle forced, but the need to tie the tails of the branches to what - * follows makes it hard to avoid. - */ -static char_u * -reg ( - int paren, /* REG_NOPAREN, REG_PAREN, REG_NPAREN or REG_ZPAREN */ - int *flagp -) -{ - char_u *ret; - char_u *br; - char_u *ender; - int parno = 0; - int flags; - - *flagp = HASWIDTH; /* Tentatively. */ - - if (paren == REG_ZPAREN) { - /* Make a ZOPEN node. */ - if (regnzpar >= NSUBEXP) - EMSG_RET_NULL(_("E50: Too many \\z(")); - parno = regnzpar; - regnzpar++; - ret = regnode(ZOPEN + parno); - } else if (paren == REG_PAREN) { - /* Make a MOPEN node. */ - if (regnpar >= NSUBEXP) - EMSG2_RET_NULL(_("E51: Too many %s("), reg_magic == MAGIC_ALL); - parno = regnpar; - ++regnpar; - ret = regnode(MOPEN + parno); - } else if (paren == REG_NPAREN) { - /* Make a NOPEN node. */ - ret = regnode(NOPEN); - } else - ret = NULL; - - /* Pick up the branches, linking them together. */ - br = regbranch(&flags); - if (br == NULL) - return NULL; - if (ret != NULL) - regtail(ret, br); /* [MZ]OPEN -> first. */ - else - ret = br; - /* If one of the branches can be zero-width, the whole thing can. - * If one of the branches has * at start or matches a line-break, the - * whole thing can. */ - if (!(flags & HASWIDTH)) - *flagp &= ~HASWIDTH; - *flagp |= flags & (SPSTART | HASNL | HASLOOKBH); - while (peekchr() == Magic('|')) { - skipchr(); - br = regbranch(&flags); - if (br == NULL || reg_toolong) - return NULL; - regtail(ret, br); /* BRANCH -> BRANCH. */ - if (!(flags & HASWIDTH)) - *flagp &= ~HASWIDTH; - *flagp |= flags & (SPSTART | HASNL | HASLOOKBH); - } - - /* Make a closing node, and hook it on the end. */ - ender = regnode( - paren == REG_ZPAREN ? ZCLOSE + parno : - paren == REG_PAREN ? MCLOSE + parno : - paren == REG_NPAREN ? NCLOSE : END); - regtail(ret, ender); - - /* Hook the tails of the branches to the closing node. */ - for (br = ret; br != NULL; br = regnext(br)) - regoptail(br, ender); - - /* Check for proper termination. */ - if (paren != REG_NOPAREN && getchr() != Magic(')')) { - if (paren == REG_ZPAREN) - EMSG_RET_NULL(_("E52: Unmatched \\z(")); - else if (paren == REG_NPAREN) - EMSG2_RET_NULL(_(e_unmatchedpp), reg_magic == MAGIC_ALL); - else - EMSG2_RET_NULL(_(e_unmatchedp), reg_magic == MAGIC_ALL); - } else if (paren == REG_NOPAREN && peekchr() != NUL) { - if (curchr == Magic(')')) - EMSG2_RET_NULL(_(e_unmatchedpar), reg_magic == MAGIC_ALL); - else - EMSG_RET_NULL(_(e_trailing)); /* "Can't happen". */ - /* NOTREACHED */ - } - // Here we set the flag allowing back references to this set of - // parentheses. - if (paren == REG_PAREN) { - had_endbrace[parno] = true; // have seen the close paren - } - return ret; -} - -/* - * Parse one alternative of an | operator. - * Implements the & operator. - */ -static char_u *regbranch(int *flagp) -{ - char_u *ret; - char_u *chain = NULL; - char_u *latest; - int flags; - - *flagp = WORST | HASNL; /* Tentatively. */ - - ret = regnode(BRANCH); - for (;; ) { - latest = regconcat(&flags); - if (latest == NULL) - return NULL; - /* If one of the branches has width, the whole thing has. If one of - * the branches anchors at start-of-line, the whole thing does. - * If one of the branches uses look-behind, the whole thing does. */ - *flagp |= flags & (HASWIDTH | SPSTART | HASLOOKBH); - /* If one of the branches doesn't match a line-break, the whole thing - * doesn't. */ - *flagp &= ~HASNL | (flags & HASNL); - if (chain != NULL) - regtail(chain, latest); - if (peekchr() != Magic('&')) - break; - skipchr(); - regtail(latest, regnode(END)); /* operand ends */ - if (reg_toolong) - break; - reginsert(MATCH, latest); - chain = latest; - } - - return ret; -} - -/* - * Parse one alternative of an | or & operator. - * Implements the concatenation operator. - */ -static char_u *regconcat(int *flagp) -{ - char_u *first = NULL; - char_u *chain = NULL; - char_u *latest; - int flags; - int cont = true; - - *flagp = WORST; /* Tentatively. */ - - while (cont) { - switch (peekchr()) { - case NUL: - case Magic('|'): - case Magic('&'): - case Magic(')'): - cont = false; - break; - case Magic('Z'): - regflags |= RF_ICOMBINE; - skipchr_keepstart(); - break; - case Magic('c'): - regflags |= RF_ICASE; - skipchr_keepstart(); - break; - case Magic('C'): - regflags |= RF_NOICASE; - skipchr_keepstart(); - break; - case Magic('v'): - reg_magic = MAGIC_ALL; - skipchr_keepstart(); - curchr = -1; - break; - case Magic('m'): - reg_magic = MAGIC_ON; - skipchr_keepstart(); - curchr = -1; - break; - case Magic('M'): - reg_magic = MAGIC_OFF; - skipchr_keepstart(); - curchr = -1; - break; - case Magic('V'): - reg_magic = MAGIC_NONE; - skipchr_keepstart(); - curchr = -1; - break; - default: - latest = regpiece(&flags); - if (latest == NULL || reg_toolong) - return NULL; - *flagp |= flags & (HASWIDTH | HASNL | HASLOOKBH); - if (chain == NULL) /* First piece. */ - *flagp |= flags & SPSTART; - else - regtail(chain, latest); - chain = latest; - if (first == NULL) - first = latest; - break; - } - } - if (first == NULL) /* Loop ran zero times. */ - first = regnode(NOTHING); - return first; -} - -/* - * Parse something followed by possible [*+=]. - * - * Note that the branching code sequences used for = and the general cases - * of * and + are somewhat optimized: they use the same NOTHING node as - * both the endmarker for their branch list and the body of the last branch. - * It might seem that this node could be dispensed with entirely, but the - * endmarker role is not redundant. - */ -static char_u *regpiece(int *flagp) -{ - char_u *ret; - int op; - char_u *next; - int flags; - long minval; - long maxval; - - ret = regatom(&flags); - if (ret == NULL) - return NULL; - - op = peekchr(); - if (re_multi_type(op) == NOT_MULTI) { - *flagp = flags; - return ret; - } - /* default flags */ - *flagp = (WORST | SPSTART | (flags & (HASNL | HASLOOKBH))); - - skipchr(); - switch (op) { - case Magic('*'): - if (flags & SIMPLE) - reginsert(STAR, ret); - else { - /* Emit x* as (x&|), where & means "self". */ - reginsert(BRANCH, ret); /* Either x */ - regoptail(ret, regnode(BACK)); /* and loop */ - regoptail(ret, ret); /* back */ - regtail(ret, regnode(BRANCH)); /* or */ - regtail(ret, regnode(NOTHING)); /* null. */ - } - break; - - case Magic('+'): - if (flags & SIMPLE) - reginsert(PLUS, ret); - else { - /* Emit x+ as x(&|), where & means "self". */ - next = regnode(BRANCH); /* Either */ - regtail(ret, next); - regtail(regnode(BACK), ret); /* loop back */ - regtail(next, regnode(BRANCH)); /* or */ - regtail(ret, regnode(NOTHING)); /* null. */ - } - *flagp = (WORST | HASWIDTH | (flags & (HASNL | HASLOOKBH))); - break; - - case Magic('@'): - { - int lop = END; - int64_t nr = getdecchrs(); - - switch (no_Magic(getchr())) { - case '=': lop = MATCH; break; /* \@= */ - case '!': lop = NOMATCH; break; /* \@! */ - case '>': lop = SUBPAT; break; /* \@> */ - case '<': switch (no_Magic(getchr())) { - case '=': lop = BEHIND; break; /* \@<= */ - case '!': lop = NOBEHIND; break; /* \@<! */ - } - } - if (lop == END) - EMSG2_RET_NULL(_("E59: invalid character after %s@"), - reg_magic == MAGIC_ALL); - /* Look behind must match with behind_pos. */ - if (lop == BEHIND || lop == NOBEHIND) { - regtail(ret, regnode(BHPOS)); - *flagp |= HASLOOKBH; - } - regtail(ret, regnode(END)); /* operand ends */ - if (lop == BEHIND || lop == NOBEHIND) { - if (nr < 0) - nr = 0; /* no limit is same as zero limit */ - reginsert_nr(lop, (uint32_t)nr, ret); - } else - reginsert(lop, ret); - break; - } - - case Magic('?'): - case Magic('='): - /* Emit x= as (x|) */ - reginsert(BRANCH, ret); /* Either x */ - regtail(ret, regnode(BRANCH)); /* or */ - next = regnode(NOTHING); /* null. */ - regtail(ret, next); - regoptail(ret, next); - break; - - case Magic('{'): - if (!read_limits(&minval, &maxval)) - return NULL; - if (flags & SIMPLE) { - reginsert(BRACE_SIMPLE, ret); - reginsert_limits(BRACE_LIMITS, minval, maxval, ret); - } else { - if (num_complex_braces >= 10) - EMSG2_RET_NULL(_("E60: Too many complex %s{...}s"), - reg_magic == MAGIC_ALL); - reginsert(BRACE_COMPLEX + num_complex_braces, ret); - regoptail(ret, regnode(BACK)); - regoptail(ret, ret); - reginsert_limits(BRACE_LIMITS, minval, maxval, ret); - ++num_complex_braces; - } - if (minval > 0 && maxval > 0) - *flagp = (HASWIDTH | (flags & (HASNL | HASLOOKBH))); - break; - } - if (re_multi_type(peekchr()) != NOT_MULTI) { - /* Can't have a multi follow a multi. */ - if (peekchr() == Magic('*')) - sprintf((char *)IObuff, _("E61: Nested %s*"), - reg_magic >= MAGIC_ON ? "" : "\\"); - else - sprintf((char *)IObuff, _("E62: Nested %s%c"), - reg_magic == MAGIC_ALL ? "" : "\\", no_Magic(peekchr())); - EMSG_RET_NULL((char *)IObuff); - } - - return ret; -} - -/* When making changes to classchars also change nfa_classcodes. */ -static char_u *classchars = (char_u *)".iIkKfFpPsSdDxXoOwWhHaAlLuU"; -static int classcodes[] = { - ANY, IDENT, SIDENT, KWORD, SKWORD, - FNAME, SFNAME, PRINT, SPRINT, - WHITE, NWHITE, DIGIT, NDIGIT, - HEX, NHEX, OCTAL, NOCTAL, - WORD, NWORD, HEAD, NHEAD, - ALPHA, NALPHA, LOWER, NLOWER, - UPPER, NUPPER -}; - -/* - * Parse the lowest level. - * - * Optimization: gobbles an entire sequence of ordinary characters so that - * it can turn them into a single node, which is smaller to store and - * faster to run. Don't do this when one_exactly is set. - */ -static char_u *regatom(int *flagp) -{ - char_u *ret; - int flags; - int c; - char_u *p; - int extra = 0; - int save_prev_at_start = prev_at_start; - - *flagp = WORST; /* Tentatively. */ - - c = getchr(); - switch (c) { - case Magic('^'): - ret = regnode(BOL); - break; - - case Magic('$'): - ret = regnode(EOL); - had_eol = true; - break; - - case Magic('<'): - ret = regnode(BOW); - break; - - case Magic('>'): - ret = regnode(EOW); - break; - - case Magic('_'): - c = no_Magic(getchr()); - if (c == '^') { /* "\_^" is start-of-line */ - ret = regnode(BOL); - break; - } - if (c == '$') { /* "\_$" is end-of-line */ - ret = regnode(EOL); - had_eol = true; - break; - } - - extra = ADD_NL; - *flagp |= HASNL; - - /* "\_[" is character range plus newline */ - if (c == '[') - goto collection; - - // "\_x" is character class plus newline - FALLTHROUGH; - - /* - * Character classes. - */ - case Magic('.'): - case Magic('i'): - case Magic('I'): - case Magic('k'): - case Magic('K'): - case Magic('f'): - case Magic('F'): - case Magic('p'): - case Magic('P'): - case Magic('s'): - case Magic('S'): - case Magic('d'): - case Magic('D'): - case Magic('x'): - case Magic('X'): - case Magic('o'): - case Magic('O'): - case Magic('w'): - case Magic('W'): - case Magic('h'): - case Magic('H'): - case Magic('a'): - case Magic('A'): - case Magic('l'): - case Magic('L'): - case Magic('u'): - case Magic('U'): - p = vim_strchr(classchars, no_Magic(c)); - if (p == NULL) - EMSG_RET_NULL(_("E63: invalid use of \\_")); - /* When '.' is followed by a composing char ignore the dot, so that - * the composing char is matched here. */ - if (c == Magic('.') && utf_iscomposing(peekchr())) { - c = getchr(); - goto do_multibyte; - } - ret = regnode(classcodes[p - classchars] + extra); - *flagp |= HASWIDTH | SIMPLE; - break; - - case Magic('n'): - if (reg_string) { - /* In a string "\n" matches a newline character. */ - ret = regnode(EXACTLY); - regc(NL); - regc(NUL); - *flagp |= HASWIDTH | SIMPLE; - } else { - /* In buffer text "\n" matches the end of a line. */ - ret = regnode(NEWL); - *flagp |= HASWIDTH | HASNL; - } - break; - - case Magic('('): - if (one_exactly) - EMSG_ONE_RET_NULL; - ret = reg(REG_PAREN, &flags); - if (ret == NULL) - return NULL; - *flagp |= flags & (HASWIDTH | SPSTART | HASNL | HASLOOKBH); - break; - - case NUL: - case Magic('|'): - case Magic('&'): - case Magic(')'): - if (one_exactly) - EMSG_ONE_RET_NULL; - IEMSG_RET_NULL(_(e_internal)); // Supposed to be caught earlier. - // NOTREACHED - - case Magic('='): - case Magic('?'): - case Magic('+'): - case Magic('@'): - case Magic('{'): - case Magic('*'): - c = no_Magic(c); - sprintf((char *)IObuff, _("E64: %s%c follows nothing"), - (c == '*' ? reg_magic >= MAGIC_ON : reg_magic == MAGIC_ALL) - ? "" : "\\", c); - EMSG_RET_NULL((char *)IObuff); - /* NOTREACHED */ - - case Magic('~'): /* previous substitute pattern */ - if (reg_prev_sub != NULL) { - char_u *lp; - - ret = regnode(EXACTLY); - lp = reg_prev_sub; - while (*lp != NUL) - regc(*lp++); - regc(NUL); - if (*reg_prev_sub != NUL) { - *flagp |= HASWIDTH; - if ((lp - reg_prev_sub) == 1) - *flagp |= SIMPLE; - } - } else - EMSG_RET_NULL(_(e_nopresub)); - break; - - case Magic('1'): - case Magic('2'): - case Magic('3'): - case Magic('4'): - case Magic('5'): - case Magic('6'): - case Magic('7'): - case Magic('8'): - case Magic('9'): - { - int refnum; - - refnum = c - Magic('0'); - if (!seen_endbrace(refnum)) { - return NULL; - } - ret = regnode(BACKREF + refnum); - } - break; - - case Magic('z'): - { - c = no_Magic(getchr()); - switch (c) { - case '(': if ((reg_do_extmatch & REX_SET) == 0) - EMSG_RET_NULL(_(e_z_not_allowed)); - if (one_exactly) - EMSG_ONE_RET_NULL; - ret = reg(REG_ZPAREN, &flags); - if (ret == NULL) - return NULL; - *flagp |= flags & (HASWIDTH|SPSTART|HASNL|HASLOOKBH); - re_has_z = REX_SET; - break; - - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': if ((reg_do_extmatch & REX_USE) == 0) - EMSG_RET_NULL(_(e_z1_not_allowed)); - ret = regnode(ZREF + c - '0'); - re_has_z = REX_USE; - break; - - case 's': ret = regnode(MOPEN + 0); - if (!re_mult_next("\\zs")) { - return NULL; - } - break; - - case 'e': ret = regnode(MCLOSE + 0); - if (!re_mult_next("\\ze")) { - return NULL; - } - break; - - default: EMSG_RET_NULL(_("E68: Invalid character after \\z")); - } - } - break; - - case Magic('%'): - { - c = no_Magic(getchr()); - switch (c) { - /* () without a back reference */ - case '(': - if (one_exactly) - EMSG_ONE_RET_NULL; - ret = reg(REG_NPAREN, &flags); - if (ret == NULL) - return NULL; - *flagp |= flags & (HASWIDTH | SPSTART | HASNL | HASLOOKBH); - break; - - /* Catch \%^ and \%$ regardless of where they appear in the - * pattern -- regardless of whether or not it makes sense. */ - case '^': - ret = regnode(RE_BOF); - break; - - case '$': - ret = regnode(RE_EOF); - break; - - case '#': - ret = regnode(CURSOR); - break; - - case 'V': - ret = regnode(RE_VISUAL); - break; - - case 'C': - ret = regnode(RE_COMPOSING); - break; - - /* \%[abc]: Emit as a list of branches, all ending at the last - * branch which matches nothing. */ - case '[': - if (one_exactly) /* doesn't nest */ - EMSG_ONE_RET_NULL; - { - char_u *lastbranch; - char_u *lastnode = NULL; - char_u *br; - - ret = NULL; - while ((c = getchr()) != ']') { - if (c == NUL) - EMSG2_RET_NULL(_(e_missing_sb), - reg_magic == MAGIC_ALL); - br = regnode(BRANCH); - if (ret == NULL) { - ret = br; - } else { - regtail(lastnode, br); - if (reg_toolong) { - return NULL; - } - } - - ungetchr(); - one_exactly = true; - lastnode = regatom(flagp); - one_exactly = false; - if (lastnode == NULL) { - return NULL; - } - } - if (ret == NULL) - EMSG2_RET_NULL(_(e_empty_sb), - reg_magic == MAGIC_ALL); - lastbranch = regnode(BRANCH); - br = regnode(NOTHING); - if (ret != JUST_CALC_SIZE) { - regtail(lastnode, br); - regtail(lastbranch, br); - /* connect all branches to the NOTHING - * branch at the end */ - for (br = ret; br != lastnode; ) { - if (OP(br) == BRANCH) { - regtail(br, lastbranch); - if (reg_toolong) { - return NULL; - } - br = OPERAND(br); - } else - br = regnext(br); - } - } - *flagp &= ~(HASWIDTH | SIMPLE); - break; - } - - case 'd': /* %d123 decimal */ - case 'o': /* %o123 octal */ - case 'x': /* %xab hex 2 */ - case 'u': /* %uabcd hex 4 */ - case 'U': /* %U1234abcd hex 8 */ - { - int64_t i; - - switch (c) { - case 'd': i = getdecchrs(); break; - case 'o': i = getoctchrs(); break; - case 'x': i = gethexchrs(2); break; - case 'u': i = gethexchrs(4); break; - case 'U': i = gethexchrs(8); break; - default: i = -1; break; - } - - if (i < 0 || i > INT_MAX) { - EMSG2_RET_NULL(_("E678: Invalid character after %s%%[dxouU]"), - reg_magic == MAGIC_ALL); - } - if (use_multibytecode(i)) { - ret = regnode(MULTIBYTECODE); - } else { - ret = regnode(EXACTLY); - } - if (i == 0) { - regc(0x0a); - } else { - regmbc(i); - } - regc(NUL); - *flagp |= HASWIDTH; - break; - } - - default: - if (ascii_isdigit(c) || c == '<' || c == '>' - || c == '\'') { - uint32_t n = 0; - int cmp; - - cmp = c; - if (cmp == '<' || cmp == '>') - c = getchr(); - while (ascii_isdigit(c)) { - n = n * 10 + (uint32_t)(c - '0'); - c = getchr(); - } - if (c == '\'' && n == 0) { - /* "\%'m", "\%<'m" and "\%>'m": Mark */ - c = getchr(); - ret = regnode(RE_MARK); - if (ret == JUST_CALC_SIZE) - regsize += 2; - else { - *regcode++ = c; - *regcode++ = cmp; - } - break; - } else if (c == 'l' || c == 'c' || c == 'v') { - if (c == 'l') { - ret = regnode(RE_LNUM); - if (save_prev_at_start) { - at_start = true; - } - } else if (c == 'c') { - ret = regnode(RE_COL); - } else { - ret = regnode(RE_VCOL); - } - if (ret == JUST_CALC_SIZE) { - regsize += 5; - } else { - // put the number and the optional - // comparator after the opcode - regcode = re_put_uint32(regcode, n); - *regcode++ = cmp; - } - break; - } - } - - EMSG2_RET_NULL(_("E71: Invalid character after %s%%"), - reg_magic == MAGIC_ALL); - } - } - break; - - case Magic('['): -collection: - { - char_u *lp; - - /* - * If there is no matching ']', we assume the '[' is a normal - * character. This makes 'incsearch' and ":help [" work. - */ - lp = skip_anyof(regparse); - if (*lp == ']') { /* there is a matching ']' */ - int startc = -1; /* > 0 when next '-' is a range */ - int endc; - - /* - * In a character class, different parsing rules apply. - * Not even \ is special anymore, nothing is. - */ - if (*regparse == '^') { /* Complement of range. */ - ret = regnode(ANYBUT + extra); - regparse++; - } else - ret = regnode(ANYOF + extra); - - /* At the start ']' and '-' mean the literal character. */ - if (*regparse == ']' || *regparse == '-') { - startc = *regparse; - regc(*regparse++); - } - - while (*regparse != NUL && *regparse != ']') { - if (*regparse == '-') { - ++regparse; - /* The '-' is not used for a range at the end and - * after or before a '\n'. */ - if (*regparse == ']' || *regparse == NUL - || startc == -1 - || (regparse[0] == '\\' && regparse[1] == 'n')) { - regc('-'); - startc = '-'; /* [--x] is a range */ - } else { - /* Also accept "a-[.z.]" */ - endc = 0; - if (*regparse == '[') - endc = get_coll_element(®parse); - if (endc == 0) { - endc = mb_ptr2char_adv((const char_u **)®parse); - } - - /* Handle \o40, \x20 and \u20AC style sequences */ - if (endc == '\\' && !reg_cpo_lit) - endc = coll_get_char(); - - if (startc > endc) { - EMSG_RET_NULL(_(e_reverse_range)); - } - if (utf_char2len(startc) > 1 - || utf_char2len(endc) > 1) { - // Limit to a range of 256 chars - if (endc > startc + 256) { - EMSG_RET_NULL(_(e_large_class)); - } - while (++startc <= endc) { - regmbc(startc); - } - } else { - while (++startc <= endc) - regc(startc); - } - startc = -1; - } - } - /* - * Only "\]", "\^", "\]" and "\\" are special in Vi. Vim - * accepts "\t", "\e", etc., but only when the 'l' flag in - * 'cpoptions' is not included. - */ - else if (*regparse == '\\' - && (vim_strchr(REGEXP_INRANGE, regparse[1]) != NULL - || (!reg_cpo_lit - && vim_strchr(REGEXP_ABBR, - regparse[1]) != NULL))) { - regparse++; - if (*regparse == 'n') { - /* '\n' in range: also match NL */ - if (ret != JUST_CALC_SIZE) { - /* Using \n inside [^] does not change what - * matches. "[^\n]" is the same as ".". */ - if (*ret == ANYOF) { - *ret = ANYOF + ADD_NL; - *flagp |= HASNL; - } - /* else: must have had a \n already */ - } - regparse++; - startc = -1; - } else if (*regparse == 'd' - || *regparse == 'o' - || *regparse == 'x' - || *regparse == 'u' - || *regparse == 'U') { - startc = coll_get_char(); - if (startc == 0) - regc(0x0a); - else - regmbc(startc); - } else { - startc = backslash_trans(*regparse++); - regc(startc); - } - } else if (*regparse == '[') { - int c_class; - int cu; - - c_class = get_char_class(®parse); - startc = -1; - /* Characters assumed to be 8 bits! */ - switch (c_class) { - case CLASS_NONE: - c_class = get_equi_class(®parse); - if (c_class != 0) { - /* produce equivalence class */ - reg_equi_class(c_class); - } else if ((c_class = - get_coll_element(®parse)) != 0) { - /* produce a collating element */ - regmbc(c_class); - } else { - /* literal '[', allow [[-x] as a range */ - startc = *regparse++; - regc(startc); - } - break; - case CLASS_ALNUM: - for (cu = 1; cu < 128; cu++) { - if (isalnum(cu)) { - regmbc(cu); - } - } - break; - case CLASS_ALPHA: - for (cu = 1; cu < 128; cu++) { - if (isalpha(cu)) { - regmbc(cu); - } - } - break; - case CLASS_BLANK: - regc(' '); - regc('\t'); - break; - case CLASS_CNTRL: - for (cu = 1; cu <= 127; cu++) { - if (iscntrl(cu)) { - regmbc(cu); - } - } - break; - case CLASS_DIGIT: - for (cu = 1; cu <= 127; cu++) { - if (ascii_isdigit(cu)) { - regmbc(cu); - } - } - break; - case CLASS_GRAPH: - for (cu = 1; cu <= 127; cu++) { - if (isgraph(cu)) { - regmbc(cu); - } - } - break; - case CLASS_LOWER: - for (cu = 1; cu <= 255; cu++) { - if (mb_islower(cu) && cu != 170 && cu != 186) { - regmbc(cu); - } - } - break; - case CLASS_PRINT: - for (cu = 1; cu <= 255; cu++) { - if (vim_isprintc(cu)) { - regmbc(cu); - } - } - break; - case CLASS_PUNCT: - for (cu = 1; cu < 128; cu++) { - if (ispunct(cu)) { - regmbc(cu); - } - } - break; - case CLASS_SPACE: - for (cu = 9; cu <= 13; cu++) - regc(cu); - regc(' '); - break; - case CLASS_UPPER: - for (cu = 1; cu <= 255; cu++) { - if (mb_isupper(cu)) { - regmbc(cu); - } - } - break; - case CLASS_XDIGIT: - for (cu = 1; cu <= 255; cu++) { - if (ascii_isxdigit(cu)) { - regmbc(cu); - } - } - break; - case CLASS_TAB: - regc('\t'); - break; - case CLASS_RETURN: - regc('\r'); - break; - case CLASS_BACKSPACE: - regc('\b'); - break; - case CLASS_ESCAPE: - regc(ESC); - break; - case CLASS_IDENT: - for (cu = 1; cu <= 255; cu++) { - if (vim_isIDc(cu)) { - regmbc(cu); - } - } - break; - case CLASS_KEYWORD: - for (cu = 1; cu <= 255; cu++) { - if (reg_iswordc(cu)) { - regmbc(cu); - } - } - break; - case CLASS_FNAME: - for (cu = 1; cu <= 255; cu++) { - if (vim_isfilec(cu)) { - regmbc(cu); - } - } - break; - } - } else { - // produce a multibyte character, including any - // following composing characters. - startc = utf_ptr2char(regparse); - int len = utfc_ptr2len(regparse); - if (utf_char2len(startc) != len) { - // composing chars - startc = -1; - } - while (--len >= 0) { - regc(*regparse++); - } - } - } - regc(NUL); - prevchr_len = 1; /* last char was the ']' */ - if (*regparse != ']') - EMSG_RET_NULL(_(e_toomsbra)); /* Cannot happen? */ - skipchr(); /* let's be friends with the lexer again */ - *flagp |= HASWIDTH | SIMPLE; - break; - } else if (reg_strict) - EMSG2_RET_NULL(_(e_missingbracket), reg_magic > MAGIC_OFF); - } - FALLTHROUGH; - - default: - { - int len; - - /* A multi-byte character is handled as a separate atom if it's - * before a multi and when it's a composing char. */ - if (use_multibytecode(c)) { -do_multibyte: - ret = regnode(MULTIBYTECODE); - regmbc(c); - *flagp |= HASWIDTH | SIMPLE; - break; - } - - ret = regnode(EXACTLY); - - /* - * Append characters as long as: - * - there is no following multi, we then need the character in - * front of it as a single character operand - * - not running into a Magic character - * - "one_exactly" is not set - * But always emit at least one character. Might be a Multi, - * e.g., a "[" without matching "]". - */ - for (len = 0; c != NUL && (len == 0 - || (re_multi_type(peekchr()) == NOT_MULTI - && !one_exactly - && !is_Magic(c))); ++len) { - c = no_Magic(c); - { - regmbc(c); - { - int l; - - /* Need to get composing character too. */ - for (;; ) { - l = utf_ptr2len(regparse); - if (!utf_composinglike(regparse, regparse + l)) { - break; - } - regmbc(utf_ptr2char(regparse)); - skipchr(); - } - } - } - c = getchr(); - } - ungetchr(); - - regc(NUL); - *flagp |= HASWIDTH; - if (len == 1) - *flagp |= SIMPLE; - } - break; - } - - return ret; -} - -/// Used in a place where no * or \+ can follow. -static bool re_mult_next(char *what) -{ - if (re_multi_type(peekchr()) == MULTI_MULT) { - EMSG2_RET_FAIL(_("E888: (NFA regexp) cannot repeat %s"), what); - } - return true; -} - -// Return true if MULTIBYTECODE should be used instead of EXACTLY for -// character "c". -static bool use_multibytecode(int c) -{ - return utf_char2len(c) > 1 - && (re_multi_type(peekchr()) != NOT_MULTI - || utf_iscomposing(c)); -} - -/* - * Emit a node. - * Return pointer to generated code. - */ -static char_u *regnode(int op) -{ - char_u *ret; - - ret = regcode; - if (ret == JUST_CALC_SIZE) - regsize += 3; - else { - *regcode++ = op; - *regcode++ = NUL; /* Null "next" pointer. */ - *regcode++ = NUL; - } - return ret; -} - -/* - * Emit (if appropriate) a byte of code - */ -static void regc(int b) -{ - if (regcode == JUST_CALC_SIZE) - regsize++; - else - *regcode++ = b; -} - -/* - * Emit (if appropriate) a multi-byte character of code - */ -static void regmbc(int c) -{ - if (regcode == JUST_CALC_SIZE) { - regsize += utf_char2len(c); - } else { - regcode += utf_char2bytes(c, regcode); - } -} - -/* - * Insert an operator in front of already-emitted operand - * - * Means relocating the operand. - */ -static void reginsert(int op, char_u *opnd) -{ - char_u *src; - char_u *dst; - char_u *place; - - if (regcode == JUST_CALC_SIZE) { - regsize += 3; - return; - } - src = regcode; - regcode += 3; - dst = regcode; - while (src > opnd) - *--dst = *--src; - - place = opnd; /* Op node, where operand used to be. */ - *place++ = op; - *place++ = NUL; - *place = NUL; -} - -/* - * Insert an operator in front of already-emitted operand. - * Add a number to the operator. - */ -static void reginsert_nr(int op, long val, char_u *opnd) -{ - char_u *src; - char_u *dst; - char_u *place; - - if (regcode == JUST_CALC_SIZE) { - regsize += 7; - return; - } - src = regcode; - regcode += 7; - dst = regcode; - while (src > opnd) - *--dst = *--src; - - place = opnd; /* Op node, where operand used to be. */ - *place++ = op; - *place++ = NUL; - *place++ = NUL; - assert(val >= 0 && (uintmax_t)val <= UINT32_MAX); - re_put_uint32(place, (uint32_t)val); -} - -/* - * Insert an operator in front of already-emitted operand. - * The operator has the given limit values as operands. Also set next pointer. - * - * Means relocating the operand. - */ -static void reginsert_limits(int op, long minval, long maxval, char_u *opnd) -{ - char_u *src; - char_u *dst; - char_u *place; - - if (regcode == JUST_CALC_SIZE) { - regsize += 11; - return; - } - src = regcode; - regcode += 11; - dst = regcode; - while (src > opnd) - *--dst = *--src; - - place = opnd; /* Op node, where operand used to be. */ - *place++ = op; - *place++ = NUL; - *place++ = NUL; - assert(minval >= 0 && (uintmax_t)minval <= UINT32_MAX); - place = re_put_uint32(place, (uint32_t)minval); - assert(maxval >= 0 && (uintmax_t)maxval <= UINT32_MAX); - place = re_put_uint32(place, (uint32_t)maxval); - regtail(opnd, place); -} - -/* - * Write a four bytes number at "p" and return pointer to the next char. - */ -static char_u *re_put_uint32(char_u *p, uint32_t val) -{ - *p++ = (char_u) ((val >> 24) & 0377); - *p++ = (char_u) ((val >> 16) & 0377); - *p++ = (char_u) ((val >> 8) & 0377); - *p++ = (char_u) (val & 0377); - return p; -} - -// Set the next-pointer at the end of a node chain. -static void regtail(char_u *p, char_u *val) -{ - int offset; - - if (p == JUST_CALC_SIZE) { - return; - } - - // Find last node. - char_u *scan = p; - for (;; ) { - char_u *temp = regnext(scan); - if (temp == NULL) { - break; - } - scan = temp; - } - - if (OP(scan) == BACK) { - offset = (int)(scan - val); - } else { - offset = (int)(val - scan); - } - // When the offset uses more than 16 bits it can no longer fit in the two - // bytes available. Use a global flag to avoid having to check return - // values in too many places. - if (offset > 0xffff) { - reg_toolong = true; - } else { - *(scan + 1) = (char_u)(((unsigned)offset >> 8) & 0377); - *(scan + 2) = (char_u)(offset & 0377); - } -} - -/* - * Like regtail, on item after a BRANCH; nop if none. - */ -static void regoptail(char_u *p, char_u *val) -{ - /* When op is neither BRANCH nor BRACE_COMPLEX0-9, it is "operandless" */ - if (p == NULL || p == JUST_CALC_SIZE - || (OP(p) != BRANCH - && (OP(p) < BRACE_COMPLEX || OP(p) > BRACE_COMPLEX + 9))) - return; - regtail(OPERAND(p), val); -} - -/* - * Functions for getting characters from the regexp input. - */ - -/* * Start parsing at "str". */ static void initchr(char_u *str) @@ -2805,9 +593,10 @@ static int peekchr(void) case '.': case '[': case '~': - /* magic when 'magic' is on */ - if (reg_magic >= MAGIC_ON) + // magic when 'magic' is on + if (reg_magic >= MAGIC_ON) { curchr = Magic(curchr); + } break; case '(': case ')': @@ -2822,18 +611,19 @@ static int peekchr(void) case '|': case '<': case '>': - case '#': /* future ext. */ - case '"': /* future ext. */ - case '\'': /* future ext. */ - case ',': /* future ext. */ - case '-': /* future ext. */ - case ':': /* future ext. */ - case ';': /* future ext. */ - case '`': /* future ext. */ - case '/': /* Can't be used in / command */ - /* magic only after "\v" */ - if (reg_magic == MAGIC_ALL) + case '#': // future ext. + case '"': // future ext. + case '\'': // future ext. + case ',': // future ext. + case '-': // future ext. + case ':': // future ext. + case ';': // future ext. + case '`': // future ext. + case '/': // Can't be used in / command + // magic only after "\v" + if (reg_magic == MAGIC_ALL) { curchr = Magic(curchr); + } break; case '*': // * is not magic as the very first character, eg "?*ptr", when @@ -2900,18 +690,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" @@ -2950,11 +736,12 @@ static int peekchr(void) */ static void skipchr(void) { - /* peekchr() eats a backslash, do the same here */ - if (*regparse == '\\') + // peekchr() eats a backslash, do the same here + if (*regparse == '\\') { prevchr_len = 1; - else + } else { prevchr_len = 0; + } if (regparse[prevchr_len] != NUL) { // Exclude composing chars that utfc_ptr2len does include. prevchr_len += utf_ptr2len(regparse + prevchr_len); @@ -2964,7 +751,7 @@ static void skipchr(void) at_start = false; prevprevchr = prevchr; prevchr = curchr; - curchr = nextchr; /* use previously unget char, or -1 */ + curchr = nextchr; // use previously unget char, or -1 nextchr = -1; } @@ -3057,8 +844,8 @@ static int64_t getdecchrs(void) break; nr *= 10; nr += c - '0'; - ++regparse; - curchr = -1; /* no longer valid */ + regparse++; + curchr = -1; // no longer valid } if (i == 0) @@ -3094,29 +881,6 @@ static int64_t getoctchrs(void) return nr; } -/* - * Get a number after a backslash that is inside []. - * When nothing is recognized return a backslash. - */ -static int coll_get_char(void) -{ - int64_t nr = -1; - - switch (*regparse++) { - case 'd': nr = getdecchrs(); break; - case 'o': nr = getoctchrs(); break; - case 'x': nr = gethexchrs(2); break; - case 'u': nr = gethexchrs(4); break; - case 'U': nr = gethexchrs(8); break; - } - if (nr < 0 || nr > INT_MAX) { - // If getting the number fails be backwards compatible: the character - // is a backslash. - regparse--; - nr = '\\'; - } - return nr; -} /* * read_limits - Read two integers to be taken as a minimum and maximum. @@ -3152,9 +916,7 @@ static int read_limits(long *minval, long *maxval) regparse++; // Allow either \{...} or \{...\} } if (*regparse != '}') { - sprintf((char *)IObuff, _("E554: Syntax error in %s{...}"), - reg_magic == MAGIC_ALL ? "" : "\\"); - EMSG_RET_FAIL((char *)IObuff); + EMSG2_RET_FAIL(_("E554: Syntax error in %s{...}"), reg_magic == MAGIC_ALL); } /* @@ -3166,7 +928,7 @@ static int read_limits(long *minval, long *maxval) *minval = *maxval; *maxval = tmp; } - skipchr(); /* let's be friends with the lexer again */ + skipchr(); // let's be friends with the lexer again return OK; } @@ -3178,22 +940,6 @@ static int read_limits(long *minval, long *maxval) * Global work variables for vim_regexec(). */ -/* Save the sub-expressions before attempting a match. */ -#define save_se(savep, posp, pp) \ - REG_MULTI ? save_se_multi((savep), (posp)) : save_se_one((savep), (pp)) - -/* After a failed match restore the sub-expressions. */ -#define restore_se(savep, posp, pp) { \ - if (REG_MULTI) \ - *(posp) = (savep)->se_u.pos; \ - else \ - *(pp) = (savep)->se_u.ptr; } - - -#ifdef REGEXP_DEBUG -int regnarrate = 0; -#endif - // Sometimes need to save a copy of a line. Since alloc()/free() is very // slow, we keep one allocated piece of memory and only re-allocate it when // it's too small. It's freed in bt_regexec_both() when finished. @@ -3231,7 +977,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 @@ -3269,41 +1015,6 @@ typedef struct { static regexec_T rex; static bool rex_in_use = false; -/* - * "regstack" and "backpos" are used by regmatch(). They are kept over calls - * to avoid invoking malloc() and free() often. - * "regstack" is a stack with regitem_T items, sometimes preceded by regstar_T - * or regbehind_T. - * "backpos_T" is a table with backpos_T for BACK - */ -static garray_T regstack = GA_EMPTY_INIT_VALUE; -static garray_T backpos = GA_EMPTY_INIT_VALUE; - -/* - * Both for regstack and backpos tables we use the following strategy of - * allocation (to reduce malloc/free calls): - * - Initial size is fairly small. - * - When needed, the tables are grown bigger (8 times at first, double after - * that). - * - After executing the match we free the memory only if the array has grown. - * Thus the memory is kept allocated when it's at the initial size. - * This makes it fast while not keeping a lot of memory allocated. - * A three times speed increase was observed when using many simple patterns. - */ -#define REGSTACK_INITIAL 2048 -#define BACKPOS_INITIAL 64 - -#if defined(EXITFREE) -void free_regexp_stuff(void) -{ - ga_clear(®stack); - ga_clear(&backpos); - xfree(reg_tofree); - xfree(reg_prev_sub); -} - -#endif - // Return true if character 'c' is included in 'iskeyword' option for // "reg_buf" buffer. static bool reg_iswordc(int c) @@ -3328,312 +1039,15 @@ static char_u *reg_getline(linenr_T lnum) return ml_get_buf(rex.reg_buf, rex.reg_firstlnum + lnum, false); } -static regsave_T behind_pos; - -static char_u *reg_startzp[NSUBEXP]; /* Workspace to mark beginning */ -static char_u *reg_endzp[NSUBEXP]; /* and end of \z(...\) matches */ -static lpos_T reg_startzpos[NSUBEXP]; /* idem, beginning pos */ -static lpos_T reg_endzpos[NSUBEXP]; /* idem, end pos */ +static char_u *reg_startzp[NSUBEXP]; // Workspace to mark beginning +static char_u *reg_endzp[NSUBEXP]; // and end of \z(...\) matches +static lpos_T reg_startzpos[NSUBEXP]; // idem, beginning pos +static lpos_T reg_endzpos[NSUBEXP]; // idem, end pos // true if using multi-line regexp. #define REG_MULTI (rex.reg_match == NULL) /* - * Match a regexp against a string. - * "rmp->regprog" is a compiled regexp as returned by vim_regcomp(). - * Uses curbuf for line count and 'iskeyword'. - * If "line_lbr" is true, consider a "\n" in "line" to be a line break. - * - * Returns 0 for failure, number of lines contained in the match otherwise. - */ -static int -bt_regexec_nl ( - regmatch_T *rmp, - char_u *line, /* string to match against */ - colnr_T col, /* column to start looking for match */ - bool line_lbr -) -{ - rex.reg_match = rmp; - rex.reg_mmatch = NULL; - rex.reg_maxline = 0; - rex.reg_line_lbr = line_lbr; - rex.reg_buf = curbuf; - rex.reg_win = NULL; - rex.reg_ic = rmp->rm_ic; - rex.reg_icombine = false; - rex.reg_maxcol = 0; - - long r = bt_regexec_both(line, col, NULL, NULL); - assert(r <= INT_MAX); - return (int)r; -} - -/// Wrapper around strchr which accounts for case-insensitive searches and -/// non-ASCII characters. -/// -/// This function is used a lot for simple searches, keep it fast! -/// -/// @param s string to search -/// @param c character to find in @a s -/// -/// @return NULL if no match, otherwise pointer to the position in @a s -static inline char_u *cstrchr(const char_u *const s, const int c) - FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL - FUNC_ATTR_ALWAYS_INLINE -{ - if (!rex.reg_ic) { - return vim_strchr(s, c); - } - - // Use folded case for UTF-8, slow! For ASCII use libc strpbrk which is - // expected to be highly optimized. - if (c > 0x80) { - const int folded_c = utf_fold(c); - for (const char_u *p = s; *p != NUL; p += utfc_ptr2len(p)) { - if (utf_fold(utf_ptr2char(p)) == folded_c) { - return (char_u *)p; - } - } - return NULL; - } - - int cc; - if (ASCII_ISUPPER(c)) { - cc = TOLOWER_ASC(c); - } else if (ASCII_ISLOWER(c)) { - cc = TOUPPER_ASC(c); - } else { - return vim_strchr(s, c); - } - - char tofind[] = { (char)c, (char)cc, NUL }; - return (char_u *)strpbrk((const char *)s, tofind); -} - -/// Matches a regexp against multiple lines. -/// "rmp->regprog" is a compiled regexp as returned by vim_regcomp(). -/// Uses curbuf for line count and 'iskeyword'. -/// -/// @param win Window in which to search or NULL -/// @param buf Buffer in which to search -/// @param lnum Number of line to start looking for match -/// @param col Column to start looking for match -/// @param tm Timeout limit or NULL -/// -/// @return zero if there is no match and number of lines contained in the match -/// otherwise. -static long bt_regexec_multi(regmmatch_T *rmp, win_T *win, buf_T *buf, - linenr_T lnum, colnr_T col, - proftime_T *tm, int *timed_out) -{ - rex.reg_match = NULL; - rex.reg_mmatch = rmp; - rex.reg_buf = buf; - rex.reg_win = win; - rex.reg_firstlnum = lnum; - rex.reg_maxline = rex.reg_buf->b_ml.ml_line_count - lnum; - rex.reg_line_lbr = false; - rex.reg_ic = rmp->rmm_ic; - rex.reg_icombine = false; - rex.reg_maxcol = rmp->rmm_maxcol; - - return bt_regexec_both(NULL, col, tm, timed_out); -} - -/// Match a regexp against a string ("line" points to the string) or multiple -/// lines (if "line" is NULL, use reg_getline()). -/// @return 0 for failure, or number of lines contained in the match. -static long bt_regexec_both(char_u *line, - colnr_T col, // column to start search - proftime_T *tm, // timeout limit or NULL - int *timed_out) // flag set on timeout or NULL -{ - bt_regprog_T *prog; - char_u *s; - long retval = 0L; - - /* Create "regstack" and "backpos" if they are not allocated yet. - * We allocate *_INITIAL amount of bytes first and then set the grow size - * to much bigger value to avoid many malloc calls in case of deep regular - * expressions. */ - if (regstack.ga_data == NULL) { - /* Use an item size of 1 byte, since we push different things - * onto the regstack. */ - ga_init(®stack, 1, REGSTACK_INITIAL); - ga_grow(®stack, REGSTACK_INITIAL); - ga_set_growsize(®stack, REGSTACK_INITIAL * 8); - } - - if (backpos.ga_data == NULL) { - ga_init(&backpos, sizeof(backpos_T), BACKPOS_INITIAL); - ga_grow(&backpos, BACKPOS_INITIAL); - ga_set_growsize(&backpos, BACKPOS_INITIAL * 8); - } - - if (REG_MULTI) { - prog = (bt_regprog_T *)rex.reg_mmatch->regprog; - line = reg_getline((linenr_T)0); - rex.reg_startpos = rex.reg_mmatch->startpos; - rex.reg_endpos = rex.reg_mmatch->endpos; - } else { - prog = (bt_regprog_T *)rex.reg_match->regprog; - rex.reg_startp = rex.reg_match->startp; - rex.reg_endp = rex.reg_match->endp; - } - - /* Be paranoid... */ - if (prog == NULL || line == NULL) { - iemsg(_(e_null)); - goto theend; - } - - /* Check validity of program. */ - if (prog_magic_wrong()) - goto theend; - - // If the start column is past the maximum column: no need to try. - if (rex.reg_maxcol > 0 && col >= rex.reg_maxcol) { - goto theend; - } - - // If pattern contains "\c" or "\C": overrule value of rex.reg_ic - if (prog->regflags & RF_ICASE) { - rex.reg_ic = true; - } else if (prog->regflags & RF_NOICASE) { - rex.reg_ic = false; - } - - // If pattern contains "\Z" overrule value of rex.reg_icombine - if (prog->regflags & RF_ICOMBINE) { - rex.reg_icombine = true; - } - - /* If there is a "must appear" string, look for it. */ - if (prog->regmust != NULL) { - int c = utf_ptr2char(prog->regmust); - s = line + col; - - // This is used very often, esp. for ":global". Use two versions of - // the loop to avoid overhead of conditions. - if (!rex.reg_ic) { - while ((s = vim_strchr(s, c)) != NULL) { - if (cstrncmp(s, prog->regmust, &prog->regmlen) == 0) { - break; // Found it. - } - MB_PTR_ADV(s); - } - } else { - while ((s = cstrchr(s, c)) != NULL) { - if (cstrncmp(s, prog->regmust, &prog->regmlen) == 0) { - break; // Found it. - } - MB_PTR_ADV(s); - } - } - if (s == NULL) { // Not present. - goto theend; - } - } - - rex.line = line; - rex.lnum = 0; - reg_toolong = false; - - /* Simplest case: Anchored match need be tried only once. */ - if (prog->reganch) { - int c = utf_ptr2char(rex.line + col); - if (prog->regstart == NUL - || prog->regstart == c - || (rex.reg_ic - && (utf_fold(prog->regstart) == utf_fold(c) - || (c < 255 && prog->regstart < 255 - && mb_tolower(prog->regstart) == mb_tolower(c))))) { - retval = regtry(prog, col, tm, timed_out); - } else { - retval = 0; - } - } else { - int tm_count = 0; - /* Messy cases: unanchored match. */ - while (!got_int) { - if (prog->regstart != NUL) { - // Skip until the char we know it must start with. - s = cstrchr(rex.line + col, prog->regstart); - if (s == NULL) { - retval = 0; - break; - } - col = (int)(s - rex.line); - } - - // Check for maximum column to try. - if (rex.reg_maxcol > 0 && col >= rex.reg_maxcol) { - retval = 0; - break; - } - - retval = regtry(prog, col, tm, timed_out); - if (retval > 0) { - break; - } - - // if not currently on the first line, get it again - if (rex.lnum != 0) { - rex.lnum = 0; - rex.line = reg_getline((linenr_T)0); - } - if (rex.line[col] == NUL) { - break; - } - col += utfc_ptr2len(rex.line + col); - // Check for timeout once in a twenty times to avoid overhead. - if (tm != NULL && ++tm_count == 20) { - tm_count = 0; - if (profile_passed_limit(*tm)) { - if (timed_out != NULL) { - *timed_out = true; - } - break; - } - } - } - } - -theend: - /* Free "reg_tofree" when it's a bit big. - * Free regstack and backpos if they are bigger than their initial size. */ - if (reg_tofreelen > 400) { - XFREE_CLEAR(reg_tofree); - } - if (regstack.ga_maxlen > REGSTACK_INITIAL) - ga_clear(®stack); - if (backpos.ga_maxlen > BACKPOS_INITIAL) - ga_clear(&backpos); - - if (retval > 0) { - // Make sure the end is never before the start. Can happen when \zs - // and \ze are used. - if (REG_MULTI) { - const lpos_T *const start = &rex.reg_mmatch->startpos[0]; - const lpos_T *const end = &rex.reg_mmatch->endpos[0]; - - if (end->lnum < start->lnum - || (end->lnum == start->lnum && end->col < start->col)) { - rex.reg_mmatch->endpos[0] = rex.reg_mmatch->startpos[0]; - } - } else { - if (rex.reg_match->endp[0] < rex.reg_match->startp[0]) { - rex.reg_match->endp[0] = rex.reg_match->startp[0]; - } - } - } - - return retval; -} - - -/* * Create a new extmatch and mark it as referenced once. */ static reg_extmatch_T *make_extmatch(void) @@ -3669,75 +1083,6 @@ void unref_extmatch(reg_extmatch_T *em) } } -/// Try match of "prog" with at rex.line["col"]. -/// @returns 0 for failure, or number of lines contained in the match. -static long regtry(bt_regprog_T *prog, - colnr_T col, - proftime_T *tm, // timeout limit or NULL - int *timed_out) // flag set on timeout or NULL -{ - rex.input = rex.line + col; - rex.need_clear_subexpr = true; - // Clear the external match subpointers if necessaey. - rex.need_clear_zsubexpr = (prog->reghasz == REX_SET); - - if (regmatch(prog->program + 1, tm, timed_out) == 0) { - return 0; - } - - cleanup_subexpr(); - if (REG_MULTI) { - if (rex.reg_startpos[0].lnum < 0) { - rex.reg_startpos[0].lnum = 0; - rex.reg_startpos[0].col = col; - } - if (rex.reg_endpos[0].lnum < 0) { - rex.reg_endpos[0].lnum = rex.lnum; - rex.reg_endpos[0].col = (int)(rex.input - rex.line); - } else { - // Use line number of "\ze". - rex.lnum = rex.reg_endpos[0].lnum; - } - } else { - if (rex.reg_startp[0] == NULL) { - rex.reg_startp[0] = rex.line + col; - } - if (rex.reg_endp[0] == NULL) { - rex.reg_endp[0] = rex.input; - } - } - /* Package any found \z(...\) matches for export. Default is none. */ - unref_extmatch(re_extmatch_out); - re_extmatch_out = NULL; - - if (prog->reghasz == REX_SET) { - int i; - - cleanup_zsubexpr(); - re_extmatch_out = make_extmatch(); - for (i = 0; i < NSUBEXP; i++) { - if (REG_MULTI) { - /* Only accept single line matches. */ - if (reg_startzpos[i].lnum >= 0 - && reg_endzpos[i].lnum == reg_startzpos[i].lnum - && reg_endzpos[i].col >= reg_startzpos[i].col) { - re_extmatch_out->matches[i] = - vim_strnsave(reg_getline(reg_startzpos[i].lnum) - + reg_startzpos[i].col, - reg_endzpos[i].col - - reg_startzpos[i].col); - } - } else { - if (reg_startzp[i] != NULL && reg_endzp[i] != NULL) - re_extmatch_out->matches[i] = - vim_strnsave(reg_startzp[i], reg_endzp[i] - reg_startzp[i]); - } - } - } - return 1 + rex.lnum; -} - - // Get class of previous character. static int reg_prev_class(void) { @@ -3793,8 +1138,8 @@ static bool reg_match_visual(void) return false; } + col = (colnr_T)(rex.input - rex.line); if (mode == 'v') { - col = (colnr_T)(rex.input - rex.line); if ((lnum == top.lnum && col < top.col) || (lnum == bot.lnum && col >= bot.col + (*p_sel != 'e'))) { return false; @@ -3809,8 +1154,12 @@ static bool reg_match_visual(void) if (top.col == MAXCOL || bot.col == MAXCOL || curswant == MAXCOL) { end = MAXCOL; } - unsigned int cols_u = win_linetabsize(wp, rex.line, - (colnr_T)(rex.input - rex.line)); + + // getvvcol() flushes rex.line, need to get it again + rex.line = reg_getline(rex.lnum); + rex.input = rex.line + col; + + unsigned int cols_u = win_linetabsize(wp, rex.line, col); assert(cols_u <= MAXCOL); colnr_T cols = (colnr_T)cols_u; if (cols < start || cols > end - (*p_sel == 'e')) { @@ -3820,1803 +1169,6 @@ static bool reg_match_visual(void) return true; } -#define ADVANCE_REGINPUT() MB_PTR_ADV(rex.input) - -/* - * The arguments from BRACE_LIMITS are stored here. They are actually local - * to regmatch(), but they are here to reduce the amount of stack space used - * (it can be called recursively many times). - */ -static long bl_minval; -static long bl_maxval; - -/// Main matching routine -/// -/// Conceptually the strategy is simple: Check to see whether the current node -/// matches, push an item onto the regstack and loop to see whether the rest -/// matches, and then act accordingly. In practice we make some effort to -/// avoid using the regstack, in particular by going through "ordinary" nodes -/// (that don't need to know whether the rest of the match failed) by a nested -/// loop. -/// -/// Returns true when there is a match. Leaves rex.input and rex.lnum -/// just after the last matched character. -/// Returns false when there is no match. Leaves rex.input and rex.lnum in an -/// undefined state! -static bool regmatch( - char_u *scan, // Current node. - proftime_T *tm, // timeout limit or NULL - int *timed_out // flag set on timeout or NULL -) -{ - char_u *next; /* Next node. */ - int op; - int c; - regitem_T *rp; - int no; - int status; // one of the RA_ values: - int tm_count = 0; -#define RA_FAIL 1 // something failed, abort -#define RA_CONT 2 // continue in inner loop -#define RA_BREAK 3 // break inner loop -#define RA_MATCH 4 // successful match -#define RA_NOMATCH 5 // didn't match - - // Make "regstack" and "backpos" empty. They are allocated and freed in - // bt_regexec_both() to reduce malloc()/free() calls. - regstack.ga_len = 0; - backpos.ga_len = 0; - - /* - * Repeat until "regstack" is empty. - */ - for (;; ) { - /* Some patterns may take a long time to match, e.g., "\([a-z]\+\)\+Q". - * Allow interrupting them with CTRL-C. */ - fast_breakcheck(); - -#ifdef REGEXP_DEBUG - if (scan != NULL && regnarrate) { - mch_errmsg((char *)regprop(scan)); - mch_errmsg("(\n"); - } -#endif - - /* - * Repeat for items that can be matched sequentially, without using the - * regstack. - */ - for (;; ) { - if (got_int || scan == NULL) { - status = RA_FAIL; - break; - } - // Check for timeout once in a 100 times to avoid overhead. - if (tm != NULL && ++tm_count == 100) { - tm_count = 0; - if (profile_passed_limit(*tm)) { - if (timed_out != NULL) { - *timed_out = true; - } - status = RA_FAIL; - break; - } - } - status = RA_CONT; - -#ifdef REGEXP_DEBUG - if (regnarrate) { - mch_errmsg((char *)regprop(scan)); - mch_errmsg("...\n"); - if (re_extmatch_in != NULL) { - int i; - - mch_errmsg(_("External submatches:\n")); - for (i = 0; i < NSUBEXP; i++) { - mch_errmsg(" \""); - if (re_extmatch_in->matches[i] != NULL) - mch_errmsg((char *)re_extmatch_in->matches[i]); - mch_errmsg("\"\n"); - } - } - } -#endif - next = regnext(scan); - - op = OP(scan); - // Check for character class with NL added. - if (!rex.reg_line_lbr && WITH_NL(op) && REG_MULTI - && *rex.input == NUL && rex.lnum <= rex.reg_maxline) { - reg_nextline(); - } else if (rex.reg_line_lbr && WITH_NL(op) && *rex.input == '\n') { - ADVANCE_REGINPUT(); - } else { - if (WITH_NL(op)) { - op -= ADD_NL; - } - c = utf_ptr2char(rex.input); - switch (op) { - case BOL: - if (rex.input != rex.line) { - status = RA_NOMATCH; - } - break; - - case EOL: - if (c != NUL) { - status = RA_NOMATCH; - } - break; - - case RE_BOF: - // We're not at the beginning of the file when below the first - // line where we started, not at the start of the line or we - // didn't start at the first line of the buffer. - if (rex.lnum != 0 || rex.input != rex.line - || (REG_MULTI && rex.reg_firstlnum > 1)) { - status = RA_NOMATCH; - } - break; - - case RE_EOF: - if (rex.lnum != rex.reg_maxline || c != NUL) { - status = RA_NOMATCH; - } - break; - - case CURSOR: - // Check if the buffer is in a window and compare the - // rex.reg_win->w_cursor position to the match position. - if (rex.reg_win == NULL - || (rex.lnum + rex.reg_firstlnum != rex.reg_win->w_cursor.lnum) - || ((colnr_T)(rex.input - rex.line) != - rex.reg_win->w_cursor.col)) { - status = RA_NOMATCH; - } - break; - - case RE_MARK: - /* Compare the mark position to the match position. */ - { - int mark = OPERAND(scan)[0]; - int cmp = OPERAND(scan)[1]; - pos_T *pos; - - pos = getmark_buf(rex.reg_buf, mark, false); - if (pos == NULL // mark doesn't exist - || pos->lnum <= 0) { // mark isn't set in reg_buf - status = RA_NOMATCH; - } else { - const colnr_T pos_col = pos->lnum == rex.lnum + rex.reg_firstlnum - && pos->col == MAXCOL - ? (colnr_T)STRLEN(reg_getline(pos->lnum - rex.reg_firstlnum)) - : pos->col; - - if (pos->lnum == rex.lnum + rex.reg_firstlnum - ? (pos_col == (colnr_T)(rex.input - rex.line) - ? (cmp == '<' || cmp == '>') - : (pos_col < (colnr_T)(rex.input - rex.line) - ? cmp != '>' - : cmp != '<')) - : (pos->lnum < rex.lnum + rex.reg_firstlnum - ? cmp != '>' - : cmp != '<')) { - status = RA_NOMATCH; - } - } - } - break; - - case RE_VISUAL: - if (!reg_match_visual()) - status = RA_NOMATCH; - break; - - case RE_LNUM: - assert(rex.lnum + rex.reg_firstlnum >= 0 - && (uintmax_t)(rex.lnum + rex.reg_firstlnum) <= UINT32_MAX); - if (!REG_MULTI - || !re_num_cmp((uint32_t)(rex.lnum + rex.reg_firstlnum), scan)) { - status = RA_NOMATCH; - } - break; - - case RE_COL: - assert(rex.input - rex.line + 1 >= 0 - && (uintmax_t)(rex.input - rex.line + 1) <= UINT32_MAX); - if (!re_num_cmp((uint32_t)(rex.input - rex.line + 1), scan)) { - status = RA_NOMATCH; - } - break; - - case RE_VCOL: - if (!re_num_cmp(win_linetabsize(rex.reg_win == NULL - ? curwin : rex.reg_win, - rex.line, - (colnr_T)(rex.input - rex.line)) + 1, - scan)) { - status = RA_NOMATCH; - } - break; - - case BOW: // \<word; rex.input points to w - if (c == NUL) { // Can't match at end of line - status = RA_NOMATCH; - } else { - // Get class of current and previous char (if it exists). - const int this_class = - mb_get_class_tab(rex.input, rex.reg_buf->b_chartab); - if (this_class <= 1) { - status = RA_NOMATCH; // Not on a word at all. - } else if (reg_prev_class() == this_class) { - status = RA_NOMATCH; // Previous char is in same word. - } - } - break; - - case EOW: // word\>; rex.input points after d - if (rex.input == rex.line) { // Can't match at start of line - status = RA_NOMATCH; - } else { - int this_class, prev_class; - - // Get class of current and previous char (if it exists). - this_class = mb_get_class_tab(rex.input, rex.reg_buf->b_chartab); - prev_class = reg_prev_class(); - if (this_class == prev_class - || prev_class == 0 || prev_class == 1) { - status = RA_NOMATCH; - } - } - break; // Matched with EOW - - case ANY: - // ANY does not match new lines. - if (c == NUL) { - status = RA_NOMATCH; - } else { - ADVANCE_REGINPUT(); - } - break; - - case IDENT: - if (!vim_isIDc(c)) - status = RA_NOMATCH; - else - ADVANCE_REGINPUT(); - break; - - case SIDENT: - if (ascii_isdigit(*rex.input) || !vim_isIDc(c)) { - status = RA_NOMATCH; - } else { - ADVANCE_REGINPUT(); - } - break; - - case KWORD: - if (!vim_iswordp_buf(rex.input, rex.reg_buf)) { - status = RA_NOMATCH; - } else { - ADVANCE_REGINPUT(); - } - break; - - case SKWORD: - if (ascii_isdigit(*rex.input) - || !vim_iswordp_buf(rex.input, rex.reg_buf)) { - status = RA_NOMATCH; - } else { - ADVANCE_REGINPUT(); - } - break; - - case FNAME: - if (!vim_isfilec(c)) { - status = RA_NOMATCH; - } else { - ADVANCE_REGINPUT(); - } - break; - - case SFNAME: - if (ascii_isdigit(*rex.input) || !vim_isfilec(c)) { - status = RA_NOMATCH; - } else { - ADVANCE_REGINPUT(); - } - break; - - case PRINT: - if (!vim_isprintc(utf_ptr2char(rex.input))) { - status = RA_NOMATCH; - } else { - ADVANCE_REGINPUT(); - } - break; - - case SPRINT: - if (ascii_isdigit(*rex.input) || !vim_isprintc(utf_ptr2char(rex.input))) { - status = RA_NOMATCH; - } else { - ADVANCE_REGINPUT(); - } - break; - - case WHITE: - if (!ascii_iswhite(c)) - status = RA_NOMATCH; - else - ADVANCE_REGINPUT(); - break; - - case NWHITE: - if (c == NUL || ascii_iswhite(c)) - status = RA_NOMATCH; - else - ADVANCE_REGINPUT(); - break; - - case DIGIT: - if (!ri_digit(c)) - status = RA_NOMATCH; - else - ADVANCE_REGINPUT(); - break; - - case NDIGIT: - if (c == NUL || ri_digit(c)) - status = RA_NOMATCH; - else - ADVANCE_REGINPUT(); - break; - - case HEX: - if (!ri_hex(c)) - status = RA_NOMATCH; - else - ADVANCE_REGINPUT(); - break; - - case NHEX: - if (c == NUL || ri_hex(c)) - status = RA_NOMATCH; - else - ADVANCE_REGINPUT(); - break; - - case OCTAL: - if (!ri_octal(c)) - status = RA_NOMATCH; - else - ADVANCE_REGINPUT(); - break; - - case NOCTAL: - if (c == NUL || ri_octal(c)) - status = RA_NOMATCH; - else - ADVANCE_REGINPUT(); - break; - - case WORD: - if (!ri_word(c)) - status = RA_NOMATCH; - else - ADVANCE_REGINPUT(); - break; - - case NWORD: - if (c == NUL || ri_word(c)) - status = RA_NOMATCH; - else - ADVANCE_REGINPUT(); - break; - - case HEAD: - if (!ri_head(c)) - status = RA_NOMATCH; - else - ADVANCE_REGINPUT(); - break; - - case NHEAD: - if (c == NUL || ri_head(c)) - status = RA_NOMATCH; - else - ADVANCE_REGINPUT(); - break; - - case ALPHA: - if (!ri_alpha(c)) - status = RA_NOMATCH; - else - ADVANCE_REGINPUT(); - break; - - case NALPHA: - if (c == NUL || ri_alpha(c)) - status = RA_NOMATCH; - else - ADVANCE_REGINPUT(); - break; - - case LOWER: - if (!ri_lower(c)) - status = RA_NOMATCH; - else - ADVANCE_REGINPUT(); - break; - - case NLOWER: - if (c == NUL || ri_lower(c)) - status = RA_NOMATCH; - else - ADVANCE_REGINPUT(); - break; - - case UPPER: - if (!ri_upper(c)) - status = RA_NOMATCH; - else - ADVANCE_REGINPUT(); - break; - - case NUPPER: - if (c == NUL || ri_upper(c)) - status = RA_NOMATCH; - else - ADVANCE_REGINPUT(); - break; - - case EXACTLY: - { - int len; - char_u *opnd; - - opnd = OPERAND(scan); - // Inline the first byte, for speed. - if (*opnd != *rex.input - && (!rex.reg_ic)) { - status = RA_NOMATCH; - } else if (*opnd == NUL) { - // match empty string always works; happens when "~" is - // empty. - } else { - if (opnd[1] == NUL && !rex.reg_ic) { - len = 1; // matched a single byte above - } else { - // Need to match first byte again for multi-byte. - len = (int)STRLEN(opnd); - if (cstrncmp(opnd, rex.input, &len) != 0) { - status = RA_NOMATCH; - } - } - // Check for following composing character, unless %C - // follows (skips over all composing chars). - if (status != RA_NOMATCH - && utf_composinglike(rex.input, rex.input + len) - && !rex.reg_icombine - && OP(next) != RE_COMPOSING) { - // raaron: This code makes a composing character get - // ignored, which is the correct behavior (sometimes) - // for voweled Hebrew texts. - status = RA_NOMATCH; - } - if (status != RA_NOMATCH) { - rex.input += len; - } - } - } - break; - - case ANYOF: - case ANYBUT: - if (c == NUL) - status = RA_NOMATCH; - else if ((cstrchr(OPERAND(scan), c) == NULL) == (op == ANYOF)) - status = RA_NOMATCH; - else - ADVANCE_REGINPUT(); - break; - - case MULTIBYTECODE: - { - int i, len; - - const char_u *opnd = OPERAND(scan); - // Safety check (just in case 'encoding' was changed since - // compiling the program). - if ((len = utfc_ptr2len(opnd)) < 2) { - status = RA_NOMATCH; - break; - } - const int opndc = utf_ptr2char(opnd); - if (utf_iscomposing(opndc)) { - // When only a composing char is given match at any - // position where that composing char appears. - status = RA_NOMATCH; - for (i = 0; rex.input[i] != NUL; - i += utf_ptr2len(rex.input + i)) { - const int inpc = utf_ptr2char(rex.input + i); - if (!utf_iscomposing(inpc)) { - if (i > 0) { - break; - } - } else if (opndc == inpc) { - // Include all following composing chars. - len = i + utfc_ptr2len(rex.input + i); - status = RA_MATCH; - break; - } - } - } else { - for (i = 0; i < len; i++) { - if (opnd[i] != rex.input[i]) { - status = RA_NOMATCH; - break; - } - } - } - rex.input += len; - } - break; - - case RE_COMPOSING: - { - // Skip composing characters. - while (utf_iscomposing(utf_ptr2char(rex.input))) { - MB_CPTR_ADV(rex.input); - } - } - break; - - case NOTHING: - break; - - case BACK: - { - int i; - - /* - * When we run into BACK we need to check if we don't keep - * looping without matching any input. The second and later - * times a BACK is encountered it fails if the input is still - * at the same position as the previous time. - * The positions are stored in "backpos" and found by the - * current value of "scan", the position in the RE program. - */ - backpos_T *bp = (backpos_T *)backpos.ga_data; - for (i = 0; i < backpos.ga_len; ++i) - if (bp[i].bp_scan == scan) - break; - if (i == backpos.ga_len) { - backpos_T *p = GA_APPEND_VIA_PTR(backpos_T, &backpos); - p->bp_scan = scan; - } else if (reg_save_equal(&bp[i].bp_pos)) - /* Still at same position as last time, fail. */ - status = RA_NOMATCH; - - assert(status != RA_FAIL); - if (status != RA_NOMATCH) { - reg_save(&bp[i].bp_pos, &backpos); - } - } - break; - - case MOPEN + 0: /* Match start: \zs */ - case MOPEN + 1: /* \( */ - case MOPEN + 2: - case MOPEN + 3: - case MOPEN + 4: - case MOPEN + 5: - case MOPEN + 6: - case MOPEN + 7: - case MOPEN + 8: - case MOPEN + 9: - { - no = op - MOPEN; - cleanup_subexpr(); - rp = regstack_push(RS_MOPEN, scan); - if (rp == NULL) - status = RA_FAIL; - else { - rp->rs_no = no; - save_se(&rp->rs_un.sesave, &rex.reg_startpos[no], - &rex.reg_startp[no]); - // We simply continue and handle the result when done. - } - } - break; - - case NOPEN: /* \%( */ - case NCLOSE: /* \) after \%( */ - if (regstack_push(RS_NOPEN, scan) == NULL) - status = RA_FAIL; - /* We simply continue and handle the result when done. */ - break; - - case ZOPEN + 1: - case ZOPEN + 2: - case ZOPEN + 3: - case ZOPEN + 4: - case ZOPEN + 5: - case ZOPEN + 6: - case ZOPEN + 7: - case ZOPEN + 8: - case ZOPEN + 9: - { - no = op - ZOPEN; - cleanup_zsubexpr(); - rp = regstack_push(RS_ZOPEN, scan); - if (rp == NULL) - status = RA_FAIL; - else { - rp->rs_no = no; - save_se(&rp->rs_un.sesave, ®_startzpos[no], - ®_startzp[no]); - /* We simply continue and handle the result when done. */ - } - } - break; - - case MCLOSE + 0: /* Match end: \ze */ - case MCLOSE + 1: /* \) */ - case MCLOSE + 2: - case MCLOSE + 3: - case MCLOSE + 4: - case MCLOSE + 5: - case MCLOSE + 6: - case MCLOSE + 7: - case MCLOSE + 8: - case MCLOSE + 9: - { - no = op - MCLOSE; - cleanup_subexpr(); - rp = regstack_push(RS_MCLOSE, scan); - if (rp == NULL) { - status = RA_FAIL; - } else { - rp->rs_no = no; - save_se(&rp->rs_un.sesave, &rex.reg_endpos[no], &rex.reg_endp[no]); - // We simply continue and handle the result when done. - } - } - break; - - case ZCLOSE + 1: /* \) after \z( */ - case ZCLOSE + 2: - case ZCLOSE + 3: - case ZCLOSE + 4: - case ZCLOSE + 5: - case ZCLOSE + 6: - case ZCLOSE + 7: - case ZCLOSE + 8: - case ZCLOSE + 9: - { - no = op - ZCLOSE; - cleanup_zsubexpr(); - rp = regstack_push(RS_ZCLOSE, scan); - if (rp == NULL) - status = RA_FAIL; - else { - rp->rs_no = no; - save_se(&rp->rs_un.sesave, ®_endzpos[no], - ®_endzp[no]); - /* We simply continue and handle the result when done. */ - } - } - break; - - case BACKREF + 1: - case BACKREF + 2: - case BACKREF + 3: - case BACKREF + 4: - case BACKREF + 5: - case BACKREF + 6: - case BACKREF + 7: - case BACKREF + 8: - case BACKREF + 9: - { - int len; - - no = op - BACKREF; - cleanup_subexpr(); - if (!REG_MULTI) { // Single-line regexp - if (rex.reg_startp[no] == NULL || rex.reg_endp[no] == NULL) { - // Backref was not set: Match an empty string. - len = 0; - } else { - // Compare current input with back-ref in the same line. - len = (int)(rex.reg_endp[no] - rex.reg_startp[no]); - if (cstrncmp(rex.reg_startp[no], rex.input, &len) != 0) { - status = RA_NOMATCH; - } - } - } else { // Multi-line regexp - if (rex.reg_startpos[no].lnum < 0 || rex.reg_endpos[no].lnum < 0) { - // Backref was not set: Match an empty string. - len = 0; - } else { - if (rex.reg_startpos[no].lnum == rex.lnum - && rex.reg_endpos[no].lnum == rex.lnum) { - // Compare back-ref within the current line. - len = rex.reg_endpos[no].col - rex.reg_startpos[no].col; - if (cstrncmp(rex.line + rex.reg_startpos[no].col, - rex.input, &len) != 0) { - status = RA_NOMATCH; - } - } else { - // Messy situation: Need to compare between two lines. - int r = match_with_backref(rex.reg_startpos[no].lnum, - rex.reg_startpos[no].col, - rex.reg_endpos[no].lnum, - rex.reg_endpos[no].col, - &len); - if (r != RA_MATCH) { - status = r; - } - } - } - } - - // Matched the backref, skip over it. - rex.input += len; - } - break; - - case ZREF + 1: - case ZREF + 2: - case ZREF + 3: - case ZREF + 4: - case ZREF + 5: - case ZREF + 6: - case ZREF + 7: - case ZREF + 8: - case ZREF + 9: - { - cleanup_zsubexpr(); - no = op - ZREF; - if (re_extmatch_in != NULL - && re_extmatch_in->matches[no] != NULL) { - int len = (int)STRLEN(re_extmatch_in->matches[no]); - if (cstrncmp(re_extmatch_in->matches[no], rex.input, &len) != 0) { - status = RA_NOMATCH; - } else { - rex.input += len; - } - } else { - // Backref was not set: Match an empty string. - } - } - break; - - case BRANCH: - { - if (OP(next) != BRANCH) /* No choice. */ - next = OPERAND(scan); /* Avoid recursion. */ - else { - rp = regstack_push(RS_BRANCH, scan); - if (rp == NULL) - status = RA_FAIL; - else - status = RA_BREAK; /* rest is below */ - } - } - break; - - case BRACE_LIMITS: - { - if (OP(next) == BRACE_SIMPLE) { - bl_minval = OPERAND_MIN(scan); - bl_maxval = OPERAND_MAX(scan); - } else if (OP(next) >= BRACE_COMPLEX - && OP(next) < BRACE_COMPLEX + 10) { - no = OP(next) - BRACE_COMPLEX; - brace_min[no] = OPERAND_MIN(scan); - brace_max[no] = OPERAND_MAX(scan); - brace_count[no] = 0; - } else { - internal_error("BRACE_LIMITS"); - status = RA_FAIL; - } - } - break; - - case BRACE_COMPLEX + 0: - case BRACE_COMPLEX + 1: - case BRACE_COMPLEX + 2: - case BRACE_COMPLEX + 3: - case BRACE_COMPLEX + 4: - case BRACE_COMPLEX + 5: - case BRACE_COMPLEX + 6: - case BRACE_COMPLEX + 7: - case BRACE_COMPLEX + 8: - case BRACE_COMPLEX + 9: - { - no = op - BRACE_COMPLEX; - ++brace_count[no]; - - /* If not matched enough times yet, try one more */ - if (brace_count[no] <= (brace_min[no] <= brace_max[no] - ? brace_min[no] : brace_max[no])) { - rp = regstack_push(RS_BRCPLX_MORE, scan); - if (rp == NULL) - status = RA_FAIL; - else { - rp->rs_no = no; - reg_save(&rp->rs_un.regsave, &backpos); - next = OPERAND(scan); - /* We continue and handle the result when done. */ - } - break; - } - - /* If matched enough times, may try matching some more */ - if (brace_min[no] <= brace_max[no]) { - /* Range is the normal way around, use longest match */ - if (brace_count[no] <= brace_max[no]) { - rp = regstack_push(RS_BRCPLX_LONG, scan); - if (rp == NULL) - status = RA_FAIL; - else { - rp->rs_no = no; - reg_save(&rp->rs_un.regsave, &backpos); - next = OPERAND(scan); - /* We continue and handle the result when done. */ - } - } - } else { - /* Range is backwards, use shortest match first */ - if (brace_count[no] <= brace_min[no]) { - rp = regstack_push(RS_BRCPLX_SHORT, scan); - if (rp == NULL) - status = RA_FAIL; - else { - reg_save(&rp->rs_un.regsave, &backpos); - /* We continue and handle the result when done. */ - } - } - } - } - break; - - case BRACE_SIMPLE: - case STAR: - case PLUS: - { - regstar_T rst; - - /* - * Lookahead to avoid useless match attempts when we know - * what character comes next. - */ - if (OP(next) == EXACTLY) { - rst.nextb = *OPERAND(next); - if (rex.reg_ic) { - if (mb_isupper(rst.nextb)) { - rst.nextb_ic = mb_tolower(rst.nextb); - } else { - rst.nextb_ic = mb_toupper(rst.nextb); - } - } else { - rst.nextb_ic = rst.nextb; - } - } else { - rst.nextb = NUL; - rst.nextb_ic = NUL; - } - if (op != BRACE_SIMPLE) { - rst.minval = (op == STAR) ? 0 : 1; - rst.maxval = MAX_LIMIT; - } else { - rst.minval = bl_minval; - rst.maxval = bl_maxval; - } - - /* - * When maxval > minval, try matching as much as possible, up - * to maxval. When maxval < minval, try matching at least the - * minimal number (since the range is backwards, that's also - * maxval!). - */ - rst.count = regrepeat(OPERAND(scan), rst.maxval); - if (got_int) { - status = RA_FAIL; - break; - } - if (rst.minval <= rst.maxval - ? rst.count >= rst.minval : rst.count >= rst.maxval) { - /* It could match. Prepare for trying to match what - * follows. The code is below. Parameters are stored in - * a regstar_T on the regstack. */ - if ((long)((unsigned)regstack.ga_len >> 10) >= p_mmp) { - emsg(_(e_maxmempat)); - status = RA_FAIL; - } else { - ga_grow(®stack, sizeof(regstar_T)); - regstack.ga_len += sizeof(regstar_T); - rp = regstack_push(rst.minval <= rst.maxval - ? RS_STAR_LONG : RS_STAR_SHORT, scan); - if (rp == NULL) - status = RA_FAIL; - else { - *(((regstar_T *)rp) - 1) = rst; - status = RA_BREAK; /* skip the restore bits */ - } - } - } else - status = RA_NOMATCH; - - } - break; - - case NOMATCH: - case MATCH: - case SUBPAT: - rp = regstack_push(RS_NOMATCH, scan); - if (rp == NULL) - status = RA_FAIL; - else { - rp->rs_no = op; - reg_save(&rp->rs_un.regsave, &backpos); - next = OPERAND(scan); - /* We continue and handle the result when done. */ - } - break; - - case BEHIND: - case NOBEHIND: - /* Need a bit of room to store extra positions. */ - if ((long)((unsigned)regstack.ga_len >> 10) >= p_mmp) { - emsg(_(e_maxmempat)); - status = RA_FAIL; - } else { - ga_grow(®stack, sizeof(regbehind_T)); - regstack.ga_len += sizeof(regbehind_T); - rp = regstack_push(RS_BEHIND1, scan); - if (rp == NULL) - status = RA_FAIL; - else { - /* Need to save the subexpr to be able to restore them - * when there is a match but we don't use it. */ - save_subexpr(((regbehind_T *)rp) - 1); - - rp->rs_no = op; - reg_save(&rp->rs_un.regsave, &backpos); - /* First try if what follows matches. If it does then we - * check the behind match by looping. */ - } - } - break; - - case BHPOS: - if (REG_MULTI) { - if (behind_pos.rs_u.pos.col != (colnr_T)(rex.input - rex.line) - || behind_pos.rs_u.pos.lnum != rex.lnum) { - status = RA_NOMATCH; - } - } else if (behind_pos.rs_u.ptr != rex.input) { - status = RA_NOMATCH; - } - break; - - case NEWL: - if ((c != NUL || !REG_MULTI || rex.lnum > rex.reg_maxline - || rex.reg_line_lbr) && (c != '\n' || !rex.reg_line_lbr)) { - status = RA_NOMATCH; - } else if (rex.reg_line_lbr) { - ADVANCE_REGINPUT(); - } else { - reg_nextline(); - } - break; - - case END: - status = RA_MATCH; /* Success! */ - break; - - default: - iemsg(_(e_re_corr)); -#ifdef REGEXP_DEBUG - printf("Illegal op code %d\n", op); -#endif - status = RA_FAIL; - break; - } - } - - /* If we can't continue sequentially, break the inner loop. */ - if (status != RA_CONT) - break; - - /* Continue in inner loop, advance to next item. */ - scan = next; - - } /* end of inner loop */ - - /* - * If there is something on the regstack execute the code for the state. - * If the state is popped then loop and use the older state. - */ - while (!GA_EMPTY(®stack) && status != RA_FAIL) { - rp = (regitem_T *)((char *)regstack.ga_data + regstack.ga_len) - 1; - switch (rp->rs_state) { - case RS_NOPEN: - /* Result is passed on as-is, simply pop the state. */ - regstack_pop(&scan); - break; - - case RS_MOPEN: - // Pop the state. Restore pointers when there is no match. - if (status == RA_NOMATCH) { - restore_se(&rp->rs_un.sesave, &rex.reg_startpos[rp->rs_no], - &rex.reg_startp[rp->rs_no]); - } - regstack_pop(&scan); - break; - - case RS_ZOPEN: - /* Pop the state. Restore pointers when there is no match. */ - if (status == RA_NOMATCH) - restore_se(&rp->rs_un.sesave, ®_startzpos[rp->rs_no], - ®_startzp[rp->rs_no]); - regstack_pop(&scan); - break; - - case RS_MCLOSE: - // Pop the state. Restore pointers when there is no match. - if (status == RA_NOMATCH) { - restore_se(&rp->rs_un.sesave, &rex.reg_endpos[rp->rs_no], - &rex.reg_endp[rp->rs_no]); - } - regstack_pop(&scan); - break; - - case RS_ZCLOSE: - /* Pop the state. Restore pointers when there is no match. */ - if (status == RA_NOMATCH) - restore_se(&rp->rs_un.sesave, ®_endzpos[rp->rs_no], - ®_endzp[rp->rs_no]); - regstack_pop(&scan); - break; - - case RS_BRANCH: - if (status == RA_MATCH) - /* this branch matched, use it */ - regstack_pop(&scan); - else { - if (status != RA_BREAK) { - /* After a non-matching branch: try next one. */ - reg_restore(&rp->rs_un.regsave, &backpos); - scan = rp->rs_scan; - } - if (scan == NULL || OP(scan) != BRANCH) { - /* no more branches, didn't find a match */ - status = RA_NOMATCH; - regstack_pop(&scan); - } else { - /* Prepare to try a branch. */ - rp->rs_scan = regnext(scan); - reg_save(&rp->rs_un.regsave, &backpos); - scan = OPERAND(scan); - } - } - break; - - case RS_BRCPLX_MORE: - /* Pop the state. Restore pointers when there is no match. */ - if (status == RA_NOMATCH) { - reg_restore(&rp->rs_un.regsave, &backpos); - --brace_count[rp->rs_no]; /* decrement match count */ - } - regstack_pop(&scan); - break; - - case RS_BRCPLX_LONG: - /* Pop the state. Restore pointers when there is no match. */ - if (status == RA_NOMATCH) { - /* There was no match, but we did find enough matches. */ - reg_restore(&rp->rs_un.regsave, &backpos); - --brace_count[rp->rs_no]; - /* continue with the items after "\{}" */ - status = RA_CONT; - } - regstack_pop(&scan); - if (status == RA_CONT) - scan = regnext(scan); - break; - - case RS_BRCPLX_SHORT: - /* Pop the state. Restore pointers when there is no match. */ - if (status == RA_NOMATCH) - /* There was no match, try to match one more item. */ - reg_restore(&rp->rs_un.regsave, &backpos); - regstack_pop(&scan); - if (status == RA_NOMATCH) { - scan = OPERAND(scan); - status = RA_CONT; - } - break; - - case RS_NOMATCH: - /* Pop the state. If the operand matches for NOMATCH or - * doesn't match for MATCH/SUBPAT, we fail. Otherwise backup, - * except for SUBPAT, and continue with the next item. */ - if (status == (rp->rs_no == NOMATCH ? RA_MATCH : RA_NOMATCH)) - status = RA_NOMATCH; - else { - status = RA_CONT; - if (rp->rs_no != SUBPAT) /* zero-width */ - reg_restore(&rp->rs_un.regsave, &backpos); - } - regstack_pop(&scan); - if (status == RA_CONT) - scan = regnext(scan); - break; - - case RS_BEHIND1: - if (status == RA_NOMATCH) { - regstack_pop(&scan); - regstack.ga_len -= sizeof(regbehind_T); - } else { - /* The stuff after BEHIND/NOBEHIND matches. Now try if - * the behind part does (not) match before the current - * position in the input. This must be done at every - * position in the input and checking if the match ends at - * the current position. */ - - /* save the position after the found match for next */ - reg_save(&(((regbehind_T *)rp) - 1)->save_after, &backpos); - - /* Start looking for a match with operand at the current - * position. Go back one character until we find the - * result, hitting the start of the line or the previous - * line (for multi-line matching). - * Set behind_pos to where the match should end, BHPOS - * will match it. Save the current value. */ - (((regbehind_T *)rp) - 1)->save_behind = behind_pos; - behind_pos = rp->rs_un.regsave; - - rp->rs_state = RS_BEHIND2; - - reg_restore(&rp->rs_un.regsave, &backpos); - scan = OPERAND(rp->rs_scan) + 4; - } - break; - - case RS_BEHIND2: - /* - * Looping for BEHIND / NOBEHIND match. - */ - if (status == RA_MATCH && reg_save_equal(&behind_pos)) { - /* found a match that ends where "next" started */ - behind_pos = (((regbehind_T *)rp) - 1)->save_behind; - if (rp->rs_no == BEHIND) - reg_restore(&(((regbehind_T *)rp) - 1)->save_after, - &backpos); - else { - /* But we didn't want a match. Need to restore the - * subexpr, because what follows matched, so they have - * been set. */ - status = RA_NOMATCH; - restore_subexpr(((regbehind_T *)rp) - 1); - } - regstack_pop(&scan); - regstack.ga_len -= sizeof(regbehind_T); - } else { - long limit; - - /* No match or a match that doesn't end where we want it: Go - * back one character. May go to previous line once. */ - no = OK; - limit = OPERAND_MIN(rp->rs_scan); - if (REG_MULTI) { - if (limit > 0 - && ((rp->rs_un.regsave.rs_u.pos.lnum - < behind_pos.rs_u.pos.lnum - ? (colnr_T)STRLEN(rex.line) - : behind_pos.rs_u.pos.col) - - rp->rs_un.regsave.rs_u.pos.col >= limit)) - no = FAIL; - else if (rp->rs_un.regsave.rs_u.pos.col == 0) { - if (rp->rs_un.regsave.rs_u.pos.lnum - < behind_pos.rs_u.pos.lnum - || reg_getline( - --rp->rs_un.regsave.rs_u.pos.lnum) - == NULL) - no = FAIL; - else { - reg_restore(&rp->rs_un.regsave, &backpos); - rp->rs_un.regsave.rs_u.pos.col = - (colnr_T)STRLEN(rex.line); - } - } else { - const char_u *const line = - reg_getline(rp->rs_un.regsave.rs_u.pos.lnum); - - rp->rs_un.regsave.rs_u.pos.col -= - utf_head_off(line, - line + rp->rs_un.regsave.rs_u.pos.col - 1) - + 1; - } - } else { - if (rp->rs_un.regsave.rs_u.ptr == rex.line) { - no = FAIL; - } else { - MB_PTR_BACK(rex.line, rp->rs_un.regsave.rs_u.ptr); - if (limit > 0 - && (long)(behind_pos.rs_u.ptr - - rp->rs_un.regsave.rs_u.ptr) > limit) { - no = FAIL; - } - } - } - if (no == OK) { - /* Advanced, prepare for finding match again. */ - reg_restore(&rp->rs_un.regsave, &backpos); - scan = OPERAND(rp->rs_scan) + 4; - if (status == RA_MATCH) { - /* We did match, so subexpr may have been changed, - * need to restore them for the next try. */ - status = RA_NOMATCH; - restore_subexpr(((regbehind_T *)rp) - 1); - } - } else { - /* Can't advance. For NOBEHIND that's a match. */ - behind_pos = (((regbehind_T *)rp) - 1)->save_behind; - if (rp->rs_no == NOBEHIND) { - reg_restore(&(((regbehind_T *)rp) - 1)->save_after, - &backpos); - status = RA_MATCH; - } else { - /* We do want a proper match. Need to restore the - * subexpr if we had a match, because they may have - * been set. */ - if (status == RA_MATCH) { - status = RA_NOMATCH; - restore_subexpr(((regbehind_T *)rp) - 1); - } - } - regstack_pop(&scan); - regstack.ga_len -= sizeof(regbehind_T); - } - } - break; - - case RS_STAR_LONG: - case RS_STAR_SHORT: - { - regstar_T *rst = ((regstar_T *)rp) - 1; - - if (status == RA_MATCH) { - regstack_pop(&scan); - regstack.ga_len -= sizeof(regstar_T); - break; - } - - /* Tried once already, restore input pointers. */ - if (status != RA_BREAK) - reg_restore(&rp->rs_un.regsave, &backpos); - - /* Repeat until we found a position where it could match. */ - for (;; ) { - if (status != RA_BREAK) { - /* Tried first position already, advance. */ - if (rp->rs_state == RS_STAR_LONG) { - /* Trying for longest match, but couldn't or - * didn't match -- back up one char. */ - if (--rst->count < rst->minval) - break; - if (rex.input == rex.line) { - // backup to last char of previous line - rex.lnum--; - rex.line = reg_getline(rex.lnum); - // Just in case regrepeat() didn't count right. - if (rex.line == NULL) { - break; - } - rex.input = rex.line + STRLEN(rex.line); - fast_breakcheck(); - } else { - MB_PTR_BACK(rex.line, rex.input); - } - } else { - /* Range is backwards, use shortest match first. - * Careful: maxval and minval are exchanged! - * Couldn't or didn't match: try advancing one - * char. */ - if (rst->count == rst->minval - || regrepeat(OPERAND(rp->rs_scan), 1L) == 0) - break; - ++rst->count; - } - if (got_int) - break; - } else - status = RA_NOMATCH; - - // If it could match, try it. - if (rst->nextb == NUL || *rex.input == rst->nextb - || *rex.input == rst->nextb_ic) { - reg_save(&rp->rs_un.regsave, &backpos); - scan = regnext(rp->rs_scan); - status = RA_CONT; - break; - } - } - if (status != RA_CONT) { - /* Failed. */ - regstack_pop(&scan); - regstack.ga_len -= sizeof(regstar_T); - status = RA_NOMATCH; - } - } - break; - } - - /* If we want to continue the inner loop or didn't pop a state - * continue matching loop */ - if (status == RA_CONT || rp == (regitem_T *) - ((char *)regstack.ga_data + regstack.ga_len) - 1) - break; - } - - /* May need to continue with the inner loop, starting at "scan". */ - if (status == RA_CONT) - continue; - - /* - * If the regstack is empty or something failed we are done. - */ - if (GA_EMPTY(®stack) || status == RA_FAIL) { - if (scan == NULL) { - /* - * We get here only if there's trouble -- normally "case END" is - * the terminating point. - */ - iemsg(_(e_re_corr)); -#ifdef REGEXP_DEBUG - printf("Premature EOL\n"); -#endif - } - return status == RA_MATCH; - } - - } /* End of loop until the regstack is empty. */ - - /* NOTREACHED */ -} - -/* - * Push an item onto the regstack. - * Returns pointer to new item. Returns NULL when out of memory. - */ -static regitem_T *regstack_push(regstate_T state, char_u *scan) -{ - regitem_T *rp; - - if ((long)((unsigned)regstack.ga_len >> 10) >= p_mmp) { - emsg(_(e_maxmempat)); - return NULL; - } - ga_grow(®stack, sizeof(regitem_T)); - - rp = (regitem_T *)((char *)regstack.ga_data + regstack.ga_len); - rp->rs_state = state; - rp->rs_scan = scan; - - regstack.ga_len += sizeof(regitem_T); - return rp; -} - -/* - * Pop an item from the regstack. - */ -static void regstack_pop(char_u **scan) -{ - regitem_T *rp; - - rp = (regitem_T *)((char *)regstack.ga_data + regstack.ga_len) - 1; - *scan = rp->rs_scan; - - regstack.ga_len -= sizeof(regitem_T); -} - -/* - * regrepeat - repeatedly match something simple, return how many. - * Advances rex.input (and rex.lnum) to just after the matched chars. - */ -static int -regrepeat ( - char_u *p, - long maxcount /* maximum number of matches allowed */ -) -{ - long count = 0; - char_u *opnd; - int mask; - int testval = 0; - - char_u *scan = rex.input; // Make local copy of rex.input for speed. - opnd = OPERAND(p); - switch (OP(p)) { - case ANY: - case ANY + ADD_NL: - while (count < maxcount) { - /* Matching anything means we continue until end-of-line (or - * end-of-file for ANY + ADD_NL), only limited by maxcount. */ - while (*scan != NUL && count < maxcount) { - count++; - MB_PTR_ADV(scan); - } - if (!REG_MULTI || !WITH_NL(OP(p)) || rex.lnum > rex.reg_maxline - || rex.reg_line_lbr || count == maxcount) { - break; - } - count++; // count the line-break - reg_nextline(); - scan = rex.input; - if (got_int) { - break; - } - } - break; - - case IDENT: - case IDENT + ADD_NL: - testval = 1; - FALLTHROUGH; - case SIDENT: - case SIDENT + ADD_NL: - while (count < maxcount) { - if (vim_isIDc(utf_ptr2char(scan)) && (testval || !ascii_isdigit(*scan))) { - MB_PTR_ADV(scan); - } else if (*scan == NUL) { - if (!REG_MULTI || !WITH_NL(OP(p)) || rex.lnum > rex.reg_maxline - || rex.reg_line_lbr) { - break; - } - reg_nextline(); - scan = rex.input; - if (got_int) { - break; - } - } else if (rex.reg_line_lbr && *scan == '\n' && WITH_NL(OP(p))) { - scan++; - } else { - break; - } - ++count; - } - break; - - case KWORD: - case KWORD + ADD_NL: - testval = 1; - FALLTHROUGH; - case SKWORD: - case SKWORD + ADD_NL: - while (count < maxcount) { - if (vim_iswordp_buf(scan, rex.reg_buf) - && (testval || !ascii_isdigit(*scan))) { - MB_PTR_ADV(scan); - } else if (*scan == NUL) { - if (!REG_MULTI || !WITH_NL(OP(p)) || rex.lnum > rex.reg_maxline - || rex.reg_line_lbr) { - break; - } - reg_nextline(); - scan = rex.input; - if (got_int) { - break; - } - } else if (rex.reg_line_lbr && *scan == '\n' && WITH_NL(OP(p))) { - scan++; - } else { - break; - } - count++; - } - break; - - case FNAME: - case FNAME + ADD_NL: - testval = 1; - FALLTHROUGH; - case SFNAME: - case SFNAME + ADD_NL: - while (count < maxcount) { - if (vim_isfilec(utf_ptr2char(scan)) && (testval || !ascii_isdigit(*scan))) { - MB_PTR_ADV(scan); - } else if (*scan == NUL) { - if (!REG_MULTI || !WITH_NL(OP(p)) || rex.lnum > rex.reg_maxline - || rex.reg_line_lbr) { - break; - } - reg_nextline(); - scan = rex.input; - if (got_int) { - break; - } - } else if (rex.reg_line_lbr && *scan == '\n' && WITH_NL(OP(p))) { - scan++; - } else { - break; - } - count++; - } - break; - - case PRINT: - case PRINT + ADD_NL: - testval = 1; - FALLTHROUGH; - case SPRINT: - case SPRINT + ADD_NL: - while (count < maxcount) { - if (*scan == NUL) { - if (!REG_MULTI || !WITH_NL(OP(p)) || rex.lnum > rex.reg_maxline - || rex.reg_line_lbr) { - break; - } - reg_nextline(); - scan = rex.input; - if (got_int) { - break; - } - } else if (vim_isprintc(utf_ptr2char(scan)) == 1 - && (testval || !ascii_isdigit(*scan))) { - MB_PTR_ADV(scan); - } else if (rex.reg_line_lbr && *scan == '\n' && WITH_NL(OP(p))) { - scan++; - } else { - break; - } - count++; - } - break; - - case WHITE: - case WHITE + ADD_NL: - testval = mask = RI_WHITE; -do_class: - while (count < maxcount) { - int l; - if (*scan == NUL) { - if (!REG_MULTI || !WITH_NL(OP(p)) || rex.lnum > rex.reg_maxline - || rex.reg_line_lbr) { - break; - } - reg_nextline(); - scan = rex.input; - if (got_int) { - break; - } - } else if ((l = utfc_ptr2len(scan)) > 1) { - if (testval != 0) { - break; - } - scan += l; - } else if ((class_tab[*scan] & mask) == testval) { - scan++; - } else if (rex.reg_line_lbr && *scan == '\n' && WITH_NL(OP(p))) { - scan++; - } else { - break; - } - ++count; - } - break; - - case NWHITE: - case NWHITE + ADD_NL: - mask = RI_WHITE; - goto do_class; - case DIGIT: - case DIGIT + ADD_NL: - testval = mask = RI_DIGIT; - goto do_class; - case NDIGIT: - case NDIGIT + ADD_NL: - mask = RI_DIGIT; - goto do_class; - case HEX: - case HEX + ADD_NL: - testval = mask = RI_HEX; - goto do_class; - case NHEX: - case NHEX + ADD_NL: - mask = RI_HEX; - goto do_class; - case OCTAL: - case OCTAL + ADD_NL: - testval = mask = RI_OCTAL; - goto do_class; - case NOCTAL: - case NOCTAL + ADD_NL: - mask = RI_OCTAL; - goto do_class; - case WORD: - case WORD + ADD_NL: - testval = mask = RI_WORD; - goto do_class; - case NWORD: - case NWORD + ADD_NL: - mask = RI_WORD; - goto do_class; - case HEAD: - case HEAD + ADD_NL: - testval = mask = RI_HEAD; - goto do_class; - case NHEAD: - case NHEAD + ADD_NL: - mask = RI_HEAD; - goto do_class; - case ALPHA: - case ALPHA + ADD_NL: - testval = mask = RI_ALPHA; - goto do_class; - case NALPHA: - case NALPHA + ADD_NL: - mask = RI_ALPHA; - goto do_class; - case LOWER: - case LOWER + ADD_NL: - testval = mask = RI_LOWER; - goto do_class; - case NLOWER: - case NLOWER + ADD_NL: - mask = RI_LOWER; - goto do_class; - case UPPER: - case UPPER + ADD_NL: - testval = mask = RI_UPPER; - goto do_class; - case NUPPER: - case NUPPER + ADD_NL: - mask = RI_UPPER; - goto do_class; - - case EXACTLY: - { - int cu, cl; - - // This doesn't do a multi-byte character, because a MULTIBYTECODE - // would have been used for it. It does handle single-byte - // characters, such as latin1. - if (rex.reg_ic) { - cu = mb_toupper(*opnd); - cl = mb_tolower(*opnd); - while (count < maxcount && (*scan == cu || *scan == cl)) { - count++; - scan++; - } - } else { - cu = *opnd; - while (count < maxcount && *scan == cu) { - count++; - scan++; - } - } - break; - } - - case MULTIBYTECODE: - { - int i, len, cf = 0; - - /* Safety check (just in case 'encoding' was changed since - * compiling the program). */ - if ((len = utfc_ptr2len(opnd)) > 1) { - if (rex.reg_ic) { - cf = utf_fold(utf_ptr2char(opnd)); - } - while (count < maxcount && utfc_ptr2len(scan) >= len) { - for (i = 0; i < len; i++) { - if (opnd[i] != scan[i]) { - break; - } - } - if (i < len && (!rex.reg_ic - || utf_fold(utf_ptr2char(scan)) != cf)) { - break; - } - scan += len; - ++count; - } - } - } - break; - - case ANYOF: - case ANYOF + ADD_NL: - testval = 1; - FALLTHROUGH; - - case ANYBUT: - case ANYBUT + ADD_NL: - while (count < maxcount) { - int len; - if (*scan == NUL) { - if (!REG_MULTI || !WITH_NL(OP(p)) || rex.lnum > rex.reg_maxline - || rex.reg_line_lbr) { - break; - } - reg_nextline(); - scan = rex.input; - if (got_int) { - break; - } - } else if (rex.reg_line_lbr && *scan == '\n' && WITH_NL(OP(p))) { - scan++; - } else if ((len = utfc_ptr2len(scan)) > 1) { - if ((cstrchr(opnd, utf_ptr2char(scan)) == NULL) == testval) { - break; - } - scan += len; - } else { - if ((cstrchr(opnd, *scan) == NULL) == testval) - break; - ++scan; - } - ++count; - } - break; - - case NEWL: - while (count < maxcount - && ((*scan == NUL && rex.lnum <= rex.reg_maxline && !rex.reg_line_lbr - && REG_MULTI) || (*scan == '\n' && rex.reg_line_lbr))) { - count++; - if (rex.reg_line_lbr) { - ADVANCE_REGINPUT(); - } else { - reg_nextline(); - } - scan = rex.input; - if (got_int) { - break; - } - } - break; - - default: // Oh dear. Called inappropriately. - iemsg(_(e_re_corr)); -#ifdef REGEXP_DEBUG - printf("Called regrepeat with op code %d\n", OP(p)); -#endif - break; - } - - rex.input = scan; - - return (int)count; -} - -/* - * regnext - dig the "next" pointer out of a node - * Returns NULL when calculating size, when there is no next item and when - * there is an error. - */ -static char_u *regnext(char_u *p) - FUNC_ATTR_NONNULL_ALL -{ - int offset; - - if (p == JUST_CALC_SIZE || reg_toolong) - return NULL; - - offset = NEXT(p); - if (offset == 0) - return NULL; - - if (OP(p) == BACK) - return p - offset; - else - return p + offset; -} - /* * Check the regexp program for its magic number. * Return true if it's wrong. @@ -5662,7 +1214,7 @@ static void cleanup_zsubexpr(void) { if (rex.need_clear_zsubexpr) { if (REG_MULTI) { - /* Use 0xff to set lnum to -1 */ + // Use 0xff to set lnum to -1 memset(reg_startzpos, 0xff, sizeof(lpos_T) * NSUBEXP); memset(reg_endzpos, 0xff, sizeof(lpos_T) * NSUBEXP); } else { @@ -5673,45 +1225,6 @@ static void cleanup_zsubexpr(void) } } -// Save the current subexpr to "bp", so that they can be restored -// later by restore_subexpr(). -static void save_subexpr(regbehind_T *bp) - FUNC_ATTR_NONNULL_ALL -{ - // When "rex.need_clear_subexpr" is set we don't need to save the values, only - // remember that this flag needs to be set again when restoring. - bp->save_need_clear_subexpr = rex.need_clear_subexpr; - if (!rex.need_clear_subexpr) { - for (int i = 0; i < NSUBEXP; i++) { - if (REG_MULTI) { - bp->save_start[i].se_u.pos = rex.reg_startpos[i]; - bp->save_end[i].se_u.pos = rex.reg_endpos[i]; - } else { - bp->save_start[i].se_u.ptr = rex.reg_startp[i]; - bp->save_end[i].se_u.ptr = rex.reg_endp[i]; - } - } - } -} - -// Restore the subexpr from "bp". -static void restore_subexpr(regbehind_T *bp) - FUNC_ATTR_NONNULL_ALL -{ - // Only need to restore saved values when they are not to be cleared. - rex.need_clear_subexpr = bp->save_need_clear_subexpr; - if (!rex.need_clear_subexpr) { - for (int i = 0; i < NSUBEXP; i++) { - if (REG_MULTI) { - rex.reg_startpos[i] = bp->save_start[i].se_u.pos; - rex.reg_endpos[i] = bp->save_end[i].se_u.pos; - } else { - rex.reg_startp[i] = bp->save_start[i].se_u.ptr; - rex.reg_endp[i] = bp->save_end[i].se_u.ptr; - } - } - } -} // Advance rex.lnum, rex.line and rex.input to the next line. static void reg_nextline(void) @@ -5721,81 +1234,6 @@ static void reg_nextline(void) fast_breakcheck(); } -// Save the input line and position in a regsave_T. -static void reg_save(regsave_T *save, garray_T *gap) - FUNC_ATTR_NONNULL_ALL -{ - if (REG_MULTI) { - save->rs_u.pos.col = (colnr_T)(rex.input - rex.line); - save->rs_u.pos.lnum = rex.lnum; - } else { - save->rs_u.ptr = rex.input; - } - save->rs_len = gap->ga_len; -} - -// Restore the input line and position from a regsave_T. -static void reg_restore(regsave_T *save, garray_T *gap) - FUNC_ATTR_NONNULL_ALL -{ - if (REG_MULTI) { - if (rex.lnum != save->rs_u.pos.lnum) { - // only call reg_getline() when the line number changed to save - // a bit of time - rex.lnum = save->rs_u.pos.lnum; - rex.line = reg_getline(rex.lnum); - } - rex.input = rex.line + save->rs_u.pos.col; - } else { - rex.input = save->rs_u.ptr; - } - gap->ga_len = save->rs_len; -} - -// Return true if current position is equal to saved position. -static bool reg_save_equal(const regsave_T *save) - FUNC_ATTR_NONNULL_ALL -{ - if (REG_MULTI) { - return rex.lnum == save->rs_u.pos.lnum - && rex.input == rex.line + save->rs_u.pos.col; - } - return rex.input == save->rs_u.ptr; -} - -/* - * Tentatively set the sub-expression start to the current position (after - * calling regmatch() they will have changed). Need to save the existing - * values for when there is no match. - * Use se_save() to use pointer (save_se_multi()) or position (save_se_one()), - * depending on REG_MULTI. - */ -static void save_se_multi(save_se_T *savep, lpos_T *posp) -{ - savep->se_u.pos = *posp; - posp->lnum = rex.lnum; - posp->col = (colnr_T)(rex.input - rex.line); -} - -static void save_se_one(save_se_T *savep, char_u **pp) -{ - savep->se_u.ptr = *pp; - *pp = rex.input; -} - -/* - * Compare a number with the operand of RE_LNUM, RE_COL or RE_VCOL. - */ -static int re_num_cmp(uint32_t val, char_u *scan) -{ - uint32_t n = (uint32_t)OPERAND_MIN(scan); - - if (OPERAND_CMP(scan) == '>') - return val > n; - if (OPERAND_CMP(scan) == '<') - return val < n; - return val == n; -} /* * Check whether a backreference matches. @@ -5803,7 +1241,12 @@ static int re_num_cmp(uint32_t val, char_u *scan) * If "bytelen" is not NULL, it is set to the byte length of the match in the * last line. */ -static int match_with_backref(linenr_T start_lnum, colnr_T start_col, linenr_T end_lnum, colnr_T end_col, int *bytelen) +static int match_with_backref( + linenr_T start_lnum, + colnr_T start_col, + linenr_T end_lnum, + colnr_T end_col, + int *bytelen) { linenr_T clnum = start_lnum; colnr_T ccol = start_col; @@ -5818,7 +1261,7 @@ static int match_with_backref(linenr_T start_lnum, colnr_T start_col, linenr_T e if (rex.line != reg_tofree) { len = (int)STRLEN(rex.line); if (reg_tofree == NULL || len >= (int)reg_tofreelen) { - len += 50; /* get some extra */ + len += 50; // get some extra xfree(reg_tofree); reg_tofree = xmalloc(len); reg_tofreelen = len; @@ -5828,7 +1271,7 @@ static int match_with_backref(linenr_T start_lnum, colnr_T start_col, linenr_T e rex.line = reg_tofree; } - /* Get the line to compare with. */ + // Get the line to compare with. p = reg_getline(clnum); assert(p); @@ -5850,7 +1293,7 @@ static int match_with_backref(linenr_T start_lnum, colnr_T start_col, linenr_T e return RA_NOMATCH; // text too short } - /* Advance to next line. */ + // Advance to next line. reg_nextline(); if (bytelen != NULL) *bytelen = 0; @@ -5865,520 +1308,72 @@ static int match_with_backref(linenr_T start_lnum, colnr_T start_col, linenr_T e return RA_MATCH; } -#ifdef BT_REGEXP_DUMP - -/* - * regdump - dump a regexp onto stdout in vaguely comprehensible form - */ -static void regdump(char_u *pattern, bt_regprog_T *r) -{ - char_u *s; - int op = EXACTLY; /* Arbitrary non-END op. */ - char_u *next; - char_u *end = NULL; - FILE *f; - -#ifdef BT_REGEXP_LOG - f = fopen("bt_regexp_log.log", "a"); -#else - f = stdout; -#endif - if (f == NULL) - return; - fprintf(f, "-------------------------------------\n\r\nregcomp(%s):\r\n", - pattern); - - s = r->program + 1; - /* - * Loop until we find the END that isn't before a referred next (an END - * can also appear in a NOMATCH operand). - */ - while (op != END || s <= end) { - op = OP(s); - fprintf(f, "%2d%s", (int)(s - r->program), regprop(s)); /* Where, what. */ - next = regnext(s); - if (next == NULL) /* Next ptr. */ - fprintf(f, "(0)"); - else - fprintf(f, "(%d)", (int)((s - r->program) + (next - s))); - if (end < next) - end = next; - if (op == BRACE_LIMITS) { - /* Two ints */ - fprintf(f, " minval %" PRId64 ", maxval %" PRId64, - (int64_t)OPERAND_MIN(s), (int64_t)OPERAND_MAX(s)); - s += 8; - } else if (op == BEHIND || op == NOBEHIND) { - /* one int */ - fprintf(f, " count %" PRId64, (int64_t)OPERAND_MIN(s)); - s += 4; - } else if (op == RE_LNUM || op == RE_COL || op == RE_VCOL) { - // one int plus comparator - fprintf(f, " count %" PRId64, (int64_t)OPERAND_MIN(s)); - s += 5; - } - s += 3; - if (op == ANYOF || op == ANYOF + ADD_NL - || op == ANYBUT || op == ANYBUT + ADD_NL - || op == EXACTLY) { - /* Literal string, where present. */ - fprintf(f, "\nxxxxxxxxx\n"); - while (*s != NUL) - fprintf(f, "%c", *s++); - fprintf(f, "\nxxxxxxxxx\n"); - s++; - } - fprintf(f, "\r\n"); - } - - /* Header fields of interest. */ - if (r->regstart != NUL) - fprintf(f, "start `%s' 0x%x; ", r->regstart < 256 - ? (char *)transchar(r->regstart) - : "multibyte", r->regstart); - if (r->reganch) - fprintf(f, "anchored; "); - if (r->regmust != NULL) - fprintf(f, "must have \"%s\"", r->regmust); - fprintf(f, "\r\n"); - -#ifdef BT_REGEXP_LOG - fclose(f); -#endif -} -#endif /* BT_REGEXP_DUMP */ - -#ifdef REGEXP_DEBUG -/* - * regprop - printable representation of opcode - */ -static char_u *regprop(char_u *op) +/// Used in a place where no * or \+ can follow. +static bool re_mult_next(char *what) { - char *p; - static char buf[50]; - - STRCPY(buf, ":"); - - switch ((int) OP(op)) { - case BOL: - p = "BOL"; - break; - case EOL: - p = "EOL"; - break; - case RE_BOF: - p = "BOF"; - break; - case RE_EOF: - p = "EOF"; - break; - case CURSOR: - p = "CURSOR"; - break; - case RE_VISUAL: - p = "RE_VISUAL"; - break; - case RE_LNUM: - p = "RE_LNUM"; - break; - case RE_MARK: - p = "RE_MARK"; - break; - case RE_COL: - p = "RE_COL"; - break; - case RE_VCOL: - p = "RE_VCOL"; - break; - case BOW: - p = "BOW"; - break; - case EOW: - p = "EOW"; - break; - case ANY: - p = "ANY"; - break; - case ANY + ADD_NL: - p = "ANY+NL"; - break; - case ANYOF: - p = "ANYOF"; - break; - case ANYOF + ADD_NL: - p = "ANYOF+NL"; - break; - case ANYBUT: - p = "ANYBUT"; - break; - case ANYBUT + ADD_NL: - p = "ANYBUT+NL"; - break; - case IDENT: - p = "IDENT"; - break; - case IDENT + ADD_NL: - p = "IDENT+NL"; - break; - case SIDENT: - p = "SIDENT"; - break; - case SIDENT + ADD_NL: - p = "SIDENT+NL"; - break; - case KWORD: - p = "KWORD"; - break; - case KWORD + ADD_NL: - p = "KWORD+NL"; - break; - case SKWORD: - p = "SKWORD"; - break; - case SKWORD + ADD_NL: - p = "SKWORD+NL"; - break; - case FNAME: - p = "FNAME"; - break; - case FNAME + ADD_NL: - p = "FNAME+NL"; - break; - case SFNAME: - p = "SFNAME"; - break; - case SFNAME + ADD_NL: - p = "SFNAME+NL"; - break; - case PRINT: - p = "PRINT"; - break; - case PRINT + ADD_NL: - p = "PRINT+NL"; - break; - case SPRINT: - p = "SPRINT"; - break; - case SPRINT + ADD_NL: - p = "SPRINT+NL"; - break; - case WHITE: - p = "WHITE"; - break; - case WHITE + ADD_NL: - p = "WHITE+NL"; - break; - case NWHITE: - p = "NWHITE"; - break; - case NWHITE + ADD_NL: - p = "NWHITE+NL"; - break; - case DIGIT: - p = "DIGIT"; - break; - case DIGIT + ADD_NL: - p = "DIGIT+NL"; - break; - case NDIGIT: - p = "NDIGIT"; - break; - case NDIGIT + ADD_NL: - p = "NDIGIT+NL"; - break; - case HEX: - p = "HEX"; - break; - case HEX + ADD_NL: - p = "HEX+NL"; - break; - case NHEX: - p = "NHEX"; - break; - case NHEX + ADD_NL: - p = "NHEX+NL"; - break; - case OCTAL: - p = "OCTAL"; - break; - case OCTAL + ADD_NL: - p = "OCTAL+NL"; - break; - case NOCTAL: - p = "NOCTAL"; - break; - case NOCTAL + ADD_NL: - p = "NOCTAL+NL"; - break; - case WORD: - p = "WORD"; - break; - case WORD + ADD_NL: - p = "WORD+NL"; - break; - case NWORD: - p = "NWORD"; - break; - case NWORD + ADD_NL: - p = "NWORD+NL"; - break; - case HEAD: - p = "HEAD"; - break; - case HEAD + ADD_NL: - p = "HEAD+NL"; - break; - case NHEAD: - p = "NHEAD"; - break; - case NHEAD + ADD_NL: - p = "NHEAD+NL"; - break; - case ALPHA: - p = "ALPHA"; - break; - case ALPHA + ADD_NL: - p = "ALPHA+NL"; - break; - case NALPHA: - p = "NALPHA"; - break; - case NALPHA + ADD_NL: - p = "NALPHA+NL"; - break; - case LOWER: - p = "LOWER"; - break; - case LOWER + ADD_NL: - p = "LOWER+NL"; - break; - case NLOWER: - p = "NLOWER"; - break; - case NLOWER + ADD_NL: - p = "NLOWER+NL"; - break; - case UPPER: - p = "UPPER"; - break; - case UPPER + ADD_NL: - p = "UPPER+NL"; - break; - case NUPPER: - p = "NUPPER"; - break; - case NUPPER + ADD_NL: - p = "NUPPER+NL"; - break; - case BRANCH: - p = "BRANCH"; - break; - case EXACTLY: - p = "EXACTLY"; - break; - case NOTHING: - p = "NOTHING"; - break; - case BACK: - p = "BACK"; - break; - case END: - p = "END"; - break; - case MOPEN + 0: - p = "MATCH START"; - break; - case MOPEN + 1: - case MOPEN + 2: - case MOPEN + 3: - case MOPEN + 4: - case MOPEN + 5: - case MOPEN + 6: - case MOPEN + 7: - case MOPEN + 8: - case MOPEN + 9: - sprintf(buf + STRLEN(buf), "MOPEN%d", OP(op) - MOPEN); - p = NULL; - break; - case MCLOSE + 0: - p = "MATCH END"; - break; - case MCLOSE + 1: - case MCLOSE + 2: - case MCLOSE + 3: - case MCLOSE + 4: - case MCLOSE + 5: - case MCLOSE + 6: - case MCLOSE + 7: - case MCLOSE + 8: - case MCLOSE + 9: - sprintf(buf + STRLEN(buf), "MCLOSE%d", OP(op) - MCLOSE); - p = NULL; - break; - case BACKREF + 1: - case BACKREF + 2: - case BACKREF + 3: - case BACKREF + 4: - case BACKREF + 5: - case BACKREF + 6: - case BACKREF + 7: - case BACKREF + 8: - case BACKREF + 9: - sprintf(buf + STRLEN(buf), "BACKREF%d", OP(op) - BACKREF); - p = NULL; - break; - case NOPEN: - p = "NOPEN"; - break; - case NCLOSE: - p = "NCLOSE"; - break; - case ZOPEN + 1: - case ZOPEN + 2: - case ZOPEN + 3: - case ZOPEN + 4: - case ZOPEN + 5: - case ZOPEN + 6: - case ZOPEN + 7: - case ZOPEN + 8: - case ZOPEN + 9: - sprintf(buf + STRLEN(buf), "ZOPEN%d", OP(op) - ZOPEN); - p = NULL; - break; - case ZCLOSE + 1: - case ZCLOSE + 2: - case ZCLOSE + 3: - case ZCLOSE + 4: - case ZCLOSE + 5: - case ZCLOSE + 6: - case ZCLOSE + 7: - case ZCLOSE + 8: - case ZCLOSE + 9: - sprintf(buf + STRLEN(buf), "ZCLOSE%d", OP(op) - ZCLOSE); - p = NULL; - break; - case ZREF + 1: - case ZREF + 2: - case ZREF + 3: - case ZREF + 4: - case ZREF + 5: - case ZREF + 6: - case ZREF + 7: - case ZREF + 8: - case ZREF + 9: - sprintf(buf + STRLEN(buf), "ZREF%d", OP(op) - ZREF); - p = NULL; - break; - case STAR: - p = "STAR"; - break; - case PLUS: - p = "PLUS"; - break; - case NOMATCH: - p = "NOMATCH"; - break; - case MATCH: - p = "MATCH"; - break; - case BEHIND: - p = "BEHIND"; - break; - case NOBEHIND: - p = "NOBEHIND"; - break; - case SUBPAT: - p = "SUBPAT"; - break; - case BRACE_LIMITS: - p = "BRACE_LIMITS"; - break; - case BRACE_SIMPLE: - p = "BRACE_SIMPLE"; - break; - case BRACE_COMPLEX + 0: - case BRACE_COMPLEX + 1: - case BRACE_COMPLEX + 2: - case BRACE_COMPLEX + 3: - case BRACE_COMPLEX + 4: - case BRACE_COMPLEX + 5: - case BRACE_COMPLEX + 6: - case BRACE_COMPLEX + 7: - case BRACE_COMPLEX + 8: - case BRACE_COMPLEX + 9: - sprintf(buf + STRLEN(buf), "BRACE_COMPLEX%d", OP(op) - BRACE_COMPLEX); - p = NULL; - break; - case MULTIBYTECODE: - p = "MULTIBYTECODE"; - break; - case NEWL: - p = "NEWL"; - break; - default: - sprintf(buf + STRLEN(buf), "corrupt %d", OP(op)); - p = NULL; - break; + if (re_multi_type(peekchr()) == MULTI_MULT) { + semsg(_("E888: (NFA regexp) cannot repeat %s"), what); + rc_did_emsg = true; + return false; } - if (p != NULL) - STRCAT(buf, p); - return (char_u *)buf; + return true; } -#endif /* REGEXP_DEBUG */ - +typedef struct { + int a, b, c; +} decomp_T; -/* 0xfb20 - 0xfb4f */ +// 0xfb20 - 0xfb4f static decomp_T decomp_table[0xfb4f-0xfb20+1] = { - {0x5e2,0,0}, /* 0xfb20 alt ayin */ - {0x5d0,0,0}, /* 0xfb21 alt alef */ - {0x5d3,0,0}, /* 0xfb22 alt dalet */ - {0x5d4,0,0}, /* 0xfb23 alt he */ - {0x5db,0,0}, /* 0xfb24 alt kaf */ - {0x5dc,0,0}, /* 0xfb25 alt lamed */ - {0x5dd,0,0}, /* 0xfb26 alt mem-sofit */ - {0x5e8,0,0}, /* 0xfb27 alt resh */ - {0x5ea,0,0}, /* 0xfb28 alt tav */ - {'+', 0, 0}, /* 0xfb29 alt plus */ - {0x5e9, 0x5c1, 0}, /* 0xfb2a shin+shin-dot */ - {0x5e9, 0x5c2, 0}, /* 0xfb2b shin+sin-dot */ - {0x5e9, 0x5c1, 0x5bc}, /* 0xfb2c shin+shin-dot+dagesh */ - {0x5e9, 0x5c2, 0x5bc}, /* 0xfb2d shin+sin-dot+dagesh */ - {0x5d0, 0x5b7, 0}, /* 0xfb2e alef+patah */ - {0x5d0, 0x5b8, 0}, /* 0xfb2f alef+qamats */ - {0x5d0, 0x5b4, 0}, /* 0xfb30 alef+hiriq */ - {0x5d1, 0x5bc, 0}, /* 0xfb31 bet+dagesh */ - {0x5d2, 0x5bc, 0}, /* 0xfb32 gimel+dagesh */ - {0x5d3, 0x5bc, 0}, /* 0xfb33 dalet+dagesh */ - {0x5d4, 0x5bc, 0}, /* 0xfb34 he+dagesh */ - {0x5d5, 0x5bc, 0}, /* 0xfb35 vav+dagesh */ - {0x5d6, 0x5bc, 0}, /* 0xfb36 zayin+dagesh */ - {0xfb37, 0, 0}, /* 0xfb37 -- */ - {0x5d8, 0x5bc, 0}, /* 0xfb38 tet+dagesh */ - {0x5d9, 0x5bc, 0}, /* 0xfb39 yud+dagesh */ - {0x5da, 0x5bc, 0}, /* 0xfb3a kaf sofit+dagesh */ - {0x5db, 0x5bc, 0}, /* 0xfb3b kaf+dagesh */ - {0x5dc, 0x5bc, 0}, /* 0xfb3c lamed+dagesh */ - {0xfb3d, 0, 0}, /* 0xfb3d -- */ - {0x5de, 0x5bc, 0}, /* 0xfb3e mem+dagesh */ - {0xfb3f, 0, 0}, /* 0xfb3f -- */ - {0x5e0, 0x5bc, 0}, /* 0xfb40 nun+dagesh */ - {0x5e1, 0x5bc, 0}, /* 0xfb41 samech+dagesh */ - {0xfb42, 0, 0}, /* 0xfb42 -- */ - {0x5e3, 0x5bc, 0}, /* 0xfb43 pe sofit+dagesh */ - {0x5e4, 0x5bc,0}, /* 0xfb44 pe+dagesh */ - {0xfb45, 0, 0}, /* 0xfb45 -- */ - {0x5e6, 0x5bc, 0}, /* 0xfb46 tsadi+dagesh */ - {0x5e7, 0x5bc, 0}, /* 0xfb47 qof+dagesh */ - {0x5e8, 0x5bc, 0}, /* 0xfb48 resh+dagesh */ - {0x5e9, 0x5bc, 0}, /* 0xfb49 shin+dagesh */ - {0x5ea, 0x5bc, 0}, /* 0xfb4a tav+dagesh */ - {0x5d5, 0x5b9, 0}, /* 0xfb4b vav+holam */ - {0x5d1, 0x5bf, 0}, /* 0xfb4c bet+rafe */ - {0x5db, 0x5bf, 0}, /* 0xfb4d kaf+rafe */ - {0x5e4, 0x5bf, 0}, /* 0xfb4e pe+rafe */ - {0x5d0, 0x5dc, 0} /* 0xfb4f alef-lamed */ + { 0x5e2, 0, 0 }, // 0xfb20 alt ayin + { 0x5d0, 0, 0 }, // 0xfb21 alt alef + { 0x5d3, 0, 0 }, // 0xfb22 alt dalet + { 0x5d4, 0, 0 }, // 0xfb23 alt he + { 0x5db, 0, 0 }, // 0xfb24 alt kaf + { 0x5dc, 0, 0 }, // 0xfb25 alt lamed + { 0x5dd, 0, 0 }, // 0xfb26 alt mem-sofit + { 0x5e8, 0, 0 }, // 0xfb27 alt resh + { 0x5ea, 0, 0 }, // 0xfb28 alt tav + { '+', 0, 0 }, // 0xfb29 alt plus + { 0x5e9, 0x5c1, 0 }, // 0xfb2a shin+shin-dot + { 0x5e9, 0x5c2, 0 }, // 0xfb2b shin+sin-dot + { 0x5e9, 0x5c1, 0x5bc }, // 0xfb2c shin+shin-dot+dagesh + { 0x5e9, 0x5c2, 0x5bc }, // 0xfb2d shin+sin-dot+dagesh + { 0x5d0, 0x5b7, 0 }, // 0xfb2e alef+patah + { 0x5d0, 0x5b8, 0 }, // 0xfb2f alef+qamats + { 0x5d0, 0x5b4, 0 }, // 0xfb30 alef+hiriq + { 0x5d1, 0x5bc, 0 }, // 0xfb31 bet+dagesh + { 0x5d2, 0x5bc, 0 }, // 0xfb32 gimel+dagesh + { 0x5d3, 0x5bc, 0 }, // 0xfb33 dalet+dagesh + { 0x5d4, 0x5bc, 0 }, // 0xfb34 he+dagesh + { 0x5d5, 0x5bc, 0 }, // 0xfb35 vav+dagesh + { 0x5d6, 0x5bc, 0 }, // 0xfb36 zayin+dagesh + { 0xfb37, 0, 0 }, // 0xfb37 -- UNUSED + { 0x5d8, 0x5bc, 0 }, // 0xfb38 tet+dagesh + { 0x5d9, 0x5bc, 0 }, // 0xfb39 yud+dagesh + { 0x5da, 0x5bc, 0 }, // 0xfb3a kaf sofit+dagesh + { 0x5db, 0x5bc, 0 }, // 0xfb3b kaf+dagesh + { 0x5dc, 0x5bc, 0 }, // 0xfb3c lamed+dagesh + { 0xfb3d, 0, 0 }, // 0xfb3d -- UNUSED + { 0x5de, 0x5bc, 0 }, // 0xfb3e mem+dagesh + { 0xfb3f, 0, 0 }, // 0xfb3f -- UNUSED + { 0x5e0, 0x5bc, 0 }, // 0xfb40 nun+dagesh + { 0x5e1, 0x5bc, 0 }, // 0xfb41 samech+dagesh + { 0xfb42, 0, 0 }, // 0xfb42 -- UNUSED + { 0x5e3, 0x5bc, 0 }, // 0xfb43 pe sofit+dagesh + { 0x5e4, 0x5bc, 0 }, // 0xfb44 pe+dagesh + { 0xfb45, 0, 0 }, // 0xfb45 -- UNUSED + { 0x5e6, 0x5bc, 0 }, // 0xfb46 tsadi+dagesh + { 0x5e7, 0x5bc, 0 }, // 0xfb47 qof+dagesh + { 0x5e8, 0x5bc, 0 }, // 0xfb48 resh+dagesh + { 0x5e9, 0x5bc, 0 }, // 0xfb49 shin+dagesh + { 0x5ea, 0x5bc, 0 }, // 0xfb4a tav+dagesh + { 0x5d5, 0x5b9, 0 }, // 0xfb4b vav+holam + { 0x5d1, 0x5bf, 0 }, // 0xfb4c bet+rafe + { 0x5db, 0x5bf, 0 }, // 0xfb4d kaf+rafe + { 0x5e4, 0x5bf, 0 }, // 0xfb4e pe+rafe + { 0x5d0, 0x5dc, 0 } // 0xfb4f alef-lamed }; static void mb_decompose(int c, int *c1, int *c2, int *c3) @@ -6447,12 +1442,53 @@ static int cstrncmp(char_u *s1, char_u *s2, int *n) return result; } +/// Wrapper around strchr which accounts for case-insensitive searches and +/// non-ASCII characters. +/// +/// This function is used a lot for simple searches, keep it fast! +/// +/// @param s string to search +/// @param c character to find in @a s +/// +/// @return NULL if no match, otherwise pointer to the position in @a s +static inline char_u *cstrchr(const char_u *const s, const int c) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL + FUNC_ATTR_ALWAYS_INLINE +{ + if (!rex.reg_ic) { + return vim_strchr(s, c); + } + + // Use folded case for UTF-8, slow! For ASCII use libc strpbrk which is + // expected to be highly optimized. + if (c > 0x80) { + const int folded_c = utf_fold(c); + for (const char_u *p = s; *p != NUL; p += utfc_ptr2len(p)) { + if (utf_fold(utf_ptr2char(p)) == folded_c) { + return (char_u *)p; + } + } + return NULL; + } + + int cc; + if (ASCII_ISUPPER(c)) { + cc = TOLOWER_ASC(c); + } else if (ASCII_ISLOWER(c)) { + cc = TOUPPER_ASC(c); + } else { + return vim_strchr(s, c); + } + + char tofind[] = { (char)c, (char)cc, NUL }; + return (char_u *)strpbrk((const char *)s, tofind); +} + //////////////////////////////////////////////////////////////// // regsub stuff // //////////////////////////////////////////////////////////////// -/* This stuff below really confuses cc on an SGI -- webb */ - +// This stuff below really confuses cc on an SGI -- webb static fptr_T do_upper(int *d, int c) @@ -6495,7 +1531,7 @@ static fptr_T do_Lower(int *d, int c) * * The tildes are parsed once before the first call to vim_regsub(). */ -char_u *regtilde(char_u *source, int magic) +char_u *regtilde(char_u *source, int magic, bool preview) { char_u *newsub = source; char_u *tmpsub; @@ -6506,13 +1542,13 @@ char_u *regtilde(char_u *source, int magic) for (p = newsub; *p; ++p) { if ((*p == '~' && magic) || (*p == '\\' && *(p + 1) == '~' && !magic)) { if (reg_prev_sub != NULL) { - /* length = len(newsub) - 1 + len(prev_sub) + 1 */ + // length = len(newsub) - 1 + len(prev_sub) + 1 prevlen = (int)STRLEN(reg_prev_sub); tmpsub = xmalloc(STRLEN(newsub) + prevlen); - /* copy prefix */ - len = (int)(p - newsub); /* not including ~ */ + // copy prefix + len = (int)(p - newsub); // not including ~ memmove(tmpsub, newsub, (size_t)len); - /* interpret tilde */ + // interpret tilde memmove(tmpsub + len, reg_prev_sub, (size_t)prevlen); // copy postfix if (!magic) { @@ -6520,15 +1556,17 @@ char_u *regtilde(char_u *source, int magic) } STRCPY(tmpsub + len + prevlen, p + 1); - if (newsub != source) /* already allocated newsub */ + if (newsub != source) { // already allocated newsub xfree(newsub); + } newsub = tmpsub; p = newsub + len + prevlen; - } else if (magic) - STRMOVE(p, p + 1); /* remove '~' */ - else - STRMOVE(p, p + 2); /* remove '\~' */ - --p; + } else if (magic) { + STRMOVE(p, p + 1); // remove '~' + } else { + STRMOVE(p, p + 2); // remove '\~' + } + p--; } else { if (*p == '\\' && p[1]) { // skip escaped characters p++; @@ -6537,11 +1575,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 (!preview) { + 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; } @@ -6642,7 +1685,8 @@ int vim_regsub(regmatch_T *rmp, char_u *source, typval_T *expr, char_u *dest, return result; } -int vim_regsub_multi(regmmatch_T *rmp, linenr_T lnum, char_u *source, char_u *dest, int copy, int magic, int backslash) +int vim_regsub_multi(regmmatch_T *rmp, linenr_T lnum, char_u *source, char_u *dest, + int copy, int magic, int backslash) { regexec_T rex_save; bool rex_in_use_save = rex_in_use; @@ -6680,8 +1724,8 @@ static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest, int no = -1; fptr_T func_all = (fptr_T)NULL; fptr_T func_one = (fptr_T)NULL; - linenr_T clnum = 0; /* init for GCC */ - int len = 0; /* init for GCC */ + linenr_T clnum = 0; // init for GCC + int len = 0; // init for GCC static char_u *eval_result = NULL; // We need to keep track of how many backslashes we escape, so that the byte @@ -6793,7 +1837,7 @@ static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest, } } if (had_backslash && backslash) { - /* Backslashes will be consumed, need to double them. */ + // Backslashes will be consumed, need to double them. s = vim_strsave_escaped(eval_result, (char_u *)"\\"); xfree(eval_result); eval_result = s; @@ -6833,9 +1877,9 @@ static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest, } } } - if (no < 0) { /* Ordinary character. */ + if (no < 0) { // Ordinary character. if (c == K_SPECIAL && src[0] != NUL && src[1] != NUL) { - /* Copy a special key as-is. */ + // Copy a special key as-is. if (copy) { *dst++ = c; *dst++ = *src++; @@ -6963,14 +2007,15 @@ static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest, } else { c = utf_ptr2char(s); - if (func_one != (fptr_T)NULL) - /* Turbo C complains without the typecast */ + if (func_one != (fptr_T)NULL) { + // Turbo C complains without the typecast func_one = (fptr_T)(func_one(&cc, c)); - else if (func_all != (fptr_T)NULL) - /* Turbo C complains without the typecast */ + } else if (func_all != (fptr_T)NULL) { + // Turbo C complains without the typecast func_all = (fptr_T)(func_all(&cc, c)); - else /* just copy */ + } else { // just copy cc = c; + } { int l; @@ -7165,6 +2210,12 @@ list_T *reg_submatch_list(int no) return list; } +// XXX Do not allow headers generator to catch definitions from regexp_nfa.c +#ifndef DO_NOT_DEFINE_EMPTY_ATTRIBUTES +# include "nvim/regexp_bt.c" +# include "nvim/regexp_nfa.c" +#endif + static regengine_T bt_regengine = { bt_regcomp, @@ -7174,12 +2225,6 @@ static regengine_T bt_regengine = (char_u *)"" }; - -// XXX Do not allow headers generator to catch definitions from regexp_nfa.c -#ifndef DO_NOT_DEFINE_EMPTY_ATTRIBUTES -# include "nvim/regexp_nfa.c" -#endif - static regengine_T nfa_regengine = { nfa_regcomp, @@ -7215,7 +2260,7 @@ regprog_T *vim_regcomp(char_u *expr_arg, int re_flags) regexp_engine = p_re; - /* Check for prefix "\%#=", that sets the regexp engine */ + // Check for prefix "\%#=", that sets the regexp engine if (STRNCMP(expr, "\\%#=", 4) == 0) { int newengine = expr[4] - '0'; @@ -7263,9 +2308,10 @@ regprog_T *vim_regcomp(char_u *expr_arg, int re_flags) if (f) { fprintf(f, "Syntax error in \"%s\"\n", expr); fclose(f); - } else + } else { semsg("(NFA) Could not open \"%s\" to write !!!", - BT_REGEXP_DEBUG_LOG_NAME); + BT_REGEXP_DEBUG_LOG_NAME); + } } #endif // If the NFA engine failed, try the backtracking engine. The NFA engine @@ -7299,6 +2345,18 @@ void vim_regfree(regprog_T *prog) prog->engine->regfree(prog); } + +#if defined(EXITFREE) +void free_regexp_stuff(void) +{ + ga_clear(®stack); + ga_clear(&backpos); + xfree(reg_tofree); + xfree(reg_prev_sub); +} + +#endif + static void report_re_switch(char_u *pat) { if (p_verbose > 0) { @@ -7321,8 +2379,7 @@ static void report_re_switch(char_u *pat) /// @param nl /// /// @return true if there is a match, false if not. -static bool vim_regexec_string(regmatch_T *rmp, char_u *line, colnr_T col, - bool nl) +static bool vim_regexec_string(regmatch_T *rmp, char_u *line, colnr_T col, bool nl) { regexec_T rex_save; bool rex_in_use_save = rex_in_use; @@ -7379,8 +2436,7 @@ static bool vim_regexec_string(regmatch_T *rmp, char_u *line, colnr_T col, // Note: "*prog" may be freed and changed. // Return true if there is a match, false if not. -bool vim_regexec_prog(regprog_T **prog, bool ignore_case, char_u *line, - colnr_T col) +bool vim_regexec_prog(regprog_T **prog, bool ignore_case, char_u *line, colnr_T col) { regmatch_T regmatch = { .regprog = *prog, .rm_ic = ignore_case }; bool r = vim_regexec_string(®match, line, col, false); @@ -7412,13 +2468,12 @@ bool vim_regexec_nl(regmatch_T *rmp, char_u *line, colnr_T col) /// match otherwise. long vim_regexec_multi( regmmatch_T *rmp, - win_T *win, // window in which to search or NULL - buf_T *buf, // buffer in which to search - linenr_T lnum, // nr of line to start looking for match - colnr_T col, // column to start looking for match - proftime_T *tm, // timeout limit or NULL - int *timed_out // flag is set when timeout limit reached -) + win_T *win, // window in which to search or NULL + buf_T *buf, // buffer in which to search + linenr_T lnum, // nr of line to start looking for match + colnr_T col, // column to start looking for match + proftime_T *tm, // timeout limit or NULL + int *timed_out) // flag is set when timeout limit reached FUNC_ATTR_NONNULL_ARG(1) { regexec_T rex_save; @@ -7449,7 +2504,8 @@ long vim_regexec_multi( char_u *pat = vim_strsave(((nfa_regprog_T *)rmp->regprog)->pattern); p_re = BACKTRACKING_ENGINE; - vim_regfree(rmp->regprog); + regprog_T *prev_prog = rmp->regprog; + report_re_switch(pat); // checking for \z misuse was already done when compiling for NFA, // allow all here @@ -7457,7 +2513,13 @@ long vim_regexec_multi( rmp->regprog = vim_regcomp(pat, re_flags); reg_do_extmatch = 0; - if (rmp->regprog != NULL) { + if (rmp->regprog == NULL) { + // Somehow compiling the pattern failed now, put back the + // previous one to avoid "regprog" becoming NULL. + rmp->regprog = prev_prog; + } else { + vim_regfree(prev_prog); + rmp->regprog->re_in_use = true; result = rmp->regprog->engine->regexec_multi(rmp, win, buf, lnum, col, tm, timed_out); diff --git a/src/nvim/regexp.h b/src/nvim/regexp.h index 9527afed58..6edbcf8a31 100644 --- a/src/nvim/regexp.h +++ b/src/nvim/regexp.h @@ -19,6 +19,7 @@ // regexp.c #ifdef INCLUDE_GENERATED_DECLARATIONS # include "regexp.h.generated.h" +# include "regexp_bt.h.generated.h" #endif #endif // NVIM_REGEXP_H diff --git a/src/nvim/regexp_bt.c b/src/nvim/regexp_bt.c new file mode 100644 index 0000000000..7d0b9a7a77 --- /dev/null +++ b/src/nvim/regexp_bt.c @@ -0,0 +1,5151 @@ +// 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 + +// uncrustify:off + +/* + * + * Backtracking regular expression implementation. + * + * This file is included in "regexp.c". + * + * NOTICE: + * + * This is NOT the original regular expression code as written by Henry + * Spencer. This code has been modified specifically for use with the VIM + * editor, and should not be used separately from Vim. If you want a good + * regular expression library, get the original code. The copyright notice + * that follows is from the original. + * + * END NOTICE + * + * Copyright (c) 1986 by University of Toronto. + * Written by Henry Spencer. Not derived from licensed software. + * + * Permission is granted to anyone to use this software for any + * purpose on any computer system, and to redistribute it freely, + * subject to the following restrictions: + * + * 1. The author is not responsible for the consequences of use of + * this software, no matter how awful, even if they arise + * from defects in it. + * + * 2. The origin of this software must not be misrepresented, either + * by explicit claim or by omission. + * + * 3. Altered versions must be plainly marked as such, and must not + * be misrepresented as being the original software. + * + * Beware that some of this code is subtly aware of the way operator + * precedence is structured in regular expressions. Serious changes in + * regular-expression syntax might require a total rethink. + * + * Changes have been made by Tony Andrews, Olaf 'Rhialto' Seibert, Robert + * Webb, Ciaran McCreesh and Bram Moolenaar. + * Named character class support added by Walter Briscoe (1998 Jul 01) + */ + + +/* + * The "internal use only" fields in regexp_defs.h are present to pass info from + * compile to execute that permits the execute phase to run lots faster on + * simple cases. They are: + * + * regstart char that must begin a match; NUL if none obvious; Can be a + * multi-byte character. + * reganch is the match anchored (at beginning-of-line only)? + * regmust string (pointer into program) that match must include, or NULL + * regmlen length of regmust string + * regflags RF_ values or'ed together + * + * Regstart and reganch permit very fast decisions on suitable starting points + * for a match, cutting down the work a lot. Regmust permits fast rejection + * of lines that cannot possibly match. The regmust tests are costly enough + * that vim_regcomp() supplies a regmust only if the r.e. contains something + * potentially expensive (at present, the only such thing detected is * or + + * at the start of the r.e., which can involve a lot of backup). Regmlen is + * supplied because the test in vim_regexec() needs it and vim_regcomp() is + * computing it anyway. + */ + +/* + * Structure for regexp "program". This is essentially a linear encoding + * of a nondeterministic finite-state machine (aka syntax charts or + * "railroad normal form" in parsing technology). Each node is an opcode + * plus a "next" pointer, possibly plus an operand. "Next" pointers of + * all nodes except BRANCH and BRACES_COMPLEX implement concatenation; a "next" + * pointer with a BRANCH on both ends of it is connecting two alternatives. + * (Here we have one of the subtle syntax dependencies: an individual BRANCH + * (as opposed to a collection of them) is never concatenated with anything + * because of operator precedence). The "next" pointer of a BRACES_COMPLEX + * node points to the node after the stuff to be repeated. + * The operand of some types of node is a literal string; for others, it is a + * node leading into a sub-FSM. In particular, the operand of a BRANCH node + * is the first node of the branch. + * (NB this is *not* a tree structure: the tail of the branch connects to the + * thing following the set of BRANCHes.) + * + * pattern is coded like: + * + * +-----------------+ + * | V + * <aa>\|<bb> BRANCH <aa> BRANCH <bb> --> END + * | ^ | ^ + * +------+ +----------+ + * + * + * +------------------+ + * V | + * <aa>* BRANCH BRANCH <aa> --> BACK BRANCH --> NOTHING --> END + * | | ^ ^ + * | +---------------+ | + * +---------------------------------------------+ + * + * + * +----------------------+ + * V | + * <aa>\+ BRANCH <aa> --> BRANCH --> BACK BRANCH --> NOTHING --> END + * | | ^ ^ + * | +-----------+ | + * +--------------------------------------------------+ + * + * + * +-------------------------+ + * V | + * <aa>\{} BRANCH BRACE_LIMITS --> BRACE_COMPLEX <aa> --> BACK END + * | | ^ + * | +----------------+ + * +-----------------------------------------------+ + * + * + * <aa>\@!<bb> BRANCH NOMATCH <aa> --> END <bb> --> END + * | | ^ ^ + * | +----------------+ | + * +--------------------------------+ + * + * +---------+ + * | V + * \z[abc] BRANCH BRANCH a BRANCH b BRANCH c BRANCH NOTHING --> END + * | | | | ^ ^ + * | | | +-----+ | + * | | +----------------+ | + * | +---------------------------+ | + * +------------------------------------------------------+ + * + * They all start with a BRANCH for "\|" alternatives, even when there is only + * one alternative. + */ + +#include <assert.h> +#include <inttypes.h> +#include <stdbool.h> +#include <string.h> + +#include "nvim/regexp.h" +#include "nvim/garray.h" + +/* + * The opcodes are: + */ + +// definition number opnd? meaning +#define END 0 // End of program or NOMATCH operand. +#define BOL 1 // Match "" at beginning of line. +#define EOL 2 // Match "" at end of line. +#define BRANCH 3 // node Match this alternative, or the + // next... +#define BACK 4 // Match "", "next" ptr points backward. +#define EXACTLY 5 // str Match this string. +#define NOTHING 6 // Match empty string. +#define STAR 7 // node Match this (simple) thing 0 or more + // times. +#define PLUS 8 // node Match this (simple) thing 1 or more + // times. +#define MATCH 9 // node match the operand zero-width +#define NOMATCH 10 // node check for no match with operand +#define BEHIND 11 // node look behind for a match with operand +#define NOBEHIND 12 // node look behind for no match with operand +#define SUBPAT 13 // node match the operand here +#define BRACE_SIMPLE 14 // node Match this (simple) thing between m and + // n times (\{m,n\}). +#define BOW 15 // Match "" after [^a-zA-Z0-9_] +#define EOW 16 // Match "" at [^a-zA-Z0-9_] +#define BRACE_LIMITS 17 // nr nr define the min & max for BRACE_SIMPLE + // and BRACE_COMPLEX. +#define NEWL 18 // Match line-break +#define BHPOS 19 // End position for BEHIND or NOBEHIND + + +// character classes: 20-48 normal, 50-78 include a line-break +#define ADD_NL 30 +#define FIRST_NL ANY + ADD_NL +#define ANY 20 // Match any one character. +#define ANYOF 21 // str Match any character in this string. +#define ANYBUT 22 // str Match any character not in this + // string. +#define IDENT 23 // Match identifier char +#define SIDENT 24 // Match identifier char but no digit +#define KWORD 25 // Match keyword char +#define SKWORD 26 // Match word char but no digit +#define FNAME 27 // Match file name char +#define SFNAME 28 // Match file name char but no digit +#define PRINT 29 // Match printable char +#define SPRINT 30 // Match printable char but no digit +#define WHITE 31 // Match whitespace char +#define NWHITE 32 // Match non-whitespace char +#define DIGIT 33 // Match digit char +#define NDIGIT 34 // Match non-digit char +#define HEX 35 // Match hex char +#define NHEX 36 // Match non-hex char +#define OCTAL 37 // Match octal char +#define NOCTAL 38 // Match non-octal char +#define WORD 39 // Match word char +#define NWORD 40 // Match non-word char +#define HEAD 41 // Match head char +#define NHEAD 42 // Match non-head char +#define ALPHA 43 // Match alpha char +#define NALPHA 44 // Match non-alpha char +#define LOWER 45 // Match lowercase char +#define NLOWER 46 // Match non-lowercase char +#define UPPER 47 // Match uppercase char +#define NUPPER 48 // Match non-uppercase char +#define LAST_NL NUPPER + ADD_NL +// -V:WITH_NL:560 +#define WITH_NL(op) ((op) >= FIRST_NL && (op) <= LAST_NL) + +#define MOPEN 80 // -89 Mark this point in input as start of + // \( … \) subexpr. MOPEN + 0 marks start of + // match. +#define MCLOSE 90 // -99 Analogous to MOPEN. MCLOSE + 0 marks + // end of match. +#define BACKREF 100 // -109 node Match same string again \1-\9. + +# define ZOPEN 110 // -119 Mark this point in input as start of + // \z( … \) subexpr. +# define ZCLOSE 120 // -129 Analogous to ZOPEN. +# define ZREF 130 // -139 node Match external submatch \z1-\z9 + +#define BRACE_COMPLEX 140 // -149 node Match nodes between m & n times + +#define NOPEN 150 // Mark this point in input as start of + // \%( subexpr. +#define NCLOSE 151 // Analogous to NOPEN. + +#define MULTIBYTECODE 200 // mbc Match one multi-byte character +#define RE_BOF 201 // Match "" at beginning of file. +#define RE_EOF 202 // Match "" at end of file. +#define CURSOR 203 // Match location of cursor. + +#define RE_LNUM 204 // nr cmp Match line number +#define RE_COL 205 // nr cmp Match column number +#define RE_VCOL 206 // nr cmp Match virtual column number + +#define RE_MARK 207 // mark cmp Match mark position +#define RE_VISUAL 208 // Match Visual area +#define RE_COMPOSING 209 // any composing characters + +/* + * Flags to be passed up and down. + */ +#define HASWIDTH 0x1 // Known never to match null string. +#define SIMPLE 0x2 // Simple enough to be STAR/PLUS operand. +#define SPSTART 0x4 // Starts with * or +. +#define HASNL 0x8 // Contains some \n. +#define HASLOOKBH 0x10 // Contains "\@<=" or "\@<!". +#define WORST 0 // Worst case. + + +static int prevchr_len; ///< byte length of previous char +static int num_complex_braces; ///< Complex \{...} count +static char_u *regcode; ///< Code-emit pointer, or JUST_CALC_SIZE +static long regsize; ///< Code size. +static int reg_toolong; ///< true when offset out of range +static char_u had_endbrace[NSUBEXP]; ///< flags, true if end of () found +static long brace_min[10]; ///< Minimums for complex brace repeats +static long brace_max[10]; ///< Maximums for complex brace repeats +static int brace_count[10]; ///< Current counts for complex brace repeats +static int one_exactly = false; ///< only do one char for EXACTLY + +// When making changes to classchars also change nfa_classcodes. +static char_u *classchars = (char_u *)".iIkKfFpPsSdDxXoOwWhHaAlLuU"; +static int classcodes[] = { + ANY, IDENT, SIDENT, KWORD, SKWORD, + FNAME, SFNAME, PRINT, SPRINT, + WHITE, NWHITE, DIGIT, NDIGIT, + HEX, NHEX, OCTAL, NOCTAL, + WORD, NWORD, HEAD, NHEAD, + ALPHA, NALPHA, LOWER, NLOWER, + UPPER, NUPPER +}; + +/* + * When regcode is set to this value, code is not emitted and size is computed + * instead. + */ +#define JUST_CALC_SIZE ((char_u *) -1) + +// Values for rs_state in regitem_T. +typedef enum regstate_E { + RS_NOPEN = 0, // NOPEN and NCLOSE + RS_MOPEN, // MOPEN + [0-9] + RS_MCLOSE, // MCLOSE + [0-9] + RS_ZOPEN, // ZOPEN + [0-9] + RS_ZCLOSE, // ZCLOSE + [0-9] + RS_BRANCH, // BRANCH + RS_BRCPLX_MORE, // BRACE_COMPLEX and trying one more match + RS_BRCPLX_LONG, // BRACE_COMPLEX and trying longest match + RS_BRCPLX_SHORT, // BRACE_COMPLEX and trying shortest match + RS_NOMATCH, // NOMATCH + RS_BEHIND1, // BEHIND / NOBEHIND matching rest + RS_BEHIND2, // BEHIND / NOBEHIND matching behind part + RS_STAR_LONG, // STAR/PLUS/BRACE_SIMPLE longest match + RS_STAR_SHORT // STAR/PLUS/BRACE_SIMPLE shortest match +} regstate_T; + +/* + * Structure used to save the current input state, when it needs to be + * restored after trying a match. Used by reg_save() and reg_restore(). + * Also stores the length of "backpos". + */ +typedef struct +{ + union + { + char_u *ptr; // rex.input pointer, for single-line regexp + lpos_T pos; // rex.input pos, for multi-line regexp + } rs_u; + int rs_len; +} regsave_T; + +// struct to save start/end pointer/position in for \(\) +typedef struct +{ + union + { + char_u *ptr; + lpos_T pos; + } se_u; +} save_se_T; + +// used for BEHIND and NOBEHIND matching +typedef struct regbehind_S +{ + regsave_T save_after; + regsave_T save_behind; + int save_need_clear_subexpr; + save_se_T save_start[NSUBEXP]; + save_se_T save_end[NSUBEXP]; +} regbehind_T; + +/* + * When there are alternatives a regstate_T is put on the regstack to remember + * what we are doing. + * Before it may be another type of item, depending on rs_state, to remember + * more things. + */ +typedef struct regitem_S +{ + regstate_T rs_state; // what we are doing, one of RS_ above + short rs_no; // submatch nr or BEHIND/NOBEHIND + char_u *rs_scan; // current node in program + union + { + save_se_T sesave; + regsave_T regsave; + } rs_un; // room for saving rex.input +} regitem_T; + + +// used for STAR, PLUS and BRACE_SIMPLE matching +typedef struct regstar_S +{ + int nextb; // next byte + int nextb_ic; // next byte reverse case + long count; + long minval; + long maxval; +} regstar_T; + +// used to store input position when a BACK was encountered, so that we now if +// we made any progress since the last time. +typedef struct backpos_S +{ + char_u *bp_scan; // "scan" where BACK was encountered + regsave_T bp_pos; // last input position +} backpos_T; + +/* + * "regstack" and "backpos" are used by regmatch(). They are kept over calls + * to avoid invoking malloc() and free() often. + * "regstack" is a stack with regitem_T items, sometimes preceded by regstar_T + * or regbehind_T. + * "backpos_T" is a table with backpos_T for BACK + */ +static garray_T regstack = GA_EMPTY_INIT_VALUE; +static garray_T backpos = GA_EMPTY_INIT_VALUE; + +static regsave_T behind_pos; + +/* + * Both for regstack and backpos tables we use the following strategy of + * allocation (to reduce malloc/free calls): + * - Initial size is fairly small. + * - When needed, the tables are grown bigger (8 times at first, double after + * that). + * - After executing the match we free the memory only if the array has grown. + * Thus the memory is kept allocated when it's at the initial size. + * This makes it fast while not keeping a lot of memory allocated. + * A three times speed increase was observed when using many simple patterns. + */ +#define REGSTACK_INITIAL 2048 +#define BACKPOS_INITIAL 64 + +/* + * Opcode notes: + * + * BRANCH The set of branches constituting a single choice are hooked + * together with their "next" pointers, since precedence prevents + * anything being concatenated to any individual branch. The + * "next" pointer of the last BRANCH in a choice points to the + * thing following the whole choice. This is also where the + * final "next" pointer of each individual branch points; each + * branch starts with the operand node of a BRANCH node. + * + * BACK Normal "next" pointers all implicitly point forward; BACK + * exists to make loop structures possible. + * + * STAR,PLUS '=', and complex '*' and '+', are implemented as circular + * BRANCH structures using BACK. Simple cases (one character + * per match) are implemented with STAR and PLUS for speed + * and to minimize recursive plunges. + * + * BRACE_LIMITS This is always followed by a BRACE_SIMPLE or BRACE_COMPLEX + * node, and defines the min and max limits to be used for that + * node. + * + * MOPEN,MCLOSE ...are numbered at compile time. + * ZOPEN,ZCLOSE ...ditto + */ + +/* + * A node is one char of opcode followed by two chars of "next" pointer. + * "Next" pointers are stored as two 8-bit bytes, high order first. The + * value is a positive offset from the opcode of the node containing it. + * An operand, if any, simply follows the node. (Note that much of the + * code generation knows about this implicit relationship.) + * + * Using two bytes for the "next" pointer is vast overkill for most things, + * but allows patterns to get big without disasters. + */ +#define OP(p) ((int)(*(p))) +#define NEXT(p) (((*((p) + 1) & 0377) << 8) + (*((p) + 2) & 0377)) +#define OPERAND(p) ((p) + 3) +// Obtain an operand that was stored as four bytes, MSB first. +#define OPERAND_MIN(p) (((long)(p)[3] << 24) + ((long)(p)[4] << 16) \ + + ((long)(p)[5] << 8) + (long)(p)[6]) +// Obtain a second operand stored as four bytes. +#define OPERAND_MAX(p) OPERAND_MIN((p) + 4) +// Obtain a second single-byte operand stored after a four bytes operand. +#define OPERAND_CMP(p) (p)[7] + +static char_u *reg(int paren, int *flagp); + +#ifdef BT_REGEXP_DUMP +static void regdump(char_u *, bt_regprog_T *); +#endif + + +#ifdef REGEXP_DEBUG +static char_u *regprop(char_u *); + +static int regnarrate = 0; +#endif + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "regexp_bt.c.generated.h" +#endif + + +/* + * Setup to parse the regexp. Used once to get the length and once to do it. + */ +static void regcomp_start( + char_u *expr, + int re_flags) // see vim_regcomp() +{ + initchr(expr); + if (re_flags & RE_MAGIC) + reg_magic = MAGIC_ON; + else + reg_magic = MAGIC_OFF; + reg_string = (re_flags & RE_STRING); + reg_strict = (re_flags & RE_STRICT); + get_cpo_flags(); + + num_complex_braces = 0; + regnpar = 1; + memset(had_endbrace, 0, sizeof(had_endbrace)); + regnzpar = 1; + re_has_z = 0; + regsize = 0L; + reg_toolong = false; + regflags = 0; + had_eol = false; +} + +// Return true if MULTIBYTECODE should be used instead of EXACTLY for +// character "c". +static bool use_multibytecode(int c) +{ + return utf_char2len(c) > 1 + && (re_multi_type(peekchr()) != NOT_MULTI + || utf_iscomposing(c)); +} + + +/* + * Emit (if appropriate) a byte of code + */ +static void regc(int b) +{ + if (regcode == JUST_CALC_SIZE) + regsize++; + else + *regcode++ = b; +} + +/* + * Emit (if appropriate) a multi-byte character of code + */ +static void regmbc(int c) +{ + if (regcode == JUST_CALC_SIZE) { + regsize += utf_char2len(c); + } else { + regcode += utf_char2bytes(c, regcode); + } +} + + +/* + * Produce the bytes for equivalence class "c". + * Currently only handles latin1, latin9 and utf-8. + * NOTE: When changing this function, also change nfa_emit_equi_class() + */ +static void reg_equi_class(int c) +{ + { + switch (c) { + // Do not use '\300' style, it results in a negative number. + case 'A': case 0xc0: case 0xc1: case 0xc2: case 0xc3: case 0xc4: + case 0xc5: case 0x100: case 0x102: case 0x104: case 0x1cd: + case 0x1de: case 0x1e0: case 0x1fa: case 0x202: case 0x226: + case 0x23a: case 0x1e00: case 0x1ea0: case 0x1ea2: case 0x1ea4: + case 0x1ea6: case 0x1ea8: case 0x1eaa: case 0x1eac: case 0x1eae: + case 0x1eb0: case 0x1eb2: case 0x1eb4: case 0x1eb6: + regmbc('A'); regmbc(0xc0); regmbc(0xc1); regmbc(0xc2); + regmbc(0xc3); regmbc(0xc4); regmbc(0xc5); + regmbc(0x100); regmbc(0x102); regmbc(0x104); + regmbc(0x1cd); regmbc(0x1de); regmbc(0x1e0); + regmbc(0x1fa); regmbc(0x202); regmbc(0x226); + regmbc(0x23a); regmbc(0x1e00); regmbc(0x1ea0); + regmbc(0x1ea2); regmbc(0x1ea4); regmbc(0x1ea6); + regmbc(0x1ea8); regmbc(0x1eaa); regmbc(0x1eac); + regmbc(0x1eae); regmbc(0x1eb0); regmbc(0x1eb2); + regmbc(0x1eb4); regmbc(0x1eb6); + return; + case 'B': case 0x181: case 0x243: case 0x1e02: + case 0x1e04: case 0x1e06: + regmbc('B'); + regmbc(0x181); regmbc(0x243); regmbc(0x1e02); + regmbc(0x1e04); regmbc(0x1e06); + return; + case 'C': case 0xc7: + case 0x106: case 0x108: case 0x10a: case 0x10c: case 0x187: + case 0x23b: case 0x1e08: case 0xa792: + regmbc('C'); regmbc(0xc7); + regmbc(0x106); regmbc(0x108); regmbc(0x10a); + regmbc(0x10c); regmbc(0x187); regmbc(0x23b); + regmbc(0x1e08); regmbc(0xa792); + return; + case 'D': case 0x10e: case 0x110: case 0x18a: + case 0x1e0a: case 0x1e0c: case 0x1e0e: case 0x1e10: + case 0x1e12: + regmbc('D'); regmbc(0x10e); regmbc(0x110); + regmbc(0x18a); regmbc(0x1e0a); regmbc(0x1e0c); + regmbc(0x1e0e); regmbc(0x1e10); regmbc(0x1e12); + return; + case 'E': case 0xc8: case 0xc9: case 0xca: case 0xcb: + case 0x112: case 0x114: case 0x116: case 0x118: case 0x11a: + case 0x204: case 0x206: case 0x228: case 0x246: case 0x1e14: + case 0x1e16: case 0x1e18: case 0x1e1a: case 0x1e1c: + case 0x1eb8: case 0x1eba: case 0x1ebc: case 0x1ebe: + case 0x1ec0: case 0x1ec2: case 0x1ec4: case 0x1ec6: + regmbc('E'); regmbc(0xc8); regmbc(0xc9); + regmbc(0xca); regmbc(0xcb); regmbc(0x112); + regmbc(0x114); regmbc(0x116); regmbc(0x118); + regmbc(0x11a); regmbc(0x204); regmbc(0x206); + regmbc(0x228); regmbc(0x246); regmbc(0x1e14); + regmbc(0x1e16); regmbc(0x1e18); regmbc(0x1e1a); + regmbc(0x1e1c); regmbc(0x1eb8); regmbc(0x1eba); + regmbc(0x1ebc); regmbc(0x1ebe); regmbc(0x1ec0); + regmbc(0x1ec2); regmbc(0x1ec4); regmbc(0x1ec6); + return; + case 'F': case 0x191: case 0x1e1e: case 0xa798: + regmbc('F'); regmbc(0x191); regmbc(0x1e1e); + regmbc(0xa798); + return; + case 'G': case 0x11c: case 0x11e: case 0x120: + case 0x122: case 0x193: case 0x1e4: case 0x1e6: + case 0x1f4: case 0x1e20: case 0xa7a0: + regmbc('G'); regmbc(0x11c); regmbc(0x11e); + regmbc(0x120); regmbc(0x122); regmbc(0x193); + regmbc(0x1e4); regmbc(0x1e6); regmbc(0x1f4); + regmbc(0x1e20); regmbc(0xa7a0); + return; + case 'H': case 0x124: case 0x126: case 0x21e: + case 0x1e22: case 0x1e24: case 0x1e26: + case 0x1e28: case 0x1e2a: case 0x2c67: + regmbc('H'); regmbc(0x124); regmbc(0x126); + regmbc(0x21e); regmbc(0x1e22); regmbc(0x1e24); + regmbc(0x1e26); regmbc(0x1e28); regmbc(0x1e2a); + regmbc(0x2c67); + return; + case 'I': case 0xcc: case 0xcd: case 0xce: case 0xcf: + case 0x128: case 0x12a: case 0x12c: case 0x12e: + case 0x130: case 0x197: case 0x1cf: case 0x208: + case 0x20a: case 0x1e2c: case 0x1e2e: case 0x1ec8: + case 0x1eca: + regmbc('I'); regmbc(0xcc); regmbc(0xcd); + regmbc(0xce); regmbc(0xcf); regmbc(0x128); + regmbc(0x12a); regmbc(0x12c); regmbc(0x12e); + regmbc(0x130); regmbc(0x197); regmbc(0x1cf); + regmbc(0x208); regmbc(0x20a); regmbc(0x1e2c); + regmbc(0x1e2e); regmbc(0x1ec8); regmbc(0x1eca); + return; + case 'J': case 0x134: case 0x248: + regmbc('J'); regmbc(0x134); regmbc(0x248); + return; + case 'K': case 0x136: case 0x198: case 0x1e8: case 0x1e30: + case 0x1e32: case 0x1e34: case 0x2c69: case 0xa740: + regmbc('K'); regmbc(0x136); regmbc(0x198); + regmbc(0x1e8); regmbc(0x1e30); regmbc(0x1e32); + regmbc(0x1e34); regmbc(0x2c69); regmbc(0xa740); + return; + case 'L': case 0x139: case 0x13b: case 0x13d: case 0x13f: + case 0x141: case 0x23d: case 0x1e36: case 0x1e38: + case 0x1e3a: case 0x1e3c: case 0x2c60: + regmbc('L'); regmbc(0x139); regmbc(0x13b); + regmbc(0x13d); regmbc(0x13f); regmbc(0x141); + regmbc(0x23d); regmbc(0x1e36); regmbc(0x1e38); + regmbc(0x1e3a); regmbc(0x1e3c); regmbc(0x2c60); + return; + case 'M': case 0x1e3e: case 0x1e40: case 0x1e42: + regmbc('M'); regmbc(0x1e3e); regmbc(0x1e40); + regmbc(0x1e42); + return; + case 'N': case 0xd1: + case 0x143: case 0x145: case 0x147: case 0x1f8: + case 0x1e44: case 0x1e46: case 0x1e48: case 0x1e4a: + case 0xa7a4: + regmbc('N'); regmbc(0xd1); + regmbc(0x143); regmbc(0x145); regmbc(0x147); + regmbc(0x1f8); regmbc(0x1e44); regmbc(0x1e46); + regmbc(0x1e48); regmbc(0x1e4a); regmbc(0xa7a4); + return; + case 'O': case 0xd2: case 0xd3: case 0xd4: case 0xd5: case 0xd6: + case 0xd8: case 0x14c: case 0x14e: case 0x150: case 0x19f: + case 0x1a0: case 0x1d1: case 0x1ea: case 0x1ec: case 0x1fe: + case 0x20c: case 0x20e: case 0x22a: case 0x22c: case 0x22e: + case 0x230: case 0x1e4c: case 0x1e4e: case 0x1e50: case 0x1e52: + case 0x1ecc: case 0x1ece: case 0x1ed0: case 0x1ed2: case 0x1ed4: + case 0x1ed6: case 0x1ed8: case 0x1eda: case 0x1edc: case 0x1ede: + case 0x1ee0: case 0x1ee2: + regmbc('O'); regmbc(0xd2); regmbc(0xd3); regmbc(0xd4); + regmbc(0xd5); regmbc(0xd6); regmbc(0xd8); + regmbc(0x14c); regmbc(0x14e); regmbc(0x150); + regmbc(0x19f); regmbc(0x1a0); regmbc(0x1d1); + regmbc(0x1ea); regmbc(0x1ec); regmbc(0x1fe); + regmbc(0x20c); regmbc(0x20e); regmbc(0x22a); + regmbc(0x22c); regmbc(0x22e); regmbc(0x230); + regmbc(0x1e4c); regmbc(0x1e4e); regmbc(0x1e50); + regmbc(0x1e52); regmbc(0x1ecc); regmbc(0x1ece); + regmbc(0x1ed0); regmbc(0x1ed2); regmbc(0x1ed4); + regmbc(0x1ed6); regmbc(0x1ed8); regmbc(0x1eda); + regmbc(0x1edc); regmbc(0x1ede); regmbc(0x1ee0); + regmbc(0x1ee2); + return; + case 'P': case 0x1a4: case 0x1e54: case 0x1e56: case 0x2c63: + regmbc('P'); regmbc(0x1a4); regmbc(0x1e54); + regmbc(0x1e56); regmbc(0x2c63); + return; + case 'Q': case 0x24a: + regmbc('Q'); regmbc(0x24a); + return; + case 'R': case 0x154: case 0x156: case 0x158: case 0x210: + case 0x212: case 0x24c: case 0x1e58: case 0x1e5a: + case 0x1e5c: case 0x1e5e: case 0x2c64: case 0xa7a6: + regmbc('R'); regmbc(0x154); regmbc(0x156); + regmbc(0x210); regmbc(0x212); regmbc(0x158); + regmbc(0x24c); regmbc(0x1e58); regmbc(0x1e5a); + regmbc(0x1e5c); regmbc(0x1e5e); regmbc(0x2c64); + regmbc(0xa7a6); + return; + case 'S': case 0x15a: case 0x15c: case 0x15e: case 0x160: + case 0x218: case 0x1e60: case 0x1e62: case 0x1e64: + case 0x1e66: case 0x1e68: case 0x2c7e: case 0xa7a8: + regmbc('S'); regmbc(0x15a); regmbc(0x15c); + regmbc(0x15e); regmbc(0x160); regmbc(0x218); + regmbc(0x1e60); regmbc(0x1e62); regmbc(0x1e64); + regmbc(0x1e66); regmbc(0x1e68); regmbc(0x2c7e); + regmbc(0xa7a8); + return; + case 'T': case 0x162: case 0x164: case 0x166: case 0x1ac: + case 0x1ae: case 0x21a: case 0x23e: case 0x1e6a: case 0x1e6c: + case 0x1e6e: case 0x1e70: + regmbc('T'); regmbc(0x162); regmbc(0x164); + regmbc(0x166); regmbc(0x1ac); regmbc(0x23e); + regmbc(0x1ae); regmbc(0x21a); regmbc(0x1e6a); + regmbc(0x1e6c); regmbc(0x1e6e); regmbc(0x1e70); + return; + case 'U': case 0xd9: case 0xda: case 0xdb: case 0xdc: + case 0x168: case 0x16a: case 0x16c: case 0x16e: + case 0x170: case 0x172: case 0x1af: case 0x1d3: + case 0x1d5: case 0x1d7: case 0x1d9: case 0x1db: + case 0x214: case 0x216: case 0x244: case 0x1e72: + case 0x1e74: case 0x1e76: case 0x1e78: case 0x1e7a: + case 0x1ee4: case 0x1ee6: case 0x1ee8: case 0x1eea: + case 0x1eec: case 0x1eee: case 0x1ef0: + regmbc('U'); regmbc(0xd9); regmbc(0xda); + regmbc(0xdb); regmbc(0xdc); regmbc(0x168); + regmbc(0x16a); regmbc(0x16c); regmbc(0x16e); + regmbc(0x170); regmbc(0x172); regmbc(0x1af); + regmbc(0x1d3); regmbc(0x1d5); regmbc(0x1d7); + regmbc(0x1d9); regmbc(0x1db); regmbc(0x214); + regmbc(0x216); regmbc(0x244); regmbc(0x1e72); + regmbc(0x1e74); regmbc(0x1e76); regmbc(0x1e78); + regmbc(0x1e7a); regmbc(0x1ee4); regmbc(0x1ee6); + regmbc(0x1ee8); regmbc(0x1eea); regmbc(0x1eec); + regmbc(0x1eee); regmbc(0x1ef0); + return; + case 'V': case 0x1b2: case 0x1e7c: case 0x1e7e: + regmbc('V'); regmbc(0x1b2); regmbc(0x1e7c); + regmbc(0x1e7e); + return; + case 'W': case 0x174: case 0x1e80: case 0x1e82: + case 0x1e84: case 0x1e86: case 0x1e88: + regmbc('W'); regmbc(0x174); regmbc(0x1e80); + regmbc(0x1e82); regmbc(0x1e84); regmbc(0x1e86); + regmbc(0x1e88); + return; + case 'X': case 0x1e8a: case 0x1e8c: + regmbc('X'); regmbc(0x1e8a); regmbc(0x1e8c); + return; + case 'Y': case 0xdd: + case 0x176: case 0x178: case 0x1b3: case 0x232: case 0x24e: + case 0x1e8e: case 0x1ef2: case 0x1ef6: case 0x1ef4: case 0x1ef8: + regmbc('Y'); regmbc(0xdd); regmbc(0x176); + regmbc(0x178); regmbc(0x1b3); regmbc(0x232); + regmbc(0x24e); regmbc(0x1e8e); regmbc(0x1ef2); + regmbc(0x1ef4); regmbc(0x1ef6); regmbc(0x1ef8); + return; + case 'Z': case 0x179: case 0x17b: case 0x17d: case 0x1b5: + case 0x1e90: case 0x1e92: case 0x1e94: case 0x2c6b: + regmbc('Z'); regmbc(0x179); regmbc(0x17b); + regmbc(0x17d); regmbc(0x1b5); regmbc(0x1e90); + regmbc(0x1e92); regmbc(0x1e94); regmbc(0x2c6b); + return; + case 'a': case 0xe0: case 0xe1: case 0xe2: + case 0xe3: case 0xe4: case 0xe5: case 0x101: case 0x103: + case 0x105: case 0x1ce: case 0x1df: case 0x1e1: case 0x1fb: + case 0x201: case 0x203: case 0x227: case 0x1d8f: case 0x1e01: + case 0x1e9a: case 0x1ea1: case 0x1ea3: case 0x1ea5: + case 0x1ea7: case 0x1ea9: case 0x1eab: case 0x1ead: + case 0x1eaf: case 0x1eb1: case 0x1eb3: case 0x1eb5: + case 0x1eb7: case 0x2c65: + regmbc('a'); regmbc(0xe0); regmbc(0xe1); + regmbc(0xe2); regmbc(0xe3); regmbc(0xe4); + regmbc(0xe5); regmbc(0x101); regmbc(0x103); + regmbc(0x105); regmbc(0x1ce); regmbc(0x1df); + regmbc(0x1e1); regmbc(0x1fb); regmbc(0x201); + regmbc(0x203); regmbc(0x227); regmbc(0x1d8f); + regmbc(0x1e01); regmbc(0x1e9a); regmbc(0x1ea1); + regmbc(0x1ea3); regmbc(0x1ea5); regmbc(0x1ea7); + regmbc(0x1ea9); regmbc(0x1eab); regmbc(0x1ead); + regmbc(0x1eaf); regmbc(0x1eb1); regmbc(0x1eb3); + regmbc(0x1eb5); regmbc(0x1eb7); regmbc(0x2c65); + return; + case 'b': case 0x180: case 0x253: case 0x1d6c: case 0x1d80: + case 0x1e03: case 0x1e05: case 0x1e07: + regmbc('b'); + regmbc(0x180); regmbc(0x253); regmbc(0x1d6c); + regmbc(0x1d80); regmbc(0x1e03); regmbc(0x1e05); + regmbc(0x1e07); + return; + case 'c': case 0xe7: + case 0x107: case 0x109: case 0x10b: case 0x10d: case 0x188: + case 0x23c: case 0x1e09: case 0xa793: case 0xa794: + regmbc('c'); regmbc(0xe7); regmbc(0x107); + regmbc(0x109); regmbc(0x10b); regmbc(0x10d); + regmbc(0x188); regmbc(0x23c); regmbc(0x1e09); + regmbc(0xa793); regmbc(0xa794); + return; + case 'd': case 0x10f: case 0x111: case 0x257: case 0x1d6d: + case 0x1d81: case 0x1d91: case 0x1e0b: case 0x1e0d: + case 0x1e0f: case 0x1e11: case 0x1e13: + regmbc('d'); regmbc(0x10f); regmbc(0x111); + regmbc(0x257); regmbc(0x1d6d); regmbc(0x1d81); + regmbc(0x1d91); regmbc(0x1e0b); regmbc(0x1e0d); + regmbc(0x1e0f); regmbc(0x1e11); regmbc(0x1e13); + return; + case 'e': case 0xe8: case 0xe9: case 0xea: case 0xeb: + case 0x113: case 0x115: case 0x117: case 0x119: + case 0x11b: case 0x205: case 0x207: case 0x229: + case 0x247: case 0x1d92: case 0x1e15: case 0x1e17: + case 0x1e19: case 0x1e1b: case 0x1eb9: case 0x1ebb: + case 0x1e1d: case 0x1ebd: case 0x1ebf: case 0x1ec1: + case 0x1ec3: case 0x1ec5: case 0x1ec7: + regmbc('e'); regmbc(0xe8); regmbc(0xe9); + regmbc(0xea); regmbc(0xeb); regmbc(0x113); + regmbc(0x115); regmbc(0x117); regmbc(0x119); + regmbc(0x11b); regmbc(0x205); regmbc(0x207); + regmbc(0x229); regmbc(0x247); regmbc(0x1d92); + regmbc(0x1e15); regmbc(0x1e17); regmbc(0x1e19); + regmbc(0x1e1b); regmbc(0x1e1d); regmbc(0x1eb9); + regmbc(0x1ebb); regmbc(0x1ebd); regmbc(0x1ebf); + regmbc(0x1ec1); regmbc(0x1ec3); regmbc(0x1ec5); + regmbc(0x1ec7); + return; + case 'f': case 0x192: case 0x1d6e: case 0x1d82: + case 0x1e1f: case 0xa799: + regmbc('f'); regmbc(0x192); regmbc(0x1d6e); + regmbc(0x1d82); regmbc(0x1e1f); regmbc(0xa799); + return; + case 'g': case 0x11d: case 0x11f: case 0x121: case 0x123: + case 0x1e5: case 0x1e7: case 0x260: case 0x1f5: case 0x1d83: + case 0x1e21: case 0xa7a1: + regmbc('g'); regmbc(0x11d); regmbc(0x11f); + regmbc(0x121); regmbc(0x123); regmbc(0x1e5); + regmbc(0x1e7); regmbc(0x1f5); regmbc(0x260); + regmbc(0x1d83); regmbc(0x1e21); regmbc(0xa7a1); + return; + case 'h': case 0x125: case 0x127: case 0x21f: case 0x1e23: + case 0x1e25: case 0x1e27: case 0x1e29: case 0x1e2b: + case 0x1e96: case 0x2c68: case 0xa795: + regmbc('h'); regmbc(0x125); regmbc(0x127); + regmbc(0x21f); regmbc(0x1e23); regmbc(0x1e25); + regmbc(0x1e27); regmbc(0x1e29); regmbc(0x1e2b); + regmbc(0x1e96); regmbc(0x2c68); regmbc(0xa795); + return; + case 'i': case 0xec: case 0xed: case 0xee: case 0xef: + case 0x129: case 0x12b: case 0x12d: case 0x12f: + case 0x1d0: case 0x209: case 0x20b: case 0x268: + case 0x1d96: case 0x1e2d: case 0x1e2f: case 0x1ec9: + case 0x1ecb: + regmbc('i'); regmbc(0xec); regmbc(0xed); + regmbc(0xee); regmbc(0xef); regmbc(0x129); + regmbc(0x12b); regmbc(0x12d); regmbc(0x12f); + regmbc(0x1d0); regmbc(0x209); regmbc(0x20b); + regmbc(0x268); regmbc(0x1d96); regmbc(0x1e2d); + regmbc(0x1e2f); regmbc(0x1ec9); regmbc(0x1ecb); + return; + case 'j': case 0x135: case 0x1f0: case 0x249: + regmbc('j'); regmbc(0x135); regmbc(0x1f0); + regmbc(0x249); + return; + case 'k': case 0x137: case 0x199: case 0x1e9: + case 0x1d84: case 0x1e31: case 0x1e33: case 0x1e35: + case 0x2c6a: case 0xa741: + regmbc('k'); regmbc(0x137); regmbc(0x199); + regmbc(0x1e9); regmbc(0x1d84); regmbc(0x1e31); + regmbc(0x1e33); regmbc(0x1e35); regmbc(0x2c6a); + regmbc(0xa741); + return; + case 'l': case 0x13a: case 0x13c: case 0x13e: + case 0x140: case 0x142: case 0x19a: case 0x1e37: + case 0x1e39: case 0x1e3b: case 0x1e3d: case 0x2c61: + regmbc('l'); regmbc(0x13a); regmbc(0x13c); + regmbc(0x13e); regmbc(0x140); regmbc(0x142); + regmbc(0x19a); regmbc(0x1e37); regmbc(0x1e39); + regmbc(0x1e3b); regmbc(0x1e3d); regmbc(0x2c61); + return; + case 'm': case 0x1d6f: case 0x1e3f: case 0x1e41: case 0x1e43: + regmbc('m'); regmbc(0x1d6f); regmbc(0x1e3f); + regmbc(0x1e41); regmbc(0x1e43); + return; + case 'n': case 0xf1: case 0x144: case 0x146: case 0x148: + case 0x149: case 0x1f9: case 0x1d70: case 0x1d87: + case 0x1e45: case 0x1e47: case 0x1e49: case 0x1e4b: + case 0xa7a5: + regmbc('n'); regmbc(0xf1); regmbc(0x144); + regmbc(0x146); regmbc(0x148); regmbc(0x149); + regmbc(0x1f9); regmbc(0x1d70); regmbc(0x1d87); + regmbc(0x1e45); regmbc(0x1e47); regmbc(0x1e49); + regmbc(0x1e4b); regmbc(0xa7a5); + return; + case 'o': case 0xf2: case 0xf3: case 0xf4: case 0xf5: + case 0xf6: case 0xf8: case 0x14d: case 0x14f: case 0x151: + case 0x1a1: case 0x1d2: case 0x1eb: case 0x1ed: case 0x1ff: + case 0x20d: case 0x20f: case 0x22b: case 0x22d: case 0x22f: + case 0x231: case 0x275: case 0x1e4d: case 0x1e4f: + case 0x1e51: case 0x1e53: case 0x1ecd: case 0x1ecf: + case 0x1ed1: case 0x1ed3: case 0x1ed5: case 0x1ed7: + case 0x1ed9: case 0x1edb: case 0x1edd: case 0x1edf: + case 0x1ee1: case 0x1ee3: + regmbc('o'); regmbc(0xf2); regmbc(0xf3); + regmbc(0xf4); regmbc(0xf5); regmbc(0xf6); + regmbc(0xf8); regmbc(0x14d); regmbc(0x14f); + regmbc(0x151); regmbc(0x1a1); regmbc(0x1d2); + regmbc(0x1eb); regmbc(0x1ed); regmbc(0x1ff); + regmbc(0x20d); regmbc(0x20f); regmbc(0x22b); + regmbc(0x22d); regmbc(0x22f); regmbc(0x231); + regmbc(0x275); regmbc(0x1e4d); regmbc(0x1e4f); + regmbc(0x1e51); regmbc(0x1e53); regmbc(0x1ecd); + regmbc(0x1ecf); regmbc(0x1ed1); regmbc(0x1ed3); + regmbc(0x1ed5); regmbc(0x1ed7); regmbc(0x1ed9); + regmbc(0x1edb); regmbc(0x1edd); regmbc(0x1edf); + regmbc(0x1ee1); regmbc(0x1ee3); + return; + case 'p': case 0x1a5: case 0x1d71: case 0x1d88: case 0x1d7d: + case 0x1e55: case 0x1e57: + regmbc('p'); regmbc(0x1a5); regmbc(0x1d71); + regmbc(0x1d7d); regmbc(0x1d88); regmbc(0x1e55); + regmbc(0x1e57); + return; + case 'q': case 0x24b: case 0x2a0: + regmbc('q'); regmbc(0x24b); regmbc(0x2a0); + return; + case 'r': case 0x155: case 0x157: case 0x159: case 0x211: + case 0x213: case 0x24d: case 0x27d: case 0x1d72: case 0x1d73: + case 0x1d89: case 0x1e59: case 0x1e5b: case 0x1e5d: case 0x1e5f: + case 0xa7a7: + regmbc('r'); regmbc(0x155); regmbc(0x157); + regmbc(0x159); regmbc(0x211); regmbc(0x213); + regmbc(0x24d); regmbc(0x1d72); regmbc(0x1d73); + regmbc(0x1d89); regmbc(0x1e59); regmbc(0x27d); + regmbc(0x1e5b); regmbc(0x1e5d); regmbc(0x1e5f); + regmbc(0xa7a7); + return; + case 's': case 0x15b: case 0x15d: case 0x15f: case 0x161: + case 0x1e61: case 0x219: case 0x23f: case 0x1d74: case 0x1d8a: + case 0x1e63: case 0x1e65: case 0x1e67: case 0x1e69: case 0xa7a9: + regmbc('s'); regmbc(0x15b); regmbc(0x15d); + regmbc(0x15f); regmbc(0x161); regmbc(0x23f); + regmbc(0x219); regmbc(0x1d74); regmbc(0x1d8a); + regmbc(0x1e61); regmbc(0x1e63); regmbc(0x1e65); + regmbc(0x1e67); regmbc(0x1e69); regmbc(0xa7a9); + return; + case 't': case 0x163: case 0x165: case 0x167: case 0x1ab: + case 0x1ad: case 0x21b: case 0x288: case 0x1d75: case 0x1e6b: + case 0x1e6d: case 0x1e6f: case 0x1e71: case 0x1e97: case 0x2c66: + regmbc('t'); regmbc(0x163); regmbc(0x165); + regmbc(0x167); regmbc(0x1ab); regmbc(0x21b); + regmbc(0x1ad); regmbc(0x288); regmbc(0x1d75); + regmbc(0x1e6b); regmbc(0x1e6d); regmbc(0x1e6f); + regmbc(0x1e71); regmbc(0x1e97); regmbc(0x2c66); + return; + case 'u': case 0xf9: case 0xfa: case 0xfb: case 0xfc: + case 0x169: case 0x16b: case 0x16d: case 0x16f: + case 0x171: case 0x173: case 0x1b0: case 0x1d4: + case 0x1d6: case 0x1d8: case 0x1da: case 0x1dc: + case 0x215: case 0x217: case 0x289: case 0x1e73: + case 0x1d7e: case 0x1d99: case 0x1e75: case 0x1e77: + case 0x1e79: case 0x1e7b: case 0x1ee5: case 0x1ee7: + case 0x1ee9: case 0x1eeb: case 0x1eed: case 0x1eef: + case 0x1ef1: + regmbc('u'); regmbc(0xf9); regmbc(0xfa); + regmbc(0xfb); regmbc(0xfc); regmbc(0x169); + regmbc(0x16b); regmbc(0x16d); regmbc(0x16f); + regmbc(0x171); regmbc(0x173); regmbc(0x1d6); + regmbc(0x1d8); regmbc(0x1da); regmbc(0x1dc); + regmbc(0x215); regmbc(0x217); regmbc(0x1b0); + regmbc(0x1d4); regmbc(0x289); regmbc(0x1d7e); + regmbc(0x1d99); regmbc(0x1e73); regmbc(0x1e75); + regmbc(0x1e77); regmbc(0x1e79); regmbc(0x1e7b); + regmbc(0x1ee5); regmbc(0x1ee7); regmbc(0x1ee9); + regmbc(0x1eeb); regmbc(0x1eed); regmbc(0x1eef); + regmbc(0x1ef1); + return; + case 'v': case 0x28b: case 0x1d8c: case 0x1e7d: case 0x1e7f: + regmbc('v'); regmbc(0x28b); regmbc(0x1d8c); + regmbc(0x1e7d); regmbc(0x1e7f); + return; + case 'w': case 0x175: case 0x1e81: case 0x1e83: + case 0x1e85: case 0x1e87: case 0x1e89: case 0x1e98: + regmbc('w'); regmbc(0x175); regmbc(0x1e81); + regmbc(0x1e83); regmbc(0x1e85); regmbc(0x1e87); + regmbc(0x1e89); regmbc(0x1e98); + return; + case 'x': case 0x1e8b: case 0x1e8d: + regmbc('x'); regmbc(0x1e8b); regmbc(0x1e8d); + return; + case 'y': case 0xfd: case 0xff: case 0x177: case 0x1b4: + case 0x233: case 0x24f: case 0x1e8f: case 0x1e99: case 0x1ef3: + case 0x1ef5: case 0x1ef7: case 0x1ef9: + regmbc('y'); regmbc(0xfd); regmbc(0xff); + regmbc(0x177); regmbc(0x1b4); regmbc(0x233); + regmbc(0x24f); regmbc(0x1e8f); regmbc(0x1e99); + regmbc(0x1ef3); regmbc(0x1ef5); regmbc(0x1ef7); + regmbc(0x1ef9); + return; + case 'z': case 0x17a: case 0x17c: case 0x17e: case 0x1b6: + case 0x1d76: case 0x1d8e: case 0x1e91: case 0x1e93: + case 0x1e95: case 0x2c6c: + regmbc('z'); regmbc(0x17a); regmbc(0x17c); + regmbc(0x17e); regmbc(0x1b6); regmbc(0x1d76); + regmbc(0x1d8e); regmbc(0x1e91); regmbc(0x1e93); + regmbc(0x1e95); regmbc(0x2c6c); + return; + } + } + regmbc(c); +} + + + +/* + * Emit a node. + * Return pointer to generated code. + */ +static char_u *regnode(int op) +{ + char_u *ret; + + ret = regcode; + if (ret == JUST_CALC_SIZE) + regsize += 3; + else { + *regcode++ = op; + *regcode++ = NUL; // Null "next" pointer. + *regcode++ = NUL; + } + return ret; +} + +/* + * Write a four bytes number at "p" and return pointer to the next char. + */ +static char_u *re_put_uint32(char_u *p, uint32_t val) +{ + *p++ = (char_u)((val >> 24) & 0377); + *p++ = (char_u)((val >> 16) & 0377); + *p++ = (char_u)((val >> 8) & 0377); + *p++ = (char_u)(val & 0377); + return p; +} + +/* + * regnext - dig the "next" pointer out of a node + * Returns NULL when calculating size, when there is no next item and when + * there is an error. + */ +static char_u *regnext(char_u *p) + FUNC_ATTR_NONNULL_ALL +{ + int offset; + + if (p == JUST_CALC_SIZE || reg_toolong) + return NULL; + + offset = NEXT(p); + if (offset == 0) + return NULL; + + if (OP(p) == BACK) + return p - offset; + else + return p + offset; +} + +// Set the next-pointer at the end of a node chain. +static void regtail(char_u *p, char_u *val) +{ + int offset; + + if (p == JUST_CALC_SIZE) { + return; + } + + // Find last node. + char_u *scan = p; + for (;; ) { + char_u *temp = regnext(scan); + if (temp == NULL) { + break; + } + scan = temp; + } + + if (OP(scan) == BACK) { + offset = (int)(scan - val); + } else { + offset = (int)(val - scan); + } + // When the offset uses more than 16 bits it can no longer fit in the two + // bytes available. Use a global flag to avoid having to check return + // values in too many places. + if (offset > 0xffff) { + reg_toolong = true; + } else { + *(scan + 1) = (char_u)(((unsigned)offset >> 8) & 0377); + *(scan + 2) = (char_u)(offset & 0377); + } +} + +/* + * Like regtail, on item after a BRANCH; nop if none. + */ +static void regoptail(char_u *p, char_u *val) +{ + // When op is neither BRANCH nor BRACE_COMPLEX0-9, it is "operandless" + if (p == NULL || p == JUST_CALC_SIZE + || (OP(p) != BRANCH + && (OP(p) < BRACE_COMPLEX || OP(p) > BRACE_COMPLEX + 9))) + return; + regtail(OPERAND(p), val); +} + + +/* + * Insert an operator in front of already-emitted operand + * + * Means relocating the operand. + */ +static void reginsert(int op, char_u *opnd) +{ + char_u *src; + char_u *dst; + char_u *place; + + if (regcode == JUST_CALC_SIZE) { + regsize += 3; + return; + } + src = regcode; + regcode += 3; + dst = regcode; + while (src > opnd) + *--dst = *--src; + + place = opnd; // Op node, where operand used to be. + *place++ = op; + *place++ = NUL; + *place = NUL; +} + +/* + * Insert an operator in front of already-emitted operand. + * Add a number to the operator. + */ +static void reginsert_nr(int op, long val, char_u *opnd) +{ + char_u *src; + char_u *dst; + char_u *place; + + if (regcode == JUST_CALC_SIZE) { + regsize += 7; + return; + } + src = regcode; + regcode += 7; + dst = regcode; + while (src > opnd) + *--dst = *--src; + + place = opnd; // Op node, where operand used to be. + *place++ = op; + *place++ = NUL; + *place++ = NUL; + assert(val >= 0 && (uintmax_t)val <= UINT32_MAX); + re_put_uint32(place, (uint32_t)val); +} + +/* + * Insert an operator in front of already-emitted operand. + * The operator has the given limit values as operands. Also set next pointer. + * + * Means relocating the operand. + */ +static void reginsert_limits(int op, long minval, long maxval, char_u *opnd) +{ + char_u *src; + char_u *dst; + char_u *place; + + if (regcode == JUST_CALC_SIZE) { + regsize += 11; + return; + } + src = regcode; + regcode += 11; + dst = regcode; + while (src > opnd) + *--dst = *--src; + + place = opnd; // Op node, where operand used to be. + *place++ = op; + *place++ = NUL; + *place++ = NUL; + assert(minval >= 0 && (uintmax_t)minval <= UINT32_MAX); + place = re_put_uint32(place, (uint32_t)minval); + assert(maxval >= 0 && (uintmax_t)maxval <= UINT32_MAX); + place = re_put_uint32(place, (uint32_t)maxval); + regtail(opnd, place); +} + +/// Return true if the back reference is legal. We must have seen the close +/// brace. +/// TODO(vim): Should also check that we don't refer to something repeated +/// (+*=): what instance of the repetition should we match? +static int seen_endbrace(int refnum) +{ + if (!had_endbrace[refnum]) { + char_u *p; + + // Trick: check if "@<=" or "@<!" follows, in which case + // the \1 can appear before the referenced match. + for (p = regparse; *p != NUL; p++) { + if (p[0] == '@' && p[1] == '<' && (p[2] == '!' || p[2] == '=')) { + break; + } + } + + if (*p == NUL) { + emsg(_("E65: Illegal back reference")); + rc_did_emsg = true; + return false; + } + } + return true; +} + +/* + * Parse the lowest level. + * + * Optimization: gobbles an entire sequence of ordinary characters so that + * it can turn them into a single node, which is smaller to store and + * faster to run. Don't do this when one_exactly is set. + */ +static char_u *regatom(int *flagp) +{ + char_u *ret; + int flags; + int c; + char_u *p; + int extra = 0; + int save_prev_at_start = prev_at_start; + + *flagp = WORST; // Tentatively. + + c = getchr(); + switch (c) { + case Magic('^'): + ret = regnode(BOL); + break; + + case Magic('$'): + ret = regnode(EOL); + had_eol = true; + break; + + case Magic('<'): + ret = regnode(BOW); + break; + + case Magic('>'): + ret = regnode(EOW); + break; + + case Magic('_'): + c = no_Magic(getchr()); + if (c == '^') { // "\_^" is start-of-line + ret = regnode(BOL); + break; + } + if (c == '$') { // "\_$" is end-of-line + ret = regnode(EOL); + had_eol = true; + break; + } + + extra = ADD_NL; + *flagp |= HASNL; + + // "\_[" is character range plus newline + if (c == '[') + goto collection; + + // "\_x" is character class plus newline + FALLTHROUGH; + + // Character classes. + case Magic('.'): + case Magic('i'): + case Magic('I'): + case Magic('k'): + case Magic('K'): + case Magic('f'): + case Magic('F'): + case Magic('p'): + case Magic('P'): + case Magic('s'): + case Magic('S'): + case Magic('d'): + case Magic('D'): + case Magic('x'): + case Magic('X'): + case Magic('o'): + case Magic('O'): + case Magic('w'): + case Magic('W'): + case Magic('h'): + case Magic('H'): + case Magic('a'): + case Magic('A'): + case Magic('l'): + case Magic('L'): + case Magic('u'): + case Magic('U'): + p = vim_strchr(classchars, no_Magic(c)); + if (p == NULL) + EMSG_RET_NULL(_("E63: invalid use of \\_")); + // When '.' is followed by a composing char ignore the dot, so that + // the composing char is matched here. + if (c == Magic('.') && utf_iscomposing(peekchr())) { + c = getchr(); + goto do_multibyte; + } + ret = regnode(classcodes[p - classchars] + extra); + *flagp |= HASWIDTH | SIMPLE; + break; + + case Magic('n'): + if (reg_string) { + // In a string "\n" matches a newline character. + ret = regnode(EXACTLY); + regc(NL); + regc(NUL); + *flagp |= HASWIDTH | SIMPLE; + } else { + // In buffer text "\n" matches the end of a line. + ret = regnode(NEWL); + *flagp |= HASWIDTH | HASNL; + } + break; + + case Magic('('): + if (one_exactly) + EMSG_ONE_RET_NULL; + ret = reg(REG_PAREN, &flags); + if (ret == NULL) + return NULL; + *flagp |= flags & (HASWIDTH | SPSTART | HASNL | HASLOOKBH); + break; + + case NUL: + case Magic('|'): + case Magic('&'): + case Magic(')'): + if (one_exactly) + EMSG_ONE_RET_NULL; + IEMSG_RET_NULL(_(e_internal)); // Supposed to be caught earlier. + // NOTREACHED + + case Magic('='): + case Magic('?'): + case Magic('+'): + case Magic('@'): + case Magic('{'): + case Magic('*'): + c = no_Magic(c); + EMSG3_RET_NULL(_("E64: %s%c follows nothing"), + (c == '*' ? reg_magic >= MAGIC_ON : reg_magic == MAGIC_ALL), c); + // NOTREACHED + + case Magic('~'): // previous substitute pattern + if (reg_prev_sub != NULL) { + char_u *lp; + + ret = regnode(EXACTLY); + lp = reg_prev_sub; + while (*lp != NUL) + regc(*lp++); + regc(NUL); + if (*reg_prev_sub != NUL) { + *flagp |= HASWIDTH; + if ((lp - reg_prev_sub) == 1) + *flagp |= SIMPLE; + } + } else + EMSG_RET_NULL(_(e_nopresub)); + break; + + case Magic('1'): + case Magic('2'): + case Magic('3'): + case Magic('4'): + case Magic('5'): + case Magic('6'): + case Magic('7'): + case Magic('8'): + case Magic('9'): + { + int refnum; + + refnum = c - Magic('0'); + if (!seen_endbrace(refnum)) { + return NULL; + } + ret = regnode(BACKREF + refnum); + } + break; + + case Magic('z'): + { + c = no_Magic(getchr()); + switch (c) { + case '(': if ((reg_do_extmatch & REX_SET) == 0) + EMSG_RET_NULL(_(e_z_not_allowed)); + if (one_exactly) + EMSG_ONE_RET_NULL; + ret = reg(REG_ZPAREN, &flags); + if (ret == NULL) + return NULL; + *flagp |= flags & (HASWIDTH|SPSTART|HASNL|HASLOOKBH); + re_has_z = REX_SET; + break; + + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': if ((reg_do_extmatch & REX_USE) == 0) + EMSG_RET_NULL(_(e_z1_not_allowed)); + ret = regnode(ZREF + c - '0'); + re_has_z = REX_USE; + break; + + case 's': ret = regnode(MOPEN + 0); + if (!re_mult_next("\\zs")) { + return NULL; + } + break; + + case 'e': ret = regnode(MCLOSE + 0); + if (!re_mult_next("\\ze")) { + return NULL; + } + break; + + default: EMSG_RET_NULL(_("E68: Invalid character after \\z")); + } + } + break; + + case Magic('%'): + { + c = no_Magic(getchr()); + switch (c) { + // () without a back reference + case '(': + if (one_exactly) + EMSG_ONE_RET_NULL; + ret = reg(REG_NPAREN, &flags); + if (ret == NULL) + return NULL; + *flagp |= flags & (HASWIDTH | SPSTART | HASNL | HASLOOKBH); + break; + + // Catch \%^ and \%$ regardless of where they appear in the + // pattern -- regardless of whether or not it makes sense. + case '^': + ret = regnode(RE_BOF); + break; + + case '$': + ret = regnode(RE_EOF); + break; + + case '#': + ret = regnode(CURSOR); + break; + + case 'V': + ret = regnode(RE_VISUAL); + break; + + case 'C': + ret = regnode(RE_COMPOSING); + break; + + // \%[abc]: Emit as a list of branches, all ending at the last + // branch which matches nothing. + case '[': + if (one_exactly) // doesn't nest + EMSG_ONE_RET_NULL; + { + char_u *lastbranch; + char_u *lastnode = NULL; + char_u *br; + + ret = NULL; + while ((c = getchr()) != ']') { + if (c == NUL) + EMSG2_RET_NULL(_(e_missing_sb), + reg_magic == MAGIC_ALL); + br = regnode(BRANCH); + if (ret == NULL) { + ret = br; + } else { + regtail(lastnode, br); + if (reg_toolong) { + return NULL; + } + } + + ungetchr(); + one_exactly = true; + lastnode = regatom(flagp); + one_exactly = false; + if (lastnode == NULL) { + return NULL; + } + } + if (ret == NULL) + EMSG2_RET_NULL(_(e_empty_sb), + reg_magic == MAGIC_ALL); + lastbranch = regnode(BRANCH); + br = regnode(NOTHING); + if (ret != JUST_CALC_SIZE) { + regtail(lastnode, br); + regtail(lastbranch, br); + // connect all branches to the NOTHING + // branch at the end + for (br = ret; br != lastnode; ) { + if (OP(br) == BRANCH) { + regtail(br, lastbranch); + if (reg_toolong) { + return NULL; + } + br = OPERAND(br); + } else + br = regnext(br); + } + } + *flagp &= ~(HASWIDTH | SIMPLE); + break; + } + + case 'd': // %d123 decimal + case 'o': // %o123 octal + case 'x': // %xab hex 2 + case 'u': // %uabcd hex 4 + case 'U': // %U1234abcd hex 8 + { + int64_t i; + + switch (c) { + case 'd': i = getdecchrs(); break; + case 'o': i = getoctchrs(); break; + case 'x': i = gethexchrs(2); break; + case 'u': i = gethexchrs(4); break; + case 'U': i = gethexchrs(8); break; + default: i = -1; break; + } + + if (i < 0 || i > INT_MAX) { + EMSG2_RET_NULL(_("E678: Invalid character after %s%%[dxouU]"), + reg_magic == MAGIC_ALL); + } + if (use_multibytecode(i)) { + ret = regnode(MULTIBYTECODE); + } else { + ret = regnode(EXACTLY); + } + if (i == 0) { + regc(0x0a); + } else { + regmbc(i); + } + regc(NUL); + *flagp |= HASWIDTH; + break; + } + + default: + if (ascii_isdigit(c) || c == '<' || c == '>' || c == '\'' || c == '.') { + uint32_t n = 0; + int cmp; + bool cur = false; + + cmp = c; + if (cmp == '<' || cmp == '>') { + c = getchr(); + } + if (no_Magic(c) == '.') { + cur = true; + c = getchr(); + } + while (ascii_isdigit(c)) { + n = n * 10 + (uint32_t)(c - '0'); + c = getchr(); + } + if (c == '\'' && n == 0) { + // "\%'m", "\%<'m" and "\%>'m": Mark + c = getchr(); + ret = regnode(RE_MARK); + if (ret == JUST_CALC_SIZE) + regsize += 2; + else { + *regcode++ = c; + *regcode++ = cmp; + } + break; + } else if (c == 'l' || c == 'c' || c == 'v') { + if (cur && n) { + semsg(_(e_regexp_number_after_dot_pos_search), no_Magic(c)); + rc_did_emsg = true; + return NULL; + } + if (c == 'l') { + if (cur) { + n = curwin->w_cursor.lnum; + } + ret = regnode(RE_LNUM); + if (save_prev_at_start) { + at_start = true; + } + } else if (c == 'c') { + if (cur) { + n = curwin->w_cursor.col; + n++; + } + ret = regnode(RE_COL); + } else { + if (cur) { + colnr_T vcol = 0; + getvvcol(curwin, &curwin->w_cursor, NULL, NULL, &vcol); + n = ++vcol; + } + ret = regnode(RE_VCOL); + } + if (ret == JUST_CALC_SIZE) { + regsize += 5; + } else { + // put the number and the optional + // comparator after the opcode + regcode = re_put_uint32(regcode, n); + *regcode++ = cmp; + } + break; + } + } + + EMSG2_RET_NULL(_("E71: Invalid character after %s%%"), + reg_magic == MAGIC_ALL); + } + } + break; + + case Magic('['): +collection: + { + char_u *lp; + + // If there is no matching ']', we assume the '[' is a normal + // character. This makes 'incsearch' and ":help [" work. + lp = skip_anyof(regparse); + if (*lp == ']') { // there is a matching ']' + int startc = -1; // > 0 when next '-' is a range + int endc; + + // In a character class, different parsing rules apply. + // Not even \ is special anymore, nothing is. + if (*regparse == '^') { // Complement of range. + ret = regnode(ANYBUT + extra); + regparse++; + } else + ret = regnode(ANYOF + extra); + + // At the start ']' and '-' mean the literal character. + if (*regparse == ']' || *regparse == '-') { + startc = *regparse; + regc(*regparse++); + } + + while (*regparse != NUL && *regparse != ']') { + if (*regparse == '-') { + ++regparse; + // The '-' is not used for a range at the end and + // after or before a '\n'. + if (*regparse == ']' || *regparse == NUL + || startc == -1 + || (regparse[0] == '\\' && regparse[1] == 'n')) { + regc('-'); + startc = '-'; // [--x] is a range + } else { + // Also accept "a-[.z.]" + endc = 0; + if (*regparse == '[') + endc = get_coll_element(®parse); + if (endc == 0) { + endc = mb_ptr2char_adv((const char_u **)®parse); + } + + // Handle \o40, \x20 and \u20AC style sequences + if (endc == '\\' && !reg_cpo_lit) + endc = coll_get_char(); + + if (startc > endc) { + EMSG_RET_NULL(_(e_reverse_range)); + } + if (utf_char2len(startc) > 1 + || utf_char2len(endc) > 1) { + // Limit to a range of 256 chars + if (endc > startc + 256) { + EMSG_RET_NULL(_(e_large_class)); + } + while (++startc <= endc) { + regmbc(startc); + } + } else { + while (++startc <= endc) + regc(startc); + } + startc = -1; + } + } + // Only "\]", "\^", "\]" and "\\" are special in Vi. Vim + // accepts "\t", "\e", etc., but only when the 'l' flag in + // 'cpoptions' is not included. + else if (*regparse == '\\' + && (vim_strchr(REGEXP_INRANGE, regparse[1]) != NULL + || (!reg_cpo_lit + && vim_strchr(REGEXP_ABBR, + regparse[1]) != NULL))) { + regparse++; + if (*regparse == 'n') { + // '\n' in range: also match NL + if (ret != JUST_CALC_SIZE) { + // Using \n inside [^] does not change what + // matches. "[^\n]" is the same as ".". + if (*ret == ANYOF) { + *ret = ANYOF + ADD_NL; + *flagp |= HASNL; + } + // else: must have had a \n already + } + regparse++; + startc = -1; + } else if (*regparse == 'd' + || *regparse == 'o' + || *regparse == 'x' + || *regparse == 'u' + || *regparse == 'U') { + startc = coll_get_char(); + if (startc == 0) + regc(0x0a); + else + regmbc(startc); + } else { + startc = backslash_trans(*regparse++); + regc(startc); + } + } else if (*regparse == '[') { + int c_class; + int cu; + + c_class = get_char_class(®parse); + startc = -1; + // Characters assumed to be 8 bits! + switch (c_class) { + case CLASS_NONE: + c_class = get_equi_class(®parse); + if (c_class != 0) { + // produce equivalence class + reg_equi_class(c_class); + } else if ((c_class = + get_coll_element(®parse)) != 0) { + // produce a collating element + regmbc(c_class); + } else { + // literal '[', allow [[-x] as a range + startc = *regparse++; + regc(startc); + } + break; + case CLASS_ALNUM: + for (cu = 1; cu < 128; cu++) { + if (isalnum(cu)) { + regmbc(cu); + } + } + break; + case CLASS_ALPHA: + for (cu = 1; cu < 128; cu++) { + if (isalpha(cu)) { + regmbc(cu); + } + } + break; + case CLASS_BLANK: + regc(' '); + regc('\t'); + break; + case CLASS_CNTRL: + for (cu = 1; cu <= 127; cu++) { + if (iscntrl(cu)) { + regmbc(cu); + } + } + break; + case CLASS_DIGIT: + for (cu = 1; cu <= 127; cu++) { + if (ascii_isdigit(cu)) { + regmbc(cu); + } + } + break; + case CLASS_GRAPH: + for (cu = 1; cu <= 127; cu++) { + if (isgraph(cu)) { + regmbc(cu); + } + } + break; + case CLASS_LOWER: + for (cu = 1; cu <= 255; cu++) { + if (mb_islower(cu) && cu != 170 && cu != 186) { + regmbc(cu); + } + } + break; + case CLASS_PRINT: + for (cu = 1; cu <= 255; cu++) { + if (vim_isprintc(cu)) { + regmbc(cu); + } + } + break; + case CLASS_PUNCT: + for (cu = 1; cu < 128; cu++) { + if (ispunct(cu)) { + regmbc(cu); + } + } + break; + case CLASS_SPACE: + for (cu = 9; cu <= 13; cu++) + regc(cu); + regc(' '); + break; + case CLASS_UPPER: + for (cu = 1; cu <= 255; cu++) { + if (mb_isupper(cu)) { + regmbc(cu); + } + } + break; + case CLASS_XDIGIT: + for (cu = 1; cu <= 255; cu++) { + if (ascii_isxdigit(cu)) { + regmbc(cu); + } + } + break; + case CLASS_TAB: + regc('\t'); + break; + case CLASS_RETURN: + regc('\r'); + break; + case CLASS_BACKSPACE: + regc('\b'); + break; + case CLASS_ESCAPE: + regc(ESC); + break; + case CLASS_IDENT: + for (cu = 1; cu <= 255; cu++) { + if (vim_isIDc(cu)) { + regmbc(cu); + } + } + break; + case CLASS_KEYWORD: + for (cu = 1; cu <= 255; cu++) { + if (reg_iswordc(cu)) { + regmbc(cu); + } + } + break; + case CLASS_FNAME: + for (cu = 1; cu <= 255; cu++) { + if (vim_isfilec(cu)) { + regmbc(cu); + } + } + break; + } + } else { + // produce a multibyte character, including any + // following composing characters. + startc = utf_ptr2char(regparse); + int len = utfc_ptr2len(regparse); + if (utf_char2len(startc) != len) { + // composing chars + startc = -1; + } + while (--len >= 0) { + regc(*regparse++); + } + } + } + regc(NUL); + prevchr_len = 1; // last char was the ']' + if (*regparse != ']') + EMSG_RET_NULL(_(e_toomsbra)); // Cannot happen? + skipchr(); // let's be friends with the lexer again + *flagp |= HASWIDTH | SIMPLE; + break; + } else if (reg_strict) + EMSG2_RET_NULL(_(e_missingbracket), reg_magic > MAGIC_OFF); + } + FALLTHROUGH; + + default: + { + int len; + + // A multi-byte character is handled as a separate atom if it's + // before a multi and when it's a composing char. + if (use_multibytecode(c)) { +do_multibyte: + ret = regnode(MULTIBYTECODE); + regmbc(c); + *flagp |= HASWIDTH | SIMPLE; + break; + } + + ret = regnode(EXACTLY); + + // Append characters as long as: + // - there is no following multi, we then need the character in + // front of it as a single character operand + // - not running into a Magic character + // - "one_exactly" is not set + // But always emit at least one character. Might be a Multi, + // e.g., a "[" without matching "]". + for (len = 0; c != NUL && (len == 0 + || (re_multi_type(peekchr()) == NOT_MULTI + && !one_exactly + && !is_Magic(c))); ++len) { + c = no_Magic(c); + { + regmbc(c); + { + int l; + + // Need to get composing character too. + for (;; ) { + l = utf_ptr2len(regparse); + if (!utf_composinglike(regparse, regparse + l)) { + break; + } + regmbc(utf_ptr2char(regparse)); + skipchr(); + } + } + } + c = getchr(); + } + ungetchr(); + + regc(NUL); + *flagp |= HASWIDTH; + if (len == 1) + *flagp |= SIMPLE; + } + break; + } + + return ret; +} + +/* + * Parse something followed by possible [*+=]. + * + * Note that the branching code sequences used for = and the general cases + * of * and + are somewhat optimized: they use the same NOTHING node as + * both the endmarker for their branch list and the body of the last branch. + * It might seem that this node could be dispensed with entirely, but the + * endmarker role is not redundant. + */ +static char_u *regpiece(int *flagp) +{ + char_u *ret; + int op; + char_u *next; + int flags; + long minval; + long maxval; + + ret = regatom(&flags); + if (ret == NULL) + return NULL; + + op = peekchr(); + if (re_multi_type(op) == NOT_MULTI) { + *flagp = flags; + return ret; + } + // default flags + *flagp = (WORST | SPSTART | (flags & (HASNL | HASLOOKBH))); + + skipchr(); + switch (op) { + case Magic('*'): + if (flags & SIMPLE) + reginsert(STAR, ret); + else { + // Emit x* as (x&|), where & means "self". + reginsert(BRANCH, ret); // Either x + regoptail(ret, regnode(BACK)); // and loop + regoptail(ret, ret); // back + regtail(ret, regnode(BRANCH)); // or + regtail(ret, regnode(NOTHING)); // null. + } + break; + + case Magic('+'): + if (flags & SIMPLE) + reginsert(PLUS, ret); + else { + // Emit x+ as x(&|), where & means "self". + next = regnode(BRANCH); // Either + regtail(ret, next); + regtail(regnode(BACK), ret); // loop back + regtail(next, regnode(BRANCH)); // or + regtail(ret, regnode(NOTHING)); // null. + } + *flagp = (WORST | HASWIDTH | (flags & (HASNL | HASLOOKBH))); + break; + + case Magic('@'): + { + int lop = END; + int64_t nr = getdecchrs(); + + switch (no_Magic(getchr())) { + case '=': lop = MATCH; break; // \@= + case '!': lop = NOMATCH; break; // \@! + case '>': lop = SUBPAT; break; // \@> + case '<': switch (no_Magic(getchr())) { + case '=': lop = BEHIND; break; // \@<= + case '!': lop = NOBEHIND; break; // \@<! + } + } + if (lop == END) + EMSG2_RET_NULL(_("E59: invalid character after %s@"), + reg_magic == MAGIC_ALL); + // Look behind must match with behind_pos. + if (lop == BEHIND || lop == NOBEHIND) { + regtail(ret, regnode(BHPOS)); + *flagp |= HASLOOKBH; + } + regtail(ret, regnode(END)); // operand ends + if (lop == BEHIND || lop == NOBEHIND) { + if (nr < 0) + nr = 0; // no limit is same as zero limit + reginsert_nr(lop, (uint32_t)nr, ret); + } else + reginsert(lop, ret); + break; + } + + case Magic('?'): + case Magic('='): + // Emit x= as (x|) + reginsert(BRANCH, ret); // Either x + regtail(ret, regnode(BRANCH)); // or + next = regnode(NOTHING); // null. + regtail(ret, next); + regoptail(ret, next); + break; + + case Magic('{'): + if (!read_limits(&minval, &maxval)) + return NULL; + if (flags & SIMPLE) { + reginsert(BRACE_SIMPLE, ret); + reginsert_limits(BRACE_LIMITS, minval, maxval, ret); + } else { + if (num_complex_braces >= 10) + EMSG2_RET_NULL(_("E60: Too many complex %s{...}s"), + reg_magic == MAGIC_ALL); + reginsert(BRACE_COMPLEX + num_complex_braces, ret); + regoptail(ret, regnode(BACK)); + regoptail(ret, ret); + reginsert_limits(BRACE_LIMITS, minval, maxval, ret); + ++num_complex_braces; + } + if (minval > 0 && maxval > 0) + *flagp = (HASWIDTH | (flags & (HASNL | HASLOOKBH))); + break; + } + if (re_multi_type(peekchr()) != NOT_MULTI) { + // Can't have a multi follow a multi. + if (peekchr() == Magic('*')) { + EMSG2_RET_NULL(_("E61: Nested %s*"), reg_magic >= MAGIC_ON); + } + EMSG3_RET_NULL(_("E62: Nested %s%c"), reg_magic == MAGIC_ALL, no_Magic(peekchr())); + } + + return ret; +} + +/* + * Parse one alternative of an | or & operator. + * Implements the concatenation operator. + */ +static char_u *regconcat(int *flagp) +{ + char_u *first = NULL; + char_u *chain = NULL; + char_u *latest; + int flags; + int cont = true; + + *flagp = WORST; // Tentatively. + + while (cont) { + switch (peekchr()) { + case NUL: + case Magic('|'): + case Magic('&'): + case Magic(')'): + cont = false; + break; + case Magic('Z'): + regflags |= RF_ICOMBINE; + skipchr_keepstart(); + break; + case Magic('c'): + regflags |= RF_ICASE; + skipchr_keepstart(); + break; + case Magic('C'): + regflags |= RF_NOICASE; + skipchr_keepstart(); + break; + case Magic('v'): + reg_magic = MAGIC_ALL; + skipchr_keepstart(); + curchr = -1; + break; + case Magic('m'): + reg_magic = MAGIC_ON; + skipchr_keepstart(); + curchr = -1; + break; + case Magic('M'): + reg_magic = MAGIC_OFF; + skipchr_keepstart(); + curchr = -1; + break; + case Magic('V'): + reg_magic = MAGIC_NONE; + skipchr_keepstart(); + curchr = -1; + break; + default: + latest = regpiece(&flags); + if (latest == NULL || reg_toolong) + return NULL; + *flagp |= flags & (HASWIDTH | HASNL | HASLOOKBH); + if (chain == NULL) // First piece. + *flagp |= flags & SPSTART; + else + regtail(chain, latest); + chain = latest; + if (first == NULL) + first = latest; + break; + } + } + if (first == NULL) // Loop ran zero times. + first = regnode(NOTHING); + return first; +} + +/* + * Parse one alternative of an | operator. + * Implements the & operator. + */ +static char_u *regbranch(int *flagp) +{ + char_u *ret; + char_u *chain = NULL; + char_u *latest; + int flags; + + *flagp = WORST | HASNL; // Tentatively. + + ret = regnode(BRANCH); + for (;; ) { + latest = regconcat(&flags); + if (latest == NULL) + return NULL; + // If one of the branches has width, the whole thing has. If one of + // the branches anchors at start-of-line, the whole thing does. + // If one of the branches uses look-behind, the whole thing does. + *flagp |= flags & (HASWIDTH | SPSTART | HASLOOKBH); + // If one of the branches doesn't match a line-break, the whole thing + // doesn't. + *flagp &= ~HASNL | (flags & HASNL); + if (chain != NULL) + regtail(chain, latest); + if (peekchr() != Magic('&')) + break; + skipchr(); + regtail(latest, regnode(END)); // operand ends + if (reg_toolong) + break; + reginsert(MATCH, latest); + chain = latest; + } + + return ret; +} + +/* + * Parse regular expression, i.e. main body or parenthesized thing. + * + * Caller must absorb opening parenthesis. + * + * Combining parenthesis handling with the base level of regular expression + * is a trifle forced, but the need to tie the tails of the branches to what + * follows makes it hard to avoid. + */ +static char_u *reg( + int paren, // REG_NOPAREN, REG_PAREN, REG_NPAREN or REG_ZPAREN + int *flagp) +{ + char_u *ret; + char_u *br; + char_u *ender; + int parno = 0; + int flags; + + *flagp = HASWIDTH; // Tentatively. + + if (paren == REG_ZPAREN) { + // Make a ZOPEN node. + if (regnzpar >= NSUBEXP) + EMSG_RET_NULL(_("E50: Too many \\z(")); + parno = regnzpar; + regnzpar++; + ret = regnode(ZOPEN + parno); + } else if (paren == REG_PAREN) { + // Make a MOPEN node. + if (regnpar >= NSUBEXP) + EMSG2_RET_NULL(_("E51: Too many %s("), reg_magic == MAGIC_ALL); + parno = regnpar; + ++regnpar; + ret = regnode(MOPEN + parno); + } else if (paren == REG_NPAREN) { + // Make a NOPEN node. + ret = regnode(NOPEN); + } else + ret = NULL; + + // Pick up the branches, linking them together. + br = regbranch(&flags); + if (br == NULL) + return NULL; + if (ret != NULL) + regtail(ret, br); // [MZ]OPEN -> first. + else + ret = br; + // If one of the branches can be zero-width, the whole thing can. + // If one of the branches has * at start or matches a line-break, the + // whole thing can. + if (!(flags & HASWIDTH)) + *flagp &= ~HASWIDTH; + *flagp |= flags & (SPSTART | HASNL | HASLOOKBH); + while (peekchr() == Magic('|')) { + skipchr(); + br = regbranch(&flags); + if (br == NULL || reg_toolong) + return NULL; + regtail(ret, br); // BRANCH -> BRANCH. + if (!(flags & HASWIDTH)) + *flagp &= ~HASWIDTH; + *flagp |= flags & (SPSTART | HASNL | HASLOOKBH); + } + + // Make a closing node, and hook it on the end. + ender = regnode( + paren == REG_ZPAREN ? ZCLOSE + parno : + paren == REG_PAREN ? MCLOSE + parno : + paren == REG_NPAREN ? NCLOSE : END); + regtail(ret, ender); + + // Hook the tails of the branches to the closing node. + for (br = ret; br != NULL; br = regnext(br)) + regoptail(br, ender); + + // Check for proper termination. + if (paren != REG_NOPAREN && getchr() != Magic(')')) { + if (paren == REG_ZPAREN) + EMSG_RET_NULL(_("E52: Unmatched \\z(")); + else if (paren == REG_NPAREN) + EMSG2_RET_NULL(_(e_unmatchedpp), reg_magic == MAGIC_ALL); + else + EMSG2_RET_NULL(_(e_unmatchedp), reg_magic == MAGIC_ALL); + } else if (paren == REG_NOPAREN && peekchr() != NUL) { + if (curchr == Magic(')')) + EMSG2_RET_NULL(_(e_unmatchedpar), reg_magic == MAGIC_ALL); + else + EMSG_RET_NULL(_(e_trailing)); // "Can't happen". + // NOTREACHED + } + // Here we set the flag allowing back references to this set of + // parentheses. + if (paren == REG_PAREN) { + had_endbrace[parno] = true; // have seen the close paren + } + return ret; +} + + +/* + * bt_regcomp() - compile a regular expression into internal code for the + * traditional back track matcher. + * Returns the program in allocated space. Returns NULL for an error. + * + * We can't allocate space until we know how big the compiled form will be, + * but we can't compile it (and thus know how big it is) until we've got a + * place to put the code. So we cheat: we compile it twice, once with code + * generation turned off and size counting turned on, and once "for real". + * This also means that we don't allocate space until we are sure that the + * thing really will compile successfully, and we never have to move the + * code and thus invalidate pointers into it. (Note that it has to be in + * one piece because free() must be able to free it all.) + * + * Whether upper/lower case is to be ignored is decided when executing the + * program, it does not matter here. + * + * Beware that the optimization-preparation code in here knows about some + * of the structure of the compiled regexp. + * "re_flags": RE_MAGIC and/or RE_STRING. + */ +static regprog_T *bt_regcomp(char_u *expr, int re_flags) +{ + char_u *scan; + char_u *longest; + int len; + int flags; + + if (expr == NULL) { + IEMSG_RET_NULL(_(e_null)); + } + + init_class_tab(); + + // First pass: determine size, legality. + regcomp_start(expr, re_flags); + regcode = JUST_CALC_SIZE; + regc(REGMAGIC); + if (reg(REG_NOPAREN, &flags) == NULL) + return NULL; + + // Allocate space. + bt_regprog_T *r = xmalloc(sizeof(bt_regprog_T) + regsize); + r->re_in_use = false; + + // Second pass: emit code. + regcomp_start(expr, re_flags); + regcode = r->program; + regc(REGMAGIC); + if (reg(REG_NOPAREN, &flags) == NULL || reg_toolong) { + xfree(r); + if (reg_toolong) + EMSG_RET_NULL(_("E339: Pattern too long")); + return NULL; + } + + // Dig out information for optimizations. + r->regstart = NUL; // Worst-case defaults. + r->reganch = 0; + r->regmust = NULL; + r->regmlen = 0; + r->regflags = regflags; + if (flags & HASNL) + r->regflags |= RF_HASNL; + if (flags & HASLOOKBH) + r->regflags |= RF_LOOKBH; + // Remember whether this pattern has any \z specials in it. + r->reghasz = re_has_z; + scan = r->program + 1; // First BRANCH. + if (OP(regnext(scan)) == END) { // Only one top-level choice. + scan = OPERAND(scan); + + // Starting-point info. + if (OP(scan) == BOL || OP(scan) == RE_BOF) { + r->reganch++; + scan = regnext(scan); + } + + if (OP(scan) == EXACTLY) { + r->regstart = utf_ptr2char(OPERAND(scan)); + } else if (OP(scan) == BOW + || OP(scan) == EOW + || OP(scan) == NOTHING + || OP(scan) == MOPEN + 0 || OP(scan) == NOPEN + || OP(scan) == MCLOSE + 0 || OP(scan) == NCLOSE) { + char_u *regnext_scan = regnext(scan); + if (OP(regnext_scan) == EXACTLY) { + r->regstart = utf_ptr2char(OPERAND(regnext_scan)); + } + } + + // If there's something expensive in the r.e., find the longest + // literal string that must appear and make it the regmust. Resolve + // ties in favor of later strings, since the regstart check works + // with the beginning of the r.e. and avoiding duplication + // strengthens checking. Not a strong reason, but sufficient in the + // absence of others. + + // When the r.e. starts with BOW, it is faster to look for a regmust + // first. Used a lot for "#" and "*" commands. (Added by mool). + if ((flags & SPSTART || OP(scan) == BOW || OP(scan) == EOW) + && !(flags & HASNL)) { + longest = NULL; + len = 0; + for (; scan != NULL; scan = regnext(scan)) + if (OP(scan) == EXACTLY && STRLEN(OPERAND(scan)) >= (size_t)len) { + longest = OPERAND(scan); + len = (int)STRLEN(OPERAND(scan)); + } + r->regmust = longest; + r->regmlen = len; + } + } +#ifdef BT_REGEXP_DUMP + regdump(expr, r); +#endif + r->engine = &bt_regengine; + return (regprog_T *)r; +} + +/* + * Check if during the previous call to vim_regcomp the EOL item "$" has been + * found. This is messy, but it works fine. + */ +int vim_regcomp_had_eol(void) +{ + return had_eol; +} + +/* + * Get a number after a backslash that is inside []. + * When nothing is recognized return a backslash. + */ +static int coll_get_char(void) +{ + int64_t nr = -1; + + switch (*regparse++) { + case 'd': nr = getdecchrs(); break; + case 'o': nr = getoctchrs(); break; + case 'x': nr = gethexchrs(2); break; + case 'u': nr = gethexchrs(4); break; + case 'U': nr = gethexchrs(8); break; + } + if (nr < 0 || nr > INT_MAX) { + // If getting the number fails be backwards compatible: the character + // is a backslash. + regparse--; + nr = '\\'; + } + return nr; +} + +/* + * Free a compiled regexp program, returned by bt_regcomp(). + */ +static void bt_regfree(regprog_T *prog) +{ + xfree(prog); +} + +#define ADVANCE_REGINPUT() MB_PTR_ADV(rex.input) + +/* + * The arguments from BRACE_LIMITS are stored here. They are actually local + * to regmatch(), but they are here to reduce the amount of stack space used + * (it can be called recursively many times). + */ +static long bl_minval; +static long bl_maxval; + +// Save the input line and position in a regsave_T. +static void reg_save(regsave_T *save, garray_T *gap) + FUNC_ATTR_NONNULL_ALL +{ + if (REG_MULTI) { + save->rs_u.pos.col = (colnr_T)(rex.input - rex.line); + save->rs_u.pos.lnum = rex.lnum; + } else { + save->rs_u.ptr = rex.input; + } + save->rs_len = gap->ga_len; +} + +// Restore the input line and position from a regsave_T. +static void reg_restore(regsave_T *save, garray_T *gap) + FUNC_ATTR_NONNULL_ALL +{ + if (REG_MULTI) { + if (rex.lnum != save->rs_u.pos.lnum) { + // only call reg_getline() when the line number changed to save + // a bit of time + rex.lnum = save->rs_u.pos.lnum; + rex.line = reg_getline(rex.lnum); + } + rex.input = rex.line + save->rs_u.pos.col; + } else { + rex.input = save->rs_u.ptr; + } + gap->ga_len = save->rs_len; +} + +// Return true if current position is equal to saved position. +static bool reg_save_equal(const regsave_T *save) + FUNC_ATTR_NONNULL_ALL +{ + if (REG_MULTI) { + return rex.lnum == save->rs_u.pos.lnum + && rex.input == rex.line + save->rs_u.pos.col; + } + return rex.input == save->rs_u.ptr; +} + +// Save the sub-expressions before attempting a match. +#define save_se(savep, posp, pp) \ + REG_MULTI ? save_se_multi((savep), (posp)) : save_se_one((savep), (pp)) + +// After a failed match restore the sub-expressions. +#define restore_se(savep, posp, pp) { \ + if (REG_MULTI) \ + *(posp) = (savep)->se_u.pos; \ + else \ + *(pp) = (savep)->se_u.ptr; } + +/* + * Tentatively set the sub-expression start to the current position (after + * calling regmatch() they will have changed). Need to save the existing + * values for when there is no match. + * Use se_save() to use pointer (save_se_multi()) or position (save_se_one()), + * depending on REG_MULTI. + */ +static void save_se_multi(save_se_T *savep, lpos_T *posp) +{ + savep->se_u.pos = *posp; + posp->lnum = rex.lnum; + posp->col = (colnr_T)(rex.input - rex.line); +} + +static void save_se_one(save_se_T *savep, char_u **pp) +{ + savep->se_u.ptr = *pp; + *pp = rex.input; +} + +/* + * regrepeat - repeatedly match something simple, return how many. + * Advances rex.input (and rex.lnum) to just after the matched chars. + */ + static int +regrepeat( + char_u *p, + long maxcount) // maximum number of matches allowed +{ + long count = 0; + char_u *opnd; + int mask; + int testval = 0; + + char_u *scan = rex.input; // Make local copy of rex.input for speed. + opnd = OPERAND(p); + switch (OP(p)) { + case ANY: + case ANY + ADD_NL: + while (count < maxcount) { + // Matching anything means we continue until end-of-line (or + // end-of-file for ANY + ADD_NL), only limited by maxcount. + while (*scan != NUL && count < maxcount) { + count++; + MB_PTR_ADV(scan); + } + if (!REG_MULTI || !WITH_NL(OP(p)) || rex.lnum > rex.reg_maxline + || rex.reg_line_lbr || count == maxcount) { + break; + } + count++; // count the line-break + reg_nextline(); + scan = rex.input; + if (got_int) { + break; + } + } + break; + + case IDENT: + case IDENT + ADD_NL: + testval = 1; + FALLTHROUGH; + case SIDENT: + case SIDENT + ADD_NL: + while (count < maxcount) { + if (vim_isIDc(utf_ptr2char(scan)) && (testval || !ascii_isdigit(*scan))) { + MB_PTR_ADV(scan); + } else if (*scan == NUL) { + if (!REG_MULTI || !WITH_NL(OP(p)) || rex.lnum > rex.reg_maxline + || rex.reg_line_lbr) { + break; + } + reg_nextline(); + scan = rex.input; + if (got_int) { + break; + } + } else if (rex.reg_line_lbr && *scan == '\n' && WITH_NL(OP(p))) { + scan++; + } else { + break; + } + ++count; + } + break; + + case KWORD: + case KWORD + ADD_NL: + testval = 1; + FALLTHROUGH; + case SKWORD: + case SKWORD + ADD_NL: + while (count < maxcount) { + if (vim_iswordp_buf(scan, rex.reg_buf) + && (testval || !ascii_isdigit(*scan))) { + MB_PTR_ADV(scan); + } else if (*scan == NUL) { + if (!REG_MULTI || !WITH_NL(OP(p)) || rex.lnum > rex.reg_maxline + || rex.reg_line_lbr) { + break; + } + reg_nextline(); + scan = rex.input; + if (got_int) { + break; + } + } else if (rex.reg_line_lbr && *scan == '\n' && WITH_NL(OP(p))) { + scan++; + } else { + break; + } + count++; + } + break; + + case FNAME: + case FNAME + ADD_NL: + testval = 1; + FALLTHROUGH; + case SFNAME: + case SFNAME + ADD_NL: + while (count < maxcount) { + if (vim_isfilec(utf_ptr2char(scan)) && (testval || !ascii_isdigit(*scan))) { + MB_PTR_ADV(scan); + } else if (*scan == NUL) { + if (!REG_MULTI || !WITH_NL(OP(p)) || rex.lnum > rex.reg_maxline + || rex.reg_line_lbr) { + break; + } + reg_nextline(); + scan = rex.input; + if (got_int) { + break; + } + } else if (rex.reg_line_lbr && *scan == '\n' && WITH_NL(OP(p))) { + scan++; + } else { + break; + } + count++; + } + break; + + case PRINT: + case PRINT + ADD_NL: + testval = 1; + FALLTHROUGH; + case SPRINT: + case SPRINT + ADD_NL: + while (count < maxcount) { + if (*scan == NUL) { + if (!REG_MULTI || !WITH_NL(OP(p)) || rex.lnum > rex.reg_maxline + || rex.reg_line_lbr) { + break; + } + reg_nextline(); + scan = rex.input; + if (got_int) { + break; + } + } else if (vim_isprintc(utf_ptr2char(scan)) == 1 + && (testval || !ascii_isdigit(*scan))) { + MB_PTR_ADV(scan); + } else if (rex.reg_line_lbr && *scan == '\n' && WITH_NL(OP(p))) { + scan++; + } else { + break; + } + count++; + } + break; + + case WHITE: + case WHITE + ADD_NL: + testval = mask = RI_WHITE; +do_class: + while (count < maxcount) { + int l; + if (*scan == NUL) { + if (!REG_MULTI || !WITH_NL(OP(p)) || rex.lnum > rex.reg_maxline + || rex.reg_line_lbr) { + break; + } + reg_nextline(); + scan = rex.input; + if (got_int) { + break; + } + } else if ((l = utfc_ptr2len(scan)) > 1) { + if (testval != 0) { + break; + } + scan += l; + } else if ((class_tab[*scan] & mask) == testval) { + scan++; + } else if (rex.reg_line_lbr && *scan == '\n' && WITH_NL(OP(p))) { + scan++; + } else { + break; + } + ++count; + } + break; + + case NWHITE: + case NWHITE + ADD_NL: + mask = RI_WHITE; + goto do_class; + case DIGIT: + case DIGIT + ADD_NL: + testval = mask = RI_DIGIT; + goto do_class; + case NDIGIT: + case NDIGIT + ADD_NL: + mask = RI_DIGIT; + goto do_class; + case HEX: + case HEX + ADD_NL: + testval = mask = RI_HEX; + goto do_class; + case NHEX: + case NHEX + ADD_NL: + mask = RI_HEX; + goto do_class; + case OCTAL: + case OCTAL + ADD_NL: + testval = mask = RI_OCTAL; + goto do_class; + case NOCTAL: + case NOCTAL + ADD_NL: + mask = RI_OCTAL; + goto do_class; + case WORD: + case WORD + ADD_NL: + testval = mask = RI_WORD; + goto do_class; + case NWORD: + case NWORD + ADD_NL: + mask = RI_WORD; + goto do_class; + case HEAD: + case HEAD + ADD_NL: + testval = mask = RI_HEAD; + goto do_class; + case NHEAD: + case NHEAD + ADD_NL: + mask = RI_HEAD; + goto do_class; + case ALPHA: + case ALPHA + ADD_NL: + testval = mask = RI_ALPHA; + goto do_class; + case NALPHA: + case NALPHA + ADD_NL: + mask = RI_ALPHA; + goto do_class; + case LOWER: + case LOWER + ADD_NL: + testval = mask = RI_LOWER; + goto do_class; + case NLOWER: + case NLOWER + ADD_NL: + mask = RI_LOWER; + goto do_class; + case UPPER: + case UPPER + ADD_NL: + testval = mask = RI_UPPER; + goto do_class; + case NUPPER: + case NUPPER + ADD_NL: + mask = RI_UPPER; + goto do_class; + + case EXACTLY: + { + int cu, cl; + + // This doesn't do a multi-byte character, because a MULTIBYTECODE + // would have been used for it. It does handle single-byte + // characters, such as latin1. + if (rex.reg_ic) { + cu = mb_toupper(*opnd); + cl = mb_tolower(*opnd); + while (count < maxcount && (*scan == cu || *scan == cl)) { + count++; + scan++; + } + } else { + cu = *opnd; + while (count < maxcount && *scan == cu) { + count++; + scan++; + } + } + break; + } + + case MULTIBYTECODE: + { + int i, len, cf = 0; + + // Safety check (just in case 'encoding' was changed since + // compiling the program). + if ((len = utfc_ptr2len(opnd)) > 1) { + if (rex.reg_ic) { + cf = utf_fold(utf_ptr2char(opnd)); + } + while (count < maxcount && utfc_ptr2len(scan) >= len) { + for (i = 0; i < len; i++) { + if (opnd[i] != scan[i]) { + break; + } + } + if (i < len && (!rex.reg_ic + || utf_fold(utf_ptr2char(scan)) != cf)) { + break; + } + scan += len; + ++count; + } + } + } + break; + + case ANYOF: + case ANYOF + ADD_NL: + testval = 1; + FALLTHROUGH; + + case ANYBUT: + case ANYBUT + ADD_NL: + while (count < maxcount) { + int len; + if (*scan == NUL) { + if (!REG_MULTI || !WITH_NL(OP(p)) || rex.lnum > rex.reg_maxline + || rex.reg_line_lbr) { + break; + } + reg_nextline(); + scan = rex.input; + if (got_int) { + break; + } + } else if (rex.reg_line_lbr && *scan == '\n' && WITH_NL(OP(p))) { + scan++; + } else if ((len = utfc_ptr2len(scan)) > 1) { + if ((cstrchr(opnd, utf_ptr2char(scan)) == NULL) == testval) { + break; + } + scan += len; + } else { + if ((cstrchr(opnd, *scan) == NULL) == testval) + break; + ++scan; + } + ++count; + } + break; + + case NEWL: + while (count < maxcount + && ((*scan == NUL && rex.lnum <= rex.reg_maxline && !rex.reg_line_lbr + && REG_MULTI) || (*scan == '\n' && rex.reg_line_lbr))) { + count++; + if (rex.reg_line_lbr) { + ADVANCE_REGINPUT(); + } else { + reg_nextline(); + } + scan = rex.input; + if (got_int) { + break; + } + } + break; + + default: // Oh dear. Called inappropriately. + iemsg(_(e_re_corr)); +#ifdef REGEXP_DEBUG + printf("Called regrepeat with op code %d\n", OP(p)); +#endif + break; + } + + rex.input = scan; + + return (int)count; +} + +/* + * Push an item onto the regstack. + * Returns pointer to new item. Returns NULL when out of memory. + */ +static regitem_T *regstack_push(regstate_T state, char_u *scan) +{ + regitem_T *rp; + + if ((long)((unsigned)regstack.ga_len >> 10) >= p_mmp) { + emsg(_(e_maxmempat)); + return NULL; + } + ga_grow(®stack, sizeof(regitem_T)); + + rp = (regitem_T *)((char *)regstack.ga_data + regstack.ga_len); + rp->rs_state = state; + rp->rs_scan = scan; + + regstack.ga_len += sizeof(regitem_T); + return rp; +} + +/* + * Pop an item from the regstack. + */ +static void regstack_pop(char_u **scan) +{ + regitem_T *rp; + + rp = (regitem_T *)((char *)regstack.ga_data + regstack.ga_len) - 1; + *scan = rp->rs_scan; + + regstack.ga_len -= sizeof(regitem_T); +} + +// Save the current subexpr to "bp", so that they can be restored +// later by restore_subexpr(). +static void save_subexpr(regbehind_T *bp) + FUNC_ATTR_NONNULL_ALL +{ + // When "rex.need_clear_subexpr" is set we don't need to save the values, only + // remember that this flag needs to be set again when restoring. + bp->save_need_clear_subexpr = rex.need_clear_subexpr; + if (!rex.need_clear_subexpr) { + for (int i = 0; i < NSUBEXP; i++) { + if (REG_MULTI) { + bp->save_start[i].se_u.pos = rex.reg_startpos[i]; + bp->save_end[i].se_u.pos = rex.reg_endpos[i]; + } else { + bp->save_start[i].se_u.ptr = rex.reg_startp[i]; + bp->save_end[i].se_u.ptr = rex.reg_endp[i]; + } + } + } +} + +// Restore the subexpr from "bp". +static void restore_subexpr(regbehind_T *bp) + FUNC_ATTR_NONNULL_ALL +{ + // Only need to restore saved values when they are not to be cleared. + rex.need_clear_subexpr = bp->save_need_clear_subexpr; + if (!rex.need_clear_subexpr) { + for (int i = 0; i < NSUBEXP; i++) { + if (REG_MULTI) { + rex.reg_startpos[i] = bp->save_start[i].se_u.pos; + rex.reg_endpos[i] = bp->save_end[i].se_u.pos; + } else { + rex.reg_startp[i] = bp->save_start[i].se_u.ptr; + rex.reg_endp[i] = bp->save_end[i].se_u.ptr; + } + } + } +} +/// Main matching routine +/// +/// Conceptually the strategy is simple: Check to see whether the current node +/// matches, push an item onto the regstack and loop to see whether the rest +/// matches, and then act accordingly. In practice we make some effort to +/// avoid using the regstack, in particular by going through "ordinary" nodes +/// (that don't need to know whether the rest of the match failed) by a nested +/// loop. +/// +/// Returns true when there is a match. Leaves rex.input and rex.lnum +/// just after the last matched character. +/// Returns false when there is no match. Leaves rex.input and rex.lnum in an +/// undefined state! +static bool regmatch( + char_u *scan, // Current node. + proftime_T *tm, // timeout limit or NULL + int *timed_out) // flag set on timeout or NULL +{ + char_u *next; // Next node. + int op; + int c; + regitem_T *rp; + int no; + int status; // one of the RA_ values: + int tm_count = 0; + + // Make "regstack" and "backpos" empty. They are allocated and freed in + // bt_regexec_both() to reduce malloc()/free() calls. + regstack.ga_len = 0; + backpos.ga_len = 0; + + // Repeat until "regstack" is empty. + for (;; ) { + // Some patterns may take a long time to match, e.g., "\([a-z]\+\)\+Q". + // Allow interrupting them with CTRL-C. + fast_breakcheck(); + +#ifdef REGEXP_DEBUG + if (scan != NULL && regnarrate) { + mch_errmsg((char *)regprop(scan)); + mch_errmsg("(\n"); + } +#endif + + // Repeat for items that can be matched sequentially, without using the + // regstack. + for (;; ) { + if (got_int || scan == NULL) { + status = RA_FAIL; + break; + } + // Check for timeout once in a 100 times to avoid overhead. + if (tm != NULL && ++tm_count == 100) { + tm_count = 0; + if (profile_passed_limit(*tm)) { + if (timed_out != NULL) { + *timed_out = true; + } + status = RA_FAIL; + break; + } + } + status = RA_CONT; + +#ifdef REGEXP_DEBUG + if (regnarrate) { + mch_errmsg((char *)regprop(scan)); + mch_errmsg("...\n"); + if (re_extmatch_in != NULL) { + int i; + + mch_errmsg(_("External submatches:\n")); + for (i = 0; i < NSUBEXP; i++) { + mch_errmsg(" \""); + if (re_extmatch_in->matches[i] != NULL) + mch_errmsg((char *)re_extmatch_in->matches[i]); + mch_errmsg("\"\n"); + } + } + } +#endif + next = regnext(scan); + + op = OP(scan); + // Check for character class with NL added. + if (!rex.reg_line_lbr && WITH_NL(op) && REG_MULTI + && *rex.input == NUL && rex.lnum <= rex.reg_maxline) { + reg_nextline(); + } else if (rex.reg_line_lbr && WITH_NL(op) && *rex.input == '\n') { + ADVANCE_REGINPUT(); + } else { + if (WITH_NL(op)) { + op -= ADD_NL; + } + c = utf_ptr2char(rex.input); + switch (op) { + case BOL: + if (rex.input != rex.line) { + status = RA_NOMATCH; + } + break; + + case EOL: + if (c != NUL) { + status = RA_NOMATCH; + } + break; + + case RE_BOF: + // We're not at the beginning of the file when below the first + // line where we started, not at the start of the line or we + // didn't start at the first line of the buffer. + if (rex.lnum != 0 || rex.input != rex.line + || (REG_MULTI && rex.reg_firstlnum > 1)) { + status = RA_NOMATCH; + } + break; + + case RE_EOF: + if (rex.lnum != rex.reg_maxline || c != NUL) { + status = RA_NOMATCH; + } + break; + + case CURSOR: + // Check if the buffer is in a window and compare the + // rex.reg_win->w_cursor position to the match position. + if (rex.reg_win == NULL + || (rex.lnum + rex.reg_firstlnum != rex.reg_win->w_cursor.lnum) + || ((colnr_T)(rex.input - rex.line) != + rex.reg_win->w_cursor.col)) { + status = RA_NOMATCH; + } + break; + + case RE_MARK: + // Compare the mark position to the match position. + { + int mark = OPERAND(scan)[0]; + int cmp = OPERAND(scan)[1]; + pos_T *pos; + size_t col = REG_MULTI ? rex.input - rex.line : 0; + + pos = getmark_buf(rex.reg_buf, mark, false); + + // Line may have been freed, get it again. + if (REG_MULTI) { + rex.line = reg_getline(rex.lnum); + rex.input = rex.line + col; + } + + if (pos == NULL // mark doesn't exist + || pos->lnum <= 0) { // mark isn't set in reg_buf + status = RA_NOMATCH; + } else { + const colnr_T pos_col = pos->lnum == rex.lnum + rex.reg_firstlnum + && pos->col == MAXCOL + ? (colnr_T)STRLEN(reg_getline(pos->lnum - rex.reg_firstlnum)) + : pos->col; + + if (pos->lnum == rex.lnum + rex.reg_firstlnum + ? (pos_col == (colnr_T)(rex.input - rex.line) + ? (cmp == '<' || cmp == '>') + : (pos_col < (colnr_T)(rex.input - rex.line) + ? cmp != '>' + : cmp != '<')) + : (pos->lnum < rex.lnum + rex.reg_firstlnum + ? cmp != '>' + : cmp != '<')) { + status = RA_NOMATCH; + } + } + } + break; + + case RE_VISUAL: + if (!reg_match_visual()) + status = RA_NOMATCH; + break; + + case RE_LNUM: + assert(rex.lnum + rex.reg_firstlnum >= 0 + && (uintmax_t)(rex.lnum + rex.reg_firstlnum) <= UINT32_MAX); + if (!REG_MULTI + || !re_num_cmp((uint32_t)(rex.lnum + rex.reg_firstlnum), scan)) { + status = RA_NOMATCH; + } + break; + + case RE_COL: + assert(rex.input - rex.line + 1 >= 0 + && (uintmax_t)(rex.input - rex.line + 1) <= UINT32_MAX); + if (!re_num_cmp((uint32_t)(rex.input - rex.line + 1), scan)) { + status = RA_NOMATCH; + } + break; + + case RE_VCOL: + if (!re_num_cmp(win_linetabsize(rex.reg_win == NULL + ? curwin : rex.reg_win, + rex.line, + (colnr_T)(rex.input - rex.line)) + 1, + scan)) { + status = RA_NOMATCH; + } + break; + + case BOW: // \<word; rex.input points to w + if (c == NUL) { // Can't match at end of line + status = RA_NOMATCH; + } else { + // Get class of current and previous char (if it exists). + const int this_class = + mb_get_class_tab(rex.input, rex.reg_buf->b_chartab); + if (this_class <= 1) { + status = RA_NOMATCH; // Not on a word at all. + } else if (reg_prev_class() == this_class) { + status = RA_NOMATCH; // Previous char is in same word. + } + } + break; + + case EOW: // word\>; rex.input points after d + if (rex.input == rex.line) { // Can't match at start of line + status = RA_NOMATCH; + } else { + int this_class, prev_class; + + // Get class of current and previous char (if it exists). + this_class = mb_get_class_tab(rex.input, rex.reg_buf->b_chartab); + prev_class = reg_prev_class(); + if (this_class == prev_class + || prev_class == 0 || prev_class == 1) { + status = RA_NOMATCH; + } + } + break; // Matched with EOW + + case ANY: + // ANY does not match new lines. + if (c == NUL) { + status = RA_NOMATCH; + } else { + ADVANCE_REGINPUT(); + } + break; + + case IDENT: + if (!vim_isIDc(c)) + status = RA_NOMATCH; + else + ADVANCE_REGINPUT(); + break; + + case SIDENT: + if (ascii_isdigit(*rex.input) || !vim_isIDc(c)) { + status = RA_NOMATCH; + } else { + ADVANCE_REGINPUT(); + } + break; + + case KWORD: + if (!vim_iswordp_buf(rex.input, rex.reg_buf)) { + status = RA_NOMATCH; + } else { + ADVANCE_REGINPUT(); + } + break; + + case SKWORD: + if (ascii_isdigit(*rex.input) + || !vim_iswordp_buf(rex.input, rex.reg_buf)) { + status = RA_NOMATCH; + } else { + ADVANCE_REGINPUT(); + } + break; + + case FNAME: + if (!vim_isfilec(c)) { + status = RA_NOMATCH; + } else { + ADVANCE_REGINPUT(); + } + break; + + case SFNAME: + if (ascii_isdigit(*rex.input) || !vim_isfilec(c)) { + status = RA_NOMATCH; + } else { + ADVANCE_REGINPUT(); + } + break; + + case PRINT: + if (!vim_isprintc(utf_ptr2char(rex.input))) { + status = RA_NOMATCH; + } else { + ADVANCE_REGINPUT(); + } + break; + + case SPRINT: + if (ascii_isdigit(*rex.input) || !vim_isprintc(utf_ptr2char(rex.input))) { + status = RA_NOMATCH; + } else { + ADVANCE_REGINPUT(); + } + break; + + case WHITE: + if (!ascii_iswhite(c)) + status = RA_NOMATCH; + else + ADVANCE_REGINPUT(); + break; + + case NWHITE: + if (c == NUL || ascii_iswhite(c)) + status = RA_NOMATCH; + else + ADVANCE_REGINPUT(); + break; + + case DIGIT: + if (!ri_digit(c)) + status = RA_NOMATCH; + else + ADVANCE_REGINPUT(); + break; + + case NDIGIT: + if (c == NUL || ri_digit(c)) + status = RA_NOMATCH; + else + ADVANCE_REGINPUT(); + break; + + case HEX: + if (!ri_hex(c)) + status = RA_NOMATCH; + else + ADVANCE_REGINPUT(); + break; + + case NHEX: + if (c == NUL || ri_hex(c)) + status = RA_NOMATCH; + else + ADVANCE_REGINPUT(); + break; + + case OCTAL: + if (!ri_octal(c)) + status = RA_NOMATCH; + else + ADVANCE_REGINPUT(); + break; + + case NOCTAL: + if (c == NUL || ri_octal(c)) + status = RA_NOMATCH; + else + ADVANCE_REGINPUT(); + break; + + case WORD: + if (!ri_word(c)) + status = RA_NOMATCH; + else + ADVANCE_REGINPUT(); + break; + + case NWORD: + if (c == NUL || ri_word(c)) + status = RA_NOMATCH; + else + ADVANCE_REGINPUT(); + break; + + case HEAD: + if (!ri_head(c)) + status = RA_NOMATCH; + else + ADVANCE_REGINPUT(); + break; + + case NHEAD: + if (c == NUL || ri_head(c)) + status = RA_NOMATCH; + else + ADVANCE_REGINPUT(); + break; + + case ALPHA: + if (!ri_alpha(c)) + status = RA_NOMATCH; + else + ADVANCE_REGINPUT(); + break; + + case NALPHA: + if (c == NUL || ri_alpha(c)) + status = RA_NOMATCH; + else + ADVANCE_REGINPUT(); + break; + + case LOWER: + if (!ri_lower(c)) + status = RA_NOMATCH; + else + ADVANCE_REGINPUT(); + break; + + case NLOWER: + if (c == NUL || ri_lower(c)) + status = RA_NOMATCH; + else + ADVANCE_REGINPUT(); + break; + + case UPPER: + if (!ri_upper(c)) + status = RA_NOMATCH; + else + ADVANCE_REGINPUT(); + break; + + case NUPPER: + if (c == NUL || ri_upper(c)) + status = RA_NOMATCH; + else + ADVANCE_REGINPUT(); + break; + + case EXACTLY: + { + int len; + char_u *opnd; + + opnd = OPERAND(scan); + // Inline the first byte, for speed. + if (*opnd != *rex.input + && (!rex.reg_ic)) { + status = RA_NOMATCH; + } else if (*opnd == NUL) { + // match empty string always works; happens when "~" is + // empty. + } else { + if (opnd[1] == NUL && !rex.reg_ic) { + len = 1; // matched a single byte above + } else { + // Need to match first byte again for multi-byte. + len = (int)STRLEN(opnd); + if (cstrncmp(opnd, rex.input, &len) != 0) { + status = RA_NOMATCH; + } + } + // Check for following composing character, unless %C + // follows (skips over all composing chars). + if (status != RA_NOMATCH + && utf_composinglike(rex.input, rex.input + len) + && !rex.reg_icombine + && OP(next) != RE_COMPOSING) { + // raaron: This code makes a composing character get + // ignored, which is the correct behavior (sometimes) + // for voweled Hebrew texts. + status = RA_NOMATCH; + } + if (status != RA_NOMATCH) { + rex.input += len; + } + } + } + break; + + case ANYOF: + case ANYBUT: + if (c == NUL) + status = RA_NOMATCH; + else if ((cstrchr(OPERAND(scan), c) == NULL) == (op == ANYOF)) + status = RA_NOMATCH; + else + ADVANCE_REGINPUT(); + break; + + case MULTIBYTECODE: + { + int i, len; + + const char_u *opnd = OPERAND(scan); + // Safety check (just in case 'encoding' was changed since + // compiling the program). + if ((len = utfc_ptr2len(opnd)) < 2) { + status = RA_NOMATCH; + break; + } + const int opndc = utf_ptr2char(opnd); + if (utf_iscomposing(opndc)) { + // When only a composing char is given match at any + // position where that composing char appears. + status = RA_NOMATCH; + for (i = 0; rex.input[i] != NUL; + i += utf_ptr2len(rex.input + i)) { + const int inpc = utf_ptr2char(rex.input + i); + if (!utf_iscomposing(inpc)) { + if (i > 0) { + break; + } + } else if (opndc == inpc) { + // Include all following composing chars. + len = i + utfc_ptr2len(rex.input + i); + status = RA_MATCH; + break; + } + } + } else { + for (i = 0; i < len; i++) { + if (opnd[i] != rex.input[i]) { + status = RA_NOMATCH; + break; + } + } + } + rex.input += len; + } + break; + + case RE_COMPOSING: + { + // Skip composing characters. + while (utf_iscomposing(utf_ptr2char(rex.input))) { + MB_CPTR_ADV(rex.input); + } + } + break; + + case NOTHING: + break; + + case BACK: + { + int i; + + // When we run into BACK we need to check if we don't keep + // looping without matching any input. The second and later + // times a BACK is encountered it fails if the input is still + // at the same position as the previous time. + // The positions are stored in "backpos" and found by the + // current value of "scan", the position in the RE program. + backpos_T *bp = (backpos_T *)backpos.ga_data; + for (i = 0; i < backpos.ga_len; ++i) + if (bp[i].bp_scan == scan) + break; + if (i == backpos.ga_len) { + backpos_T *p = GA_APPEND_VIA_PTR(backpos_T, &backpos); + p->bp_scan = scan; + } else if (reg_save_equal(&bp[i].bp_pos)) + // Still at same position as last time, fail. + status = RA_NOMATCH; + + assert(status != RA_FAIL); + if (status != RA_NOMATCH) { + reg_save(&bp[i].bp_pos, &backpos); + } + } + break; + + case MOPEN + 0: // Match start: \zs + case MOPEN + 1: // \( + case MOPEN + 2: + case MOPEN + 3: + case MOPEN + 4: + case MOPEN + 5: + case MOPEN + 6: + case MOPEN + 7: + case MOPEN + 8: + case MOPEN + 9: + { + no = op - MOPEN; + cleanup_subexpr(); + rp = regstack_push(RS_MOPEN, scan); + if (rp == NULL) + status = RA_FAIL; + else { + rp->rs_no = no; + save_se(&rp->rs_un.sesave, &rex.reg_startpos[no], + &rex.reg_startp[no]); + // We simply continue and handle the result when done. + } + } + break; + + case NOPEN: // \%( + case NCLOSE: // \) after \%( + if (regstack_push(RS_NOPEN, scan) == NULL) + status = RA_FAIL; + // We simply continue and handle the result when done. + break; + + case ZOPEN + 1: + case ZOPEN + 2: + case ZOPEN + 3: + case ZOPEN + 4: + case ZOPEN + 5: + case ZOPEN + 6: + case ZOPEN + 7: + case ZOPEN + 8: + case ZOPEN + 9: + { + no = op - ZOPEN; + cleanup_zsubexpr(); + rp = regstack_push(RS_ZOPEN, scan); + if (rp == NULL) + status = RA_FAIL; + else { + rp->rs_no = no; + save_se(&rp->rs_un.sesave, ®_startzpos[no], + ®_startzp[no]); + // We simply continue and handle the result when done. + } + } + break; + + case MCLOSE + 0: // Match end: \ze + case MCLOSE + 1: // \) + case MCLOSE + 2: + case MCLOSE + 3: + case MCLOSE + 4: + case MCLOSE + 5: + case MCLOSE + 6: + case MCLOSE + 7: + case MCLOSE + 8: + case MCLOSE + 9: + { + no = op - MCLOSE; + cleanup_subexpr(); + rp = regstack_push(RS_MCLOSE, scan); + if (rp == NULL) { + status = RA_FAIL; + } else { + rp->rs_no = no; + save_se(&rp->rs_un.sesave, &rex.reg_endpos[no], &rex.reg_endp[no]); + // We simply continue and handle the result when done. + } + } + break; + + case ZCLOSE + 1: // \) after \z( + case ZCLOSE + 2: + case ZCLOSE + 3: + case ZCLOSE + 4: + case ZCLOSE + 5: + case ZCLOSE + 6: + case ZCLOSE + 7: + case ZCLOSE + 8: + case ZCLOSE + 9: + { + no = op - ZCLOSE; + cleanup_zsubexpr(); + rp = regstack_push(RS_ZCLOSE, scan); + if (rp == NULL) + status = RA_FAIL; + else { + rp->rs_no = no; + save_se(&rp->rs_un.sesave, ®_endzpos[no], + ®_endzp[no]); + // We simply continue and handle the result when done. + } + } + break; + + case BACKREF + 1: + case BACKREF + 2: + case BACKREF + 3: + case BACKREF + 4: + case BACKREF + 5: + case BACKREF + 6: + case BACKREF + 7: + case BACKREF + 8: + case BACKREF + 9: + { + int len; + + no = op - BACKREF; + cleanup_subexpr(); + if (!REG_MULTI) { // Single-line regexp + if (rex.reg_startp[no] == NULL || rex.reg_endp[no] == NULL) { + // Backref was not set: Match an empty string. + len = 0; + } else { + // Compare current input with back-ref in the same line. + len = (int)(rex.reg_endp[no] - rex.reg_startp[no]); + if (cstrncmp(rex.reg_startp[no], rex.input, &len) != 0) { + status = RA_NOMATCH; + } + } + } else { // Multi-line regexp + if (rex.reg_startpos[no].lnum < 0 || rex.reg_endpos[no].lnum < 0) { + // Backref was not set: Match an empty string. + len = 0; + } else { + if (rex.reg_startpos[no].lnum == rex.lnum + && rex.reg_endpos[no].lnum == rex.lnum) { + // Compare back-ref within the current line. + len = rex.reg_endpos[no].col - rex.reg_startpos[no].col; + if (cstrncmp(rex.line + rex.reg_startpos[no].col, + rex.input, &len) != 0) { + status = RA_NOMATCH; + } + } else { + // Messy situation: Need to compare between two lines. + int r = match_with_backref(rex.reg_startpos[no].lnum, + rex.reg_startpos[no].col, + rex.reg_endpos[no].lnum, + rex.reg_endpos[no].col, + &len); + if (r != RA_MATCH) { + status = r; + } + } + } + } + + // Matched the backref, skip over it. + rex.input += len; + } + break; + + case ZREF + 1: + case ZREF + 2: + case ZREF + 3: + case ZREF + 4: + case ZREF + 5: + case ZREF + 6: + case ZREF + 7: + case ZREF + 8: + case ZREF + 9: + { + cleanup_zsubexpr(); + no = op - ZREF; + if (re_extmatch_in != NULL + && re_extmatch_in->matches[no] != NULL) { + int len = (int)STRLEN(re_extmatch_in->matches[no]); + if (cstrncmp(re_extmatch_in->matches[no], rex.input, &len) != 0) { + status = RA_NOMATCH; + } else { + rex.input += len; + } + } else { + // Backref was not set: Match an empty string. + } + } + break; + + case BRANCH: + { + if (OP(next) != BRANCH) // No choice. + next = OPERAND(scan); // Avoid recursion. + else { + rp = regstack_push(RS_BRANCH, scan); + if (rp == NULL) + status = RA_FAIL; + else + status = RA_BREAK; // rest is below + } + } + break; + + case BRACE_LIMITS: + { + if (OP(next) == BRACE_SIMPLE) { + bl_minval = OPERAND_MIN(scan); + bl_maxval = OPERAND_MAX(scan); + } else if (OP(next) >= BRACE_COMPLEX + && OP(next) < BRACE_COMPLEX + 10) { + no = OP(next) - BRACE_COMPLEX; + brace_min[no] = OPERAND_MIN(scan); + brace_max[no] = OPERAND_MAX(scan); + brace_count[no] = 0; + } else { + internal_error("BRACE_LIMITS"); + status = RA_FAIL; + } + } + break; + + case BRACE_COMPLEX + 0: + case BRACE_COMPLEX + 1: + case BRACE_COMPLEX + 2: + case BRACE_COMPLEX + 3: + case BRACE_COMPLEX + 4: + case BRACE_COMPLEX + 5: + case BRACE_COMPLEX + 6: + case BRACE_COMPLEX + 7: + case BRACE_COMPLEX + 8: + case BRACE_COMPLEX + 9: + { + no = op - BRACE_COMPLEX; + ++brace_count[no]; + + // If not matched enough times yet, try one more + if (brace_count[no] <= (brace_min[no] <= brace_max[no] + ? brace_min[no] : brace_max[no])) { + rp = regstack_push(RS_BRCPLX_MORE, scan); + if (rp == NULL) + status = RA_FAIL; + else { + rp->rs_no = no; + reg_save(&rp->rs_un.regsave, &backpos); + next = OPERAND(scan); + // We continue and handle the result when done. + } + break; + } + + // If matched enough times, may try matching some more + if (brace_min[no] <= brace_max[no]) { + // Range is the normal way around, use longest match + if (brace_count[no] <= brace_max[no]) { + rp = regstack_push(RS_BRCPLX_LONG, scan); + if (rp == NULL) + status = RA_FAIL; + else { + rp->rs_no = no; + reg_save(&rp->rs_un.regsave, &backpos); + next = OPERAND(scan); + // We continue and handle the result when done. + } + } + } else { + // Range is backwards, use shortest match first + if (brace_count[no] <= brace_min[no]) { + rp = regstack_push(RS_BRCPLX_SHORT, scan); + if (rp == NULL) + status = RA_FAIL; + else { + reg_save(&rp->rs_un.regsave, &backpos); + // We continue and handle the result when done. + } + } + } + } + break; + + case BRACE_SIMPLE: + case STAR: + case PLUS: + { + regstar_T rst; + + // Lookahead to avoid useless match attempts when we know + // what character comes next. + if (OP(next) == EXACTLY) { + rst.nextb = *OPERAND(next); + if (rex.reg_ic) { + if (mb_isupper(rst.nextb)) { + rst.nextb_ic = mb_tolower(rst.nextb); + } else { + rst.nextb_ic = mb_toupper(rst.nextb); + } + } else { + rst.nextb_ic = rst.nextb; + } + } else { + rst.nextb = NUL; + rst.nextb_ic = NUL; + } + if (op != BRACE_SIMPLE) { + rst.minval = (op == STAR) ? 0 : 1; + rst.maxval = MAX_LIMIT; + } else { + rst.minval = bl_minval; + rst.maxval = bl_maxval; + } + + // When maxval > minval, try matching as much as possible, up + // to maxval. When maxval < minval, try matching at least the + // minimal number (since the range is backwards, that's also + // maxval!). + rst.count = regrepeat(OPERAND(scan), rst.maxval); + if (got_int) { + status = RA_FAIL; + break; + } + if (rst.minval <= rst.maxval + ? rst.count >= rst.minval : rst.count >= rst.maxval) { + // It could match. Prepare for trying to match what + // follows. The code is below. Parameters are stored in + // a regstar_T on the regstack. + if ((long)((unsigned)regstack.ga_len >> 10) >= p_mmp) { + emsg(_(e_maxmempat)); + status = RA_FAIL; + } else { + ga_grow(®stack, sizeof(regstar_T)); + regstack.ga_len += sizeof(regstar_T); + rp = regstack_push(rst.minval <= rst.maxval + ? RS_STAR_LONG : RS_STAR_SHORT, scan); + if (rp == NULL) + status = RA_FAIL; + else { + *(((regstar_T *)rp) - 1) = rst; + status = RA_BREAK; // skip the restore bits + } + } + } else + status = RA_NOMATCH; + + } + break; + + case NOMATCH: + case MATCH: + case SUBPAT: + rp = regstack_push(RS_NOMATCH, scan); + if (rp == NULL) + status = RA_FAIL; + else { + rp->rs_no = op; + reg_save(&rp->rs_un.regsave, &backpos); + next = OPERAND(scan); + // We continue and handle the result when done. + } + break; + + case BEHIND: + case NOBEHIND: + // Need a bit of room to store extra positions. + if ((long)((unsigned)regstack.ga_len >> 10) >= p_mmp) { + emsg(_(e_maxmempat)); + status = RA_FAIL; + } else { + ga_grow(®stack, sizeof(regbehind_T)); + regstack.ga_len += sizeof(regbehind_T); + rp = regstack_push(RS_BEHIND1, scan); + if (rp == NULL) + status = RA_FAIL; + else { + // Need to save the subexpr to be able to restore them + // when there is a match but we don't use it. + save_subexpr(((regbehind_T *)rp) - 1); + + rp->rs_no = op; + reg_save(&rp->rs_un.regsave, &backpos); + // First try if what follows matches. If it does then we + // check the behind match by looping. + } + } + break; + + case BHPOS: + if (REG_MULTI) { + if (behind_pos.rs_u.pos.col != (colnr_T)(rex.input - rex.line) + || behind_pos.rs_u.pos.lnum != rex.lnum) { + status = RA_NOMATCH; + } + } else if (behind_pos.rs_u.ptr != rex.input) { + status = RA_NOMATCH; + } + break; + + case NEWL: + if ((c != NUL || !REG_MULTI || rex.lnum > rex.reg_maxline + || rex.reg_line_lbr) && (c != '\n' || !rex.reg_line_lbr)) { + status = RA_NOMATCH; + } else if (rex.reg_line_lbr) { + ADVANCE_REGINPUT(); + } else { + reg_nextline(); + } + break; + + case END: + status = RA_MATCH; // Success! + break; + + default: + iemsg(_(e_re_corr)); +#ifdef REGEXP_DEBUG + printf("Illegal op code %d\n", op); +#endif + status = RA_FAIL; + break; + } + } + + // If we can't continue sequentially, break the inner loop. + if (status != RA_CONT) + break; + + // Continue in inner loop, advance to next item. + scan = next; + + } // end of inner loop + + // If there is something on the regstack execute the code for the state. + // If the state is popped then loop and use the older state. + while (!GA_EMPTY(®stack) && status != RA_FAIL) { + rp = (regitem_T *)((char *)regstack.ga_data + regstack.ga_len) - 1; + switch (rp->rs_state) { + case RS_NOPEN: + // Result is passed on as-is, simply pop the state. + regstack_pop(&scan); + break; + + case RS_MOPEN: + // Pop the state. Restore pointers when there is no match. + if (status == RA_NOMATCH) { + restore_se(&rp->rs_un.sesave, &rex.reg_startpos[rp->rs_no], + &rex.reg_startp[rp->rs_no]); + } + regstack_pop(&scan); + break; + + case RS_ZOPEN: + // Pop the state. Restore pointers when there is no match. + if (status == RA_NOMATCH) + restore_se(&rp->rs_un.sesave, ®_startzpos[rp->rs_no], + ®_startzp[rp->rs_no]); + regstack_pop(&scan); + break; + + case RS_MCLOSE: + // Pop the state. Restore pointers when there is no match. + if (status == RA_NOMATCH) { + restore_se(&rp->rs_un.sesave, &rex.reg_endpos[rp->rs_no], + &rex.reg_endp[rp->rs_no]); + } + regstack_pop(&scan); + break; + + case RS_ZCLOSE: + // Pop the state. Restore pointers when there is no match. + if (status == RA_NOMATCH) + restore_se(&rp->rs_un.sesave, ®_endzpos[rp->rs_no], + ®_endzp[rp->rs_no]); + regstack_pop(&scan); + break; + + case RS_BRANCH: + if (status == RA_MATCH) + // this branch matched, use it + regstack_pop(&scan); + else { + if (status != RA_BREAK) { + // After a non-matching branch: try next one. + reg_restore(&rp->rs_un.regsave, &backpos); + scan = rp->rs_scan; + } + if (scan == NULL || OP(scan) != BRANCH) { + // no more branches, didn't find a match + status = RA_NOMATCH; + regstack_pop(&scan); + } else { + // Prepare to try a branch. + rp->rs_scan = regnext(scan); + reg_save(&rp->rs_un.regsave, &backpos); + scan = OPERAND(scan); + } + } + break; + + case RS_BRCPLX_MORE: + // Pop the state. Restore pointers when there is no match. + if (status == RA_NOMATCH) { + reg_restore(&rp->rs_un.regsave, &backpos); + --brace_count[rp->rs_no]; // decrement match count + } + regstack_pop(&scan); + break; + + case RS_BRCPLX_LONG: + // Pop the state. Restore pointers when there is no match. + if (status == RA_NOMATCH) { + // There was no match, but we did find enough matches. + reg_restore(&rp->rs_un.regsave, &backpos); + --brace_count[rp->rs_no]; + // continue with the items after "\{}" + status = RA_CONT; + } + regstack_pop(&scan); + if (status == RA_CONT) + scan = regnext(scan); + break; + + case RS_BRCPLX_SHORT: + // Pop the state. Restore pointers when there is no match. + if (status == RA_NOMATCH) + // There was no match, try to match one more item. + reg_restore(&rp->rs_un.regsave, &backpos); + regstack_pop(&scan); + if (status == RA_NOMATCH) { + scan = OPERAND(scan); + status = RA_CONT; + } + break; + + case RS_NOMATCH: + // Pop the state. If the operand matches for NOMATCH or + // doesn't match for MATCH/SUBPAT, we fail. Otherwise backup, + // except for SUBPAT, and continue with the next item. + if (status == (rp->rs_no == NOMATCH ? RA_MATCH : RA_NOMATCH)) + status = RA_NOMATCH; + else { + status = RA_CONT; + if (rp->rs_no != SUBPAT) // zero-width + reg_restore(&rp->rs_un.regsave, &backpos); + } + regstack_pop(&scan); + if (status == RA_CONT) + scan = regnext(scan); + break; + + case RS_BEHIND1: + if (status == RA_NOMATCH) { + regstack_pop(&scan); + regstack.ga_len -= sizeof(regbehind_T); + } else { + // The stuff after BEHIND/NOBEHIND matches. Now try if + // the behind part does (not) match before the current + // position in the input. This must be done at every + // position in the input and checking if the match ends at + // the current position. + + // save the position after the found match for next + reg_save(&(((regbehind_T *)rp) - 1)->save_after, &backpos); + + // Start looking for a match with operand at the current + // position. Go back one character until we find the + // result, hitting the start of the line or the previous + // line (for multi-line matching). + // Set behind_pos to where the match should end, BHPOS + // will match it. Save the current value. + (((regbehind_T *)rp) - 1)->save_behind = behind_pos; + behind_pos = rp->rs_un.regsave; + + rp->rs_state = RS_BEHIND2; + + reg_restore(&rp->rs_un.regsave, &backpos); + scan = OPERAND(rp->rs_scan) + 4; + } + break; + + case RS_BEHIND2: + // Looping for BEHIND / NOBEHIND match. + if (status == RA_MATCH && reg_save_equal(&behind_pos)) { + // found a match that ends where "next" started + behind_pos = (((regbehind_T *)rp) - 1)->save_behind; + if (rp->rs_no == BEHIND) + reg_restore(&(((regbehind_T *)rp) - 1)->save_after, + &backpos); + else { + // But we didn't want a match. Need to restore the + // subexpr, because what follows matched, so they have + // been set. + status = RA_NOMATCH; + restore_subexpr(((regbehind_T *)rp) - 1); + } + regstack_pop(&scan); + regstack.ga_len -= sizeof(regbehind_T); + } else { + long limit; + + // No match or a match that doesn't end where we want it: Go + // back one character. May go to previous line once. + no = OK; + limit = OPERAND_MIN(rp->rs_scan); + if (REG_MULTI) { + if (limit > 0 + && ((rp->rs_un.regsave.rs_u.pos.lnum + < behind_pos.rs_u.pos.lnum + ? (colnr_T)STRLEN(rex.line) + : behind_pos.rs_u.pos.col) + - rp->rs_un.regsave.rs_u.pos.col >= limit)) + no = FAIL; + else if (rp->rs_un.regsave.rs_u.pos.col == 0) { + if (rp->rs_un.regsave.rs_u.pos.lnum + < behind_pos.rs_u.pos.lnum + || reg_getline( + --rp->rs_un.regsave.rs_u.pos.lnum) + == NULL) + no = FAIL; + else { + reg_restore(&rp->rs_un.regsave, &backpos); + rp->rs_un.regsave.rs_u.pos.col = + (colnr_T)STRLEN(rex.line); + } + } else { + const char_u *const line = + reg_getline(rp->rs_un.regsave.rs_u.pos.lnum); + + rp->rs_un.regsave.rs_u.pos.col -= + utf_head_off(line, + line + rp->rs_un.regsave.rs_u.pos.col - 1) + + 1; + } + } else { + if (rp->rs_un.regsave.rs_u.ptr == rex.line) { + no = FAIL; + } else { + MB_PTR_BACK(rex.line, rp->rs_un.regsave.rs_u.ptr); + if (limit > 0 + && (behind_pos.rs_u.ptr - rp->rs_un.regsave.rs_u.ptr) > (ptrdiff_t)limit) { + no = FAIL; + } + } + } + if (no == OK) { + // Advanced, prepare for finding match again. + reg_restore(&rp->rs_un.regsave, &backpos); + scan = OPERAND(rp->rs_scan) + 4; + if (status == RA_MATCH) { + // We did match, so subexpr may have been changed, + // need to restore them for the next try. + status = RA_NOMATCH; + restore_subexpr(((regbehind_T *)rp) - 1); + } + } else { + // Can't advance. For NOBEHIND that's a match. + behind_pos = (((regbehind_T *)rp) - 1)->save_behind; + if (rp->rs_no == NOBEHIND) { + reg_restore(&(((regbehind_T *)rp) - 1)->save_after, + &backpos); + status = RA_MATCH; + } else { + // We do want a proper match. Need to restore the + // subexpr if we had a match, because they may have + // been set. + if (status == RA_MATCH) { + status = RA_NOMATCH; + restore_subexpr(((regbehind_T *)rp) - 1); + } + } + regstack_pop(&scan); + regstack.ga_len -= sizeof(regbehind_T); + } + } + break; + + case RS_STAR_LONG: + case RS_STAR_SHORT: + { + regstar_T *rst = ((regstar_T *)rp) - 1; + + if (status == RA_MATCH) { + regstack_pop(&scan); + regstack.ga_len -= sizeof(regstar_T); + break; + } + + // Tried once already, restore input pointers. + if (status != RA_BREAK) + reg_restore(&rp->rs_un.regsave, &backpos); + + // Repeat until we found a position where it could match. + for (;; ) { + if (status != RA_BREAK) { + // Tried first position already, advance. + if (rp->rs_state == RS_STAR_LONG) { + // Trying for longest match, but couldn't or + // didn't match -- back up one char. + if (--rst->count < rst->minval) + break; + if (rex.input == rex.line) { + // backup to last char of previous line + if (rex.lnum == 0) { + status = RA_NOMATCH; + break; + } + rex.lnum--; + rex.line = reg_getline(rex.lnum); + // Just in case regrepeat() didn't count right. + if (rex.line == NULL) { + break; + } + rex.input = rex.line + STRLEN(rex.line); + fast_breakcheck(); + } else { + MB_PTR_BACK(rex.line, rex.input); + } + } else { + // Range is backwards, use shortest match first. + // Careful: maxval and minval are exchanged! + // Couldn't or didn't match: try advancing one + // char. + if (rst->count == rst->minval + || regrepeat(OPERAND(rp->rs_scan), 1L) == 0) + break; + ++rst->count; + } + if (got_int) + break; + } else + status = RA_NOMATCH; + + // If it could match, try it. + if (rst->nextb == NUL || *rex.input == rst->nextb + || *rex.input == rst->nextb_ic) { + reg_save(&rp->rs_un.regsave, &backpos); + scan = regnext(rp->rs_scan); + status = RA_CONT; + break; + } + } + if (status != RA_CONT) { + // Failed. + regstack_pop(&scan); + regstack.ga_len -= sizeof(regstar_T); + status = RA_NOMATCH; + } + } + break; + } + + // If we want to continue the inner loop or didn't pop a state + // continue matching loop + if (status == RA_CONT || rp == (regitem_T *) + ((char *)regstack.ga_data + regstack.ga_len) - 1) + break; + } + + // May need to continue with the inner loop, starting at "scan". + if (status == RA_CONT) + continue; + + // If the regstack is empty or something failed we are done. + if (GA_EMPTY(®stack) || status == RA_FAIL) { + if (scan == NULL) { + // We get here only if there's trouble -- normally "case END" is + // the terminating point. + iemsg(_(e_re_corr)); +#ifdef REGEXP_DEBUG + printf("Premature EOL\n"); +#endif + } + return status == RA_MATCH; + } + + } // End of loop until the regstack is empty. + + // NOTREACHED +} + +/// Try match of "prog" with at rex.line["col"]. +/// @returns 0 for failure, or number of lines contained in the match. +static long regtry(bt_regprog_T *prog, + colnr_T col, + proftime_T *tm, // timeout limit or NULL + int *timed_out) // flag set on timeout or NULL +{ + rex.input = rex.line + col; + rex.need_clear_subexpr = true; + // Clear the external match subpointers if necessaey. + rex.need_clear_zsubexpr = (prog->reghasz == REX_SET); + + if (regmatch(prog->program + 1, tm, timed_out) == 0) { + return 0; + } + + cleanup_subexpr(); + if (REG_MULTI) { + if (rex.reg_startpos[0].lnum < 0) { + rex.reg_startpos[0].lnum = 0; + rex.reg_startpos[0].col = col; + } + if (rex.reg_endpos[0].lnum < 0) { + rex.reg_endpos[0].lnum = rex.lnum; + rex.reg_endpos[0].col = (int)(rex.input - rex.line); + } else { + // Use line number of "\ze". + rex.lnum = rex.reg_endpos[0].lnum; + } + } else { + if (rex.reg_startp[0] == NULL) { + rex.reg_startp[0] = rex.line + col; + } + if (rex.reg_endp[0] == NULL) { + rex.reg_endp[0] = rex.input; + } + } + // Package any found \z(...\) matches for export. Default is none. + unref_extmatch(re_extmatch_out); + re_extmatch_out = NULL; + + if (prog->reghasz == REX_SET) { + int i; + + cleanup_zsubexpr(); + re_extmatch_out = make_extmatch(); + for (i = 0; i < NSUBEXP; i++) { + if (REG_MULTI) { + // Only accept single line matches. + if (reg_startzpos[i].lnum >= 0 + && reg_endzpos[i].lnum == reg_startzpos[i].lnum + && reg_endzpos[i].col >= reg_startzpos[i].col) { + re_extmatch_out->matches[i] = + vim_strnsave(reg_getline(reg_startzpos[i].lnum) + + reg_startzpos[i].col, + reg_endzpos[i].col + - reg_startzpos[i].col); + } + } else { + if (reg_startzp[i] != NULL && reg_endzp[i] != NULL) + re_extmatch_out->matches[i] = + vim_strnsave(reg_startzp[i], reg_endzp[i] - reg_startzp[i]); + } + } + } + return 1 + rex.lnum; +} + +/// Match a regexp against a string ("line" points to the string) or multiple +/// lines (if "line" is NULL, use reg_getline()). +/// @return 0 for failure, or number of lines contained in the match. +static long bt_regexec_both(char_u *line, + colnr_T col, // column to start search + proftime_T *tm, // timeout limit or NULL + int *timed_out) // flag set on timeout or NULL +{ + bt_regprog_T *prog; + char_u *s; + long retval = 0L; + + // Create "regstack" and "backpos" if they are not allocated yet. + // We allocate *_INITIAL amount of bytes first and then set the grow size + // to much bigger value to avoid many malloc calls in case of deep regular + // expressions. + if (regstack.ga_data == NULL) { + // Use an item size of 1 byte, since we push different things + // onto the regstack. + ga_init(®stack, 1, REGSTACK_INITIAL); + ga_grow(®stack, REGSTACK_INITIAL); + ga_set_growsize(®stack, REGSTACK_INITIAL * 8); + } + + if (backpos.ga_data == NULL) { + ga_init(&backpos, sizeof(backpos_T), BACKPOS_INITIAL); + ga_grow(&backpos, BACKPOS_INITIAL); + ga_set_growsize(&backpos, BACKPOS_INITIAL * 8); + } + + if (REG_MULTI) { + prog = (bt_regprog_T *)rex.reg_mmatch->regprog; + line = reg_getline((linenr_T)0); + rex.reg_startpos = rex.reg_mmatch->startpos; + rex.reg_endpos = rex.reg_mmatch->endpos; + } else { + prog = (bt_regprog_T *)rex.reg_match->regprog; + rex.reg_startp = rex.reg_match->startp; + rex.reg_endp = rex.reg_match->endp; + } + + // Be paranoid... + if (prog == NULL || line == NULL) { + iemsg(_(e_null)); + goto theend; + } + + // Check validity of program. + if (prog_magic_wrong()) + goto theend; + + // If the start column is past the maximum column: no need to try. + if (rex.reg_maxcol > 0 && col >= rex.reg_maxcol) { + goto theend; + } + + // If pattern contains "\c" or "\C": overrule value of rex.reg_ic + if (prog->regflags & RF_ICASE) { + rex.reg_ic = true; + } else if (prog->regflags & RF_NOICASE) { + rex.reg_ic = false; + } + + // If pattern contains "\Z" overrule value of rex.reg_icombine + if (prog->regflags & RF_ICOMBINE) { + rex.reg_icombine = true; + } + + // If there is a "must appear" string, look for it. + if (prog->regmust != NULL) { + int c = utf_ptr2char(prog->regmust); + s = line + col; + + // This is used very often, esp. for ":global". Use two versions of + // the loop to avoid overhead of conditions. + if (!rex.reg_ic) { + while ((s = vim_strchr(s, c)) != NULL) { + if (cstrncmp(s, prog->regmust, &prog->regmlen) == 0) { + break; // Found it. + } + MB_PTR_ADV(s); + } + } else { + while ((s = cstrchr(s, c)) != NULL) { + if (cstrncmp(s, prog->regmust, &prog->regmlen) == 0) { + break; // Found it. + } + MB_PTR_ADV(s); + } + } + if (s == NULL) { // Not present. + goto theend; + } + } + + rex.line = line; + rex.lnum = 0; + reg_toolong = false; + + // Simplest case: Anchored match need be tried only once. + if (prog->reganch) { + int c = utf_ptr2char(rex.line + col); + if (prog->regstart == NUL + || prog->regstart == c + || (rex.reg_ic + && (utf_fold(prog->regstart) == utf_fold(c) + || (c < 255 && prog->regstart < 255 + && mb_tolower(prog->regstart) == mb_tolower(c))))) { + retval = regtry(prog, col, tm, timed_out); + } else { + retval = 0; + } + } else { + int tm_count = 0; + // Messy cases: unanchored match. + while (!got_int) { + if (prog->regstart != NUL) { + // Skip until the char we know it must start with. + s = cstrchr(rex.line + col, prog->regstart); + if (s == NULL) { + retval = 0; + break; + } + col = (int)(s - rex.line); + } + + // Check for maximum column to try. + if (rex.reg_maxcol > 0 && col >= rex.reg_maxcol) { + retval = 0; + break; + } + + retval = regtry(prog, col, tm, timed_out); + if (retval > 0) { + break; + } + + // if not currently on the first line, get it again + if (rex.lnum != 0) { + rex.lnum = 0; + rex.line = reg_getline((linenr_T)0); + } + if (rex.line[col] == NUL) { + break; + } + col += utfc_ptr2len(rex.line + col); + // Check for timeout once in a twenty times to avoid overhead. + if (tm != NULL && ++tm_count == 20) { + tm_count = 0; + if (profile_passed_limit(*tm)) { + if (timed_out != NULL) { + *timed_out = true; + } + break; + } + } + } + } + +theend: + // Free "reg_tofree" when it's a bit big. + // Free regstack and backpos if they are bigger than their initial size. + if (reg_tofreelen > 400) { + XFREE_CLEAR(reg_tofree); + } + if (regstack.ga_maxlen > REGSTACK_INITIAL) + ga_clear(®stack); + if (backpos.ga_maxlen > BACKPOS_INITIAL) + ga_clear(&backpos); + + if (retval > 0) { + // Make sure the end is never before the start. Can happen when \zs + // and \ze are used. + if (REG_MULTI) { + const lpos_T *const start = &rex.reg_mmatch->startpos[0]; + const lpos_T *const end = &rex.reg_mmatch->endpos[0]; + + if (end->lnum < start->lnum + || (end->lnum == start->lnum && end->col < start->col)) { + rex.reg_mmatch->endpos[0] = rex.reg_mmatch->startpos[0]; + } + } else { + if (rex.reg_match->endp[0] < rex.reg_match->startp[0]) { + rex.reg_match->endp[0] = rex.reg_match->startp[0]; + } + } + } + + return retval; +} + +/* + * Match a regexp against a string. + * "rmp->regprog" is a compiled regexp as returned by vim_regcomp(). + * Uses curbuf for line count and 'iskeyword'. + * If "line_lbr" is true, consider a "\n" in "line" to be a line break. + * + * Returns 0 for failure, number of lines contained in the match otherwise. + */ +static int bt_regexec_nl( + regmatch_T *rmp, + char_u *line, // string to match against + colnr_T col, // column to start looking for match + bool line_lbr) +{ + rex.reg_match = rmp; + rex.reg_mmatch = NULL; + rex.reg_maxline = 0; + rex.reg_line_lbr = line_lbr; + rex.reg_buf = curbuf; + rex.reg_win = NULL; + rex.reg_ic = rmp->rm_ic; + rex.reg_icombine = false; + rex.reg_maxcol = 0; + + long r = bt_regexec_both(line, col, NULL, NULL); + assert(r <= INT_MAX); + return (int)r; +} + + +/// Matches a regexp against multiple lines. +/// "rmp->regprog" is a compiled regexp as returned by vim_regcomp(). +/// Uses curbuf for line count and 'iskeyword'. +/// +/// @param win Window in which to search or NULL +/// @param buf Buffer in which to search +/// @param lnum Number of line to start looking for match +/// @param col Column to start looking for match +/// @param tm Timeout limit or NULL +/// +/// @return zero if there is no match and number of lines contained in the match +/// otherwise. +static long bt_regexec_multi(regmmatch_T *rmp, win_T *win, buf_T *buf, + linenr_T lnum, colnr_T col, + proftime_T *tm, int *timed_out) +{ + rex.reg_match = NULL; + rex.reg_mmatch = rmp; + rex.reg_buf = buf; + rex.reg_win = win; + rex.reg_firstlnum = lnum; + rex.reg_maxline = rex.reg_buf->b_ml.ml_line_count - lnum; + rex.reg_line_lbr = false; + rex.reg_ic = rmp->rmm_ic; + rex.reg_icombine = false; + rex.reg_maxcol = rmp->rmm_maxcol; + + return bt_regexec_both(NULL, col, tm, timed_out); +} + +/* + * Compare a number with the operand of RE_LNUM, RE_COL or RE_VCOL. + */ +static int re_num_cmp(uint32_t val, char_u *scan) +{ + uint32_t n = (uint32_t)OPERAND_MIN(scan); + + if (OPERAND_CMP(scan) == '>') + return val > n; + if (OPERAND_CMP(scan) == '<') + return val < n; + return val == n; +} + +#ifdef BT_REGEXP_DUMP + +/* + * regdump - dump a regexp onto stdout in vaguely comprehensible form + */ +static void regdump(char_u *pattern, bt_regprog_T *r) +{ + char_u *s; + int op = EXACTLY; // Arbitrary non-END op. + char_u *next; + char_u *end = NULL; + FILE *f; + +#ifdef BT_REGEXP_LOG + f = fopen("bt_regexp_log.log", "a"); +#else + f = stdout; +#endif + if (f == NULL) + return; + fprintf(f, "-------------------------------------\n\r\nregcomp(%s):\r\n", + pattern); + + s = r->program + 1; + // Loop until we find the END that isn't before a referred next (an END + // can also appear in a NOMATCH operand). + while (op != END || s <= end) { + op = OP(s); + fprintf(f, "%2d%s", (int)(s - r->program), regprop(s)); // Where, what. + next = regnext(s); + if (next == NULL) // Next ptr. + fprintf(f, "(0)"); + else + fprintf(f, "(%d)", (int)((s - r->program) + (next - s))); + if (end < next) + end = next; + if (op == BRACE_LIMITS) { + // Two ints + fprintf(f, " minval %" PRId64 ", maxval %" PRId64, + (int64_t)OPERAND_MIN(s), (int64_t)OPERAND_MAX(s)); + s += 8; + } else if (op == BEHIND || op == NOBEHIND) { + // one int + fprintf(f, " count %" PRId64, (int64_t)OPERAND_MIN(s)); + s += 4; + } else if (op == RE_LNUM || op == RE_COL || op == RE_VCOL) { + // one int plus comparator + fprintf(f, " count %" PRId64, (int64_t)OPERAND_MIN(s)); + s += 5; + } + s += 3; + if (op == ANYOF || op == ANYOF + ADD_NL + || op == ANYBUT || op == ANYBUT + ADD_NL + || op == EXACTLY) { + // Literal string, where present. + fprintf(f, "\nxxxxxxxxx\n"); + while (*s != NUL) + fprintf(f, "%c", *s++); + fprintf(f, "\nxxxxxxxxx\n"); + s++; + } + fprintf(f, "\r\n"); + } + + // Header fields of interest. + if (r->regstart != NUL) + fprintf(f, "start `%s' 0x%x; ", r->regstart < 256 + ? (char *)transchar(r->regstart) + : "multibyte", r->regstart); + if (r->reganch) + fprintf(f, "anchored; "); + if (r->regmust != NULL) + fprintf(f, "must have \"%s\"", r->regmust); + fprintf(f, "\r\n"); + +#ifdef BT_REGEXP_LOG + fclose(f); +#endif +} +#endif // BT_REGEXP_DUMP + +#ifdef REGEXP_DEBUG + +/* + * regprop - printable representation of opcode + */ +static char_u *regprop(char_u *op) +{ + char *p; + static char buf[50]; + + STRCPY(buf, ":"); + + switch ((int)OP(op)) { + case BOL: + p = "BOL"; + break; + case EOL: + p = "EOL"; + break; + case RE_BOF: + p = "BOF"; + break; + case RE_EOF: + p = "EOF"; + break; + case CURSOR: + p = "CURSOR"; + break; + case RE_VISUAL: + p = "RE_VISUAL"; + break; + case RE_LNUM: + p = "RE_LNUM"; + break; + case RE_MARK: + p = "RE_MARK"; + break; + case RE_COL: + p = "RE_COL"; + break; + case RE_VCOL: + p = "RE_VCOL"; + break; + case BOW: + p = "BOW"; + break; + case EOW: + p = "EOW"; + break; + case ANY: + p = "ANY"; + break; + case ANY + ADD_NL: + p = "ANY+NL"; + break; + case ANYOF: + p = "ANYOF"; + break; + case ANYOF + ADD_NL: + p = "ANYOF+NL"; + break; + case ANYBUT: + p = "ANYBUT"; + break; + case ANYBUT + ADD_NL: + p = "ANYBUT+NL"; + break; + case IDENT: + p = "IDENT"; + break; + case IDENT + ADD_NL: + p = "IDENT+NL"; + break; + case SIDENT: + p = "SIDENT"; + break; + case SIDENT + ADD_NL: + p = "SIDENT+NL"; + break; + case KWORD: + p = "KWORD"; + break; + case KWORD + ADD_NL: + p = "KWORD+NL"; + break; + case SKWORD: + p = "SKWORD"; + break; + case SKWORD + ADD_NL: + p = "SKWORD+NL"; + break; + case FNAME: + p = "FNAME"; + break; + case FNAME + ADD_NL: + p = "FNAME+NL"; + break; + case SFNAME: + p = "SFNAME"; + break; + case SFNAME + ADD_NL: + p = "SFNAME+NL"; + break; + case PRINT: + p = "PRINT"; + break; + case PRINT + ADD_NL: + p = "PRINT+NL"; + break; + case SPRINT: + p = "SPRINT"; + break; + case SPRINT + ADD_NL: + p = "SPRINT+NL"; + break; + case WHITE: + p = "WHITE"; + break; + case WHITE + ADD_NL: + p = "WHITE+NL"; + break; + case NWHITE: + p = "NWHITE"; + break; + case NWHITE + ADD_NL: + p = "NWHITE+NL"; + break; + case DIGIT: + p = "DIGIT"; + break; + case DIGIT + ADD_NL: + p = "DIGIT+NL"; + break; + case NDIGIT: + p = "NDIGIT"; + break; + case NDIGIT + ADD_NL: + p = "NDIGIT+NL"; + break; + case HEX: + p = "HEX"; + break; + case HEX + ADD_NL: + p = "HEX+NL"; + break; + case NHEX: + p = "NHEX"; + break; + case NHEX + ADD_NL: + p = "NHEX+NL"; + break; + case OCTAL: + p = "OCTAL"; + break; + case OCTAL + ADD_NL: + p = "OCTAL+NL"; + break; + case NOCTAL: + p = "NOCTAL"; + break; + case NOCTAL + ADD_NL: + p = "NOCTAL+NL"; + break; + case WORD: + p = "WORD"; + break; + case WORD + ADD_NL: + p = "WORD+NL"; + break; + case NWORD: + p = "NWORD"; + break; + case NWORD + ADD_NL: + p = "NWORD+NL"; + break; + case HEAD: + p = "HEAD"; + break; + case HEAD + ADD_NL: + p = "HEAD+NL"; + break; + case NHEAD: + p = "NHEAD"; + break; + case NHEAD + ADD_NL: + p = "NHEAD+NL"; + break; + case ALPHA: + p = "ALPHA"; + break; + case ALPHA + ADD_NL: + p = "ALPHA+NL"; + break; + case NALPHA: + p = "NALPHA"; + break; + case NALPHA + ADD_NL: + p = "NALPHA+NL"; + break; + case LOWER: + p = "LOWER"; + break; + case LOWER + ADD_NL: + p = "LOWER+NL"; + break; + case NLOWER: + p = "NLOWER"; + break; + case NLOWER + ADD_NL: + p = "NLOWER+NL"; + break; + case UPPER: + p = "UPPER"; + break; + case UPPER + ADD_NL: + p = "UPPER+NL"; + break; + case NUPPER: + p = "NUPPER"; + break; + case NUPPER + ADD_NL: + p = "NUPPER+NL"; + break; + case BRANCH: + p = "BRANCH"; + break; + case EXACTLY: + p = "EXACTLY"; + break; + case NOTHING: + p = "NOTHING"; + break; + case BACK: + p = "BACK"; + break; + case END: + p = "END"; + break; + case MOPEN + 0: + p = "MATCH START"; + break; + case MOPEN + 1: + case MOPEN + 2: + case MOPEN + 3: + case MOPEN + 4: + case MOPEN + 5: + case MOPEN + 6: + case MOPEN + 7: + case MOPEN + 8: + case MOPEN + 9: + sprintf(buf + STRLEN(buf), "MOPEN%d", OP(op) - MOPEN); + p = NULL; + break; + case MCLOSE + 0: + p = "MATCH END"; + break; + case MCLOSE + 1: + case MCLOSE + 2: + case MCLOSE + 3: + case MCLOSE + 4: + case MCLOSE + 5: + case MCLOSE + 6: + case MCLOSE + 7: + case MCLOSE + 8: + case MCLOSE + 9: + sprintf(buf + STRLEN(buf), "MCLOSE%d", OP(op) - MCLOSE); + p = NULL; + break; + case BACKREF + 1: + case BACKREF + 2: + case BACKREF + 3: + case BACKREF + 4: + case BACKREF + 5: + case BACKREF + 6: + case BACKREF + 7: + case BACKREF + 8: + case BACKREF + 9: + sprintf(buf + STRLEN(buf), "BACKREF%d", OP(op) - BACKREF); + p = NULL; + break; + case NOPEN: + p = "NOPEN"; + break; + case NCLOSE: + p = "NCLOSE"; + break; + case ZOPEN + 1: + case ZOPEN + 2: + case ZOPEN + 3: + case ZOPEN + 4: + case ZOPEN + 5: + case ZOPEN + 6: + case ZOPEN + 7: + case ZOPEN + 8: + case ZOPEN + 9: + sprintf(buf + STRLEN(buf), "ZOPEN%d", OP(op) - ZOPEN); + p = NULL; + break; + case ZCLOSE + 1: + case ZCLOSE + 2: + case ZCLOSE + 3: + case ZCLOSE + 4: + case ZCLOSE + 5: + case ZCLOSE + 6: + case ZCLOSE + 7: + case ZCLOSE + 8: + case ZCLOSE + 9: + sprintf(buf + STRLEN(buf), "ZCLOSE%d", OP(op) - ZCLOSE); + p = NULL; + break; + case ZREF + 1: + case ZREF + 2: + case ZREF + 3: + case ZREF + 4: + case ZREF + 5: + case ZREF + 6: + case ZREF + 7: + case ZREF + 8: + case ZREF + 9: + sprintf(buf + STRLEN(buf), "ZREF%d", OP(op) - ZREF); + p = NULL; + break; + case STAR: + p = "STAR"; + break; + case PLUS: + p = "PLUS"; + break; + case NOMATCH: + p = "NOMATCH"; + break; + case MATCH: + p = "MATCH"; + break; + case BEHIND: + p = "BEHIND"; + break; + case NOBEHIND: + p = "NOBEHIND"; + break; + case SUBPAT: + p = "SUBPAT"; + break; + case BRACE_LIMITS: + p = "BRACE_LIMITS"; + break; + case BRACE_SIMPLE: + p = "BRACE_SIMPLE"; + break; + case BRACE_COMPLEX + 0: + case BRACE_COMPLEX + 1: + case BRACE_COMPLEX + 2: + case BRACE_COMPLEX + 3: + case BRACE_COMPLEX + 4: + case BRACE_COMPLEX + 5: + case BRACE_COMPLEX + 6: + case BRACE_COMPLEX + 7: + case BRACE_COMPLEX + 8: + case BRACE_COMPLEX + 9: + sprintf(buf + STRLEN(buf), "BRACE_COMPLEX%d", OP(op) - BRACE_COMPLEX); + p = NULL; + break; + case MULTIBYTECODE: + p = "MULTIBYTECODE"; + break; + case NEWL: + p = "NEWL"; + break; + default: + sprintf(buf + STRLEN(buf), "corrupt %d", OP(op)); + p = NULL; + break; + } + if (p != NULL) + STRCAT(buf, p); + return (char_u *)buf; +} +#endif // REGEXP_DEBUG diff --git a/src/nvim/regexp_defs.h b/src/nvim/regexp_defs.h index 1d112bd64a..913cfb2074 100644 --- a/src/nvim/regexp_defs.h +++ b/src/nvim/regexp_defs.h @@ -34,7 +34,7 @@ // In the NFA engine: how many states are allowed. #define NFA_MAX_STATES 100000 -#define NFA_TOO_EXPENSIVE -1 +#define NFA_TOO_EXPENSIVE (-1) // Which regexp engine to use? Needed for vim_regcomp(). // Must match with 'regexpengine'. diff --git a/src/nvim/regexp_nfa.c b/src/nvim/regexp_nfa.c index 5df5cc5975..a8d6e9c40b 100644 --- a/src/nvim/regexp_nfa.c +++ b/src/nvim/regexp_nfa.c @@ -16,23 +16,22 @@ #include "nvim/ascii.h" #include "nvim/garray.h" - -/* - * Logging of NFA engine. - * - * The NFA engine can write four log files: - * - Error log: Contains NFA engine's fatal errors. - * - Dump log: Contains compiled NFA state machine's information. - * - Run log: Contains information of matching procedure. - * - Debug log: Contains detailed information of matching procedure. Can be - * disabled by undefining NFA_REGEXP_DEBUG_LOG. - * The first one can also be used without debug mode. - * The last three are enabled when compiled as debug mode and individually - * disabled by commenting them out. - * The log files can get quite big! - * Do disable all of this when compiling Vim for debugging, undefine REGEXP_DEBUG in - * regexp.c - */ +#include "nvim/os/input.h" + +// Logging of NFA engine. +// +// The NFA engine can write four log files: +// - Error log: Contains NFA engine's fatal errors. +// - Dump log: Contains compiled NFA state machine's information. +// - Run log: Contains information of matching procedure. +// - Debug log: Contains detailed information of matching procedure. Can be +// disabled by undefining NFA_REGEXP_DEBUG_LOG. +// The first one can also be used without debug mode. +// The last three are enabled when compiled as debug mode and individually +// disabled by commenting them out. +// The log files can get quite big! +// To disable all of this when compiling Vim for debugging, undefine REGEXP_DEBUG in +// regexp.c #ifdef REGEXP_DEBUG # define NFA_REGEXP_ERROR_LOG "nfa_regexp_error.log" # define NFA_REGEXP_DUMP_LOG "nfa_regexp_dump.log" @@ -712,7 +711,6 @@ static int nfa_recognize_char_class(char_u *start, char_u *end, int extra_newl) static void nfa_emit_equi_class(int c) { #define EMIT2(c) EMIT(c); EMIT(NFA_CONCAT); -#define EMITMBC(c) EMIT(c); EMIT(NFA_CONCAT); { #define A_grave 0xc0 @@ -772,361 +770,518 @@ static void nfa_emit_equi_class(int c) #define y_diaeresis 0xff switch (c) { case 'A': case A_grave: case A_acute: case A_circumflex: - case A_virguilla: case A_diaeresis: case A_ring: - CASEMBC(0x100) CASEMBC(0x102) CASEMBC(0x104) - CASEMBC(0x1cd) CASEMBC(0x1de) CASEMBC(0x1e0) - CASEMBC(0x1ea2) - EMIT2('A'); EMIT2(A_grave); EMIT2(A_acute); - EMIT2(A_circumflex); EMIT2(A_virguilla); - EMIT2(A_diaeresis); EMIT2(A_ring); - EMITMBC(0x100) EMITMBC(0x102) EMITMBC(0x104) - EMITMBC(0x1cd) EMITMBC(0x1de) EMITMBC(0x1e0) - EMITMBC(0x1ea2) + case A_virguilla: case A_diaeresis: case A_ring: + case 0x100: case 0x102: case 0x104: case 0x1cd: + case 0x1de: case 0x1e0: case 0x1fa: case 0x200: + case 0x202: case 0x226: case 0x23a: case 0x1e00: + case 0x1ea0: case 0x1ea2: case 0x1ea4: case 0x1ea6: + case 0x1ea8: case 0x1eaa: case 0x1eac: case 0x1eae: + case 0x1eb0: case 0x1eb2: case 0x1eb4: case 0x1eb6: + EMIT2('A') EMIT2(A_grave) EMIT2(A_acute) + EMIT2(A_circumflex) EMIT2(A_virguilla) + EMIT2(A_diaeresis) EMIT2(A_ring) + EMIT2(0x100) EMIT2(0x102) EMIT2(0x104) + EMIT2(0x1cd) EMIT2(0x1de) EMIT2(0x1e0) + EMIT2(0x1fa) EMIT2(0x200) EMIT2(0x202) + EMIT2(0x226) EMIT2(0x23a) EMIT2(0x1e00) + EMIT2(0x1ea0) EMIT2(0x1ea2) EMIT2(0x1ea4) + EMIT2(0x1ea6) EMIT2(0x1ea8) EMIT2(0x1eaa) + EMIT2(0x1eac) EMIT2(0x1eae) EMIT2(0x1eb0) + EMIT2(0x1eb2) EMIT2(0x1eb6) EMIT2(0x1eb4) return; - case 'B': CASEMBC(0x1e02) CASEMBC(0x1e06) - EMIT2('B'); EMITMBC(0x1e02) EMITMBC(0x1e06) + case 'B': case 0x181: case 0x243: case 0x1e02: + case 0x1e04: case 0x1e06: + EMIT2('B') + EMIT2(0x181) EMIT2(0x243) EMIT2(0x1e02) + EMIT2(0x1e04) EMIT2(0x1e06) return; - case 'C': case C_cedilla: CASEMBC(0x106) CASEMBC(0x108) CASEMBC(0x10a) - CASEMBC(0x10c) - EMIT2('C'); EMIT2(C_cedilla); EMITMBC(0x106) EMITMBC(0x108) - EMITMBC(0x10a) EMITMBC(0x10c) + case 'C': case C_cedilla: case 0x106: case 0x108: + case 0x10a: case 0x10c: case 0x187: case 0x23b: + case 0x1e08: case 0xa792: + EMIT2('C') EMIT2(C_cedilla) + EMIT2(0x106) EMIT2(0x108) EMIT2(0x10a) + EMIT2(0x10c) EMIT2(0x187) EMIT2(0x23b) + EMIT2(0x1e08) EMIT2(0xa792) return; - case 'D': CASEMBC(0x10e) CASEMBC(0x110) CASEMBC(0x1e0a) - CASEMBC(0x1e0e) CASEMBC(0x1e10) - EMIT2('D'); EMITMBC(0x10e) EMITMBC(0x110) EMITMBC(0x1e0a) - EMITMBC(0x1e0e) EMITMBC(0x1e10) + case 'D': case 0x10e: case 0x110: case 0x18a: + case 0x1e0a: case 0x1e0c: case 0x1e0e: case 0x1e10: + case 0x1e12: + EMIT2('D') EMIT2(0x10e) EMIT2(0x110) EMIT2(0x18a) + EMIT2(0x1e0a) EMIT2(0x1e0c) EMIT2(0x1e0e) + EMIT2(0x1e10) EMIT2(0x1e12) return; case 'E': case E_grave: case E_acute: case E_circumflex: - case E_diaeresis: CASEMBC(0x112) CASEMBC(0x114) - CASEMBC(0x116) CASEMBC(0x118) CASEMBC(0x11a) - CASEMBC(0x1eba) CASEMBC(0x1ebc) - EMIT2('E'); EMIT2(E_grave); EMIT2(E_acute); - EMIT2(E_circumflex); EMIT2(E_diaeresis); - EMITMBC(0x112) EMITMBC(0x114) EMITMBC(0x116) - EMITMBC(0x118) EMITMBC(0x11a) EMITMBC(0x1eba) - EMITMBC(0x1ebc) + case E_diaeresis: case 0x112: case 0x114: case 0x116: + case 0x118: case 0x11a: case 0x204: case 0x206: + case 0x228: case 0x246: case 0x1e14: case 0x1e16: + case 0x1e18: case 0x1e1a: case 0x1e1c: case 0x1eb8: + case 0x1eba: case 0x1ebc: case 0x1ebe: case 0x1ec0: + case 0x1ec2: case 0x1ec4: case 0x1ec6: + EMIT2('E') EMIT2(E_grave) EMIT2(E_acute) + EMIT2(E_circumflex) EMIT2(E_diaeresis) + EMIT2(0x112) EMIT2(0x114) EMIT2(0x116) + EMIT2(0x118) EMIT2(0x11a) EMIT2(0x204) + EMIT2(0x206) EMIT2(0x228) EMIT2(0x246) + EMIT2(0x1e14) EMIT2(0x1e16) EMIT2(0x1e18) + EMIT2(0x1e1a) EMIT2(0x1e1c) EMIT2(0x1eb8) + EMIT2(0x1eba) EMIT2(0x1ebc) EMIT2(0x1ebe) + EMIT2(0x1ec0) EMIT2(0x1ec2) EMIT2(0x1ec4) + EMIT2(0x1ec6) return; - case 'F': CASEMBC(0x1e1e) - EMIT2('F'); EMITMBC(0x1e1e) + case 'F': case 0x191: case 0x1e1e: case 0xa798: + EMIT2('F') EMIT2(0x191) EMIT2(0x1e1e) EMIT2(0xa798) return; - case 'G': CASEMBC(0x11c) CASEMBC(0x11e) CASEMBC(0x120) - CASEMBC(0x122) CASEMBC(0x1e4) CASEMBC(0x1e6) - CASEMBC(0x1f4) CASEMBC(0x1e20) - EMIT2('G'); EMITMBC(0x11c) EMITMBC(0x11e) EMITMBC(0x120) - EMITMBC(0x122) EMITMBC(0x1e4) EMITMBC(0x1e6) - EMITMBC(0x1f4) EMITMBC(0x1e20) + case 'G': case 0x11c: case 0x11e: case 0x120: + case 0x122: case 0x193: case 0x1e4: case 0x1e6: + case 0x1f4: case 0x1e20: case 0xa7a0: + EMIT2('G') EMIT2(0x11c) EMIT2(0x11e) EMIT2(0x120) + EMIT2(0x122) EMIT2(0x193) EMIT2(0x1e4) + EMIT2(0x1e6) EMIT2(0x1f4) EMIT2(0x1e20) + EMIT2(0xa7a0) return; - case 'H': CASEMBC(0x124) CASEMBC(0x126) CASEMBC(0x1e22) - CASEMBC(0x1e26) CASEMBC(0x1e28) - EMIT2('H'); EMITMBC(0x124) EMITMBC(0x126) EMITMBC(0x1e22) - EMITMBC(0x1e26) EMITMBC(0x1e28) + case 'H': case 0x124: case 0x126: case 0x21e: + case 0x1e22: case 0x1e24: case 0x1e26: case 0x1e28: + case 0x1e2a: case 0x2c67: + EMIT2('H') EMIT2(0x124) EMIT2(0x126) EMIT2(0x21e) + EMIT2(0x1e22) EMIT2(0x1e24) EMIT2(0x1e26) + EMIT2(0x1e28) EMIT2(0x1e2a) EMIT2(0x2c67) return; case 'I': case I_grave: case I_acute: case I_circumflex: - case I_diaeresis: CASEMBC(0x128) CASEMBC(0x12a) - CASEMBC(0x12c) CASEMBC(0x12e) CASEMBC(0x130) - CASEMBC(0x1cf) CASEMBC(0x1ec8) - EMIT2('I'); EMIT2(I_grave); EMIT2(I_acute); - EMIT2(I_circumflex); EMIT2(I_diaeresis); - EMITMBC(0x128) EMITMBC(0x12a) - EMITMBC(0x12c) EMITMBC(0x12e) EMITMBC(0x130) - EMITMBC(0x1cf) EMITMBC(0x1ec8) + case I_diaeresis: case 0x128: case 0x12a: case 0x12c: + case 0x12e: case 0x130: case 0x197: case 0x1cf: + case 0x208: case 0x20a: case 0x1e2c: case 0x1e2e: + case 0x1ec8: case 0x1eca: + EMIT2('I') EMIT2(I_grave) EMIT2(I_acute) + EMIT2(I_circumflex) EMIT2(I_diaeresis) + EMIT2(0x128) EMIT2(0x12a) EMIT2(0x12c) + EMIT2(0x12e) EMIT2(0x130) EMIT2(0x197) + EMIT2(0x1cf) EMIT2(0x208) EMIT2(0x20a) + EMIT2(0x1e2c) EMIT2(0x1e2e) EMIT2(0x1ec8) + EMIT2(0x1eca) return; - case 'J': CASEMBC(0x134) - EMIT2('J'); EMITMBC(0x134) + case 'J': case 0x134: case 0x248: + EMIT2('J') EMIT2(0x134) EMIT2(0x248) return; - case 'K': CASEMBC(0x136) CASEMBC(0x1e8) CASEMBC(0x1e30) - CASEMBC(0x1e34) - EMIT2('K'); EMITMBC(0x136) EMITMBC(0x1e8) EMITMBC(0x1e30) - EMITMBC(0x1e34) + case 'K': case 0x136: case 0x198: case 0x1e8: case 0x1e30: + case 0x1e32: case 0x1e34: case 0x2c69: case 0xa740: + EMIT2('K') EMIT2(0x136) EMIT2(0x198) EMIT2(0x1e8) + EMIT2(0x1e30) EMIT2(0x1e32) EMIT2(0x1e34) + EMIT2(0x2c69) EMIT2(0xa740) return; - case 'L': CASEMBC(0x139) CASEMBC(0x13b) CASEMBC(0x13d) - CASEMBC(0x13f) CASEMBC(0x141) CASEMBC(0x1e3a) - EMIT2('L'); EMITMBC(0x139) EMITMBC(0x13b) EMITMBC(0x13d) - EMITMBC(0x13f) EMITMBC(0x141) EMITMBC(0x1e3a) + case 'L': case 0x139: case 0x13b: case 0x13d: + case 0x13f: case 0x141: case 0x23d: case 0x1e36: + case 0x1e38: case 0x1e3a: case 0x1e3c: case 0x2c60: + EMIT2('L') EMIT2(0x139) EMIT2(0x13b) + EMIT2(0x13d) EMIT2(0x13f) EMIT2(0x141) + EMIT2(0x23d) EMIT2(0x1e36) EMIT2(0x1e38) + EMIT2(0x1e3a) EMIT2(0x1e3c) EMIT2(0x2c60) return; - case 'M': CASEMBC(0x1e3e) CASEMBC(0x1e40) - EMIT2('M'); EMITMBC(0x1e3e) EMITMBC(0x1e40) + case 'M': case 0x1e3e: case 0x1e40: case 0x1e42: + EMIT2('M') EMIT2(0x1e3e) EMIT2(0x1e40) + EMIT2(0x1e42) return; - case 'N': case N_virguilla: CASEMBC(0x143) CASEMBC(0x145) - CASEMBC(0x147) CASEMBC(0x1e44) CASEMBC(0x1e48) - EMIT2('N'); EMIT2(N_virguilla); - EMITMBC(0x143) EMITMBC(0x145) - EMITMBC(0x147) EMITMBC(0x1e44) EMITMBC(0x1e48) + case 'N': case N_virguilla: + case 0x143: case 0x145: case 0x147: case 0x1f8: + case 0x1e44: case 0x1e46: case 0x1e48: case 0x1e4a: + case 0xa7a4: + EMIT2('N') EMIT2(N_virguilla) + EMIT2(0x143) EMIT2(0x145) EMIT2(0x147) + EMIT2(0x1f8) EMIT2(0x1e44) EMIT2(0x1e46) + EMIT2(0x1e48) EMIT2(0x1e4a) EMIT2(0xa7a4) return; case 'O': case O_grave: case O_acute: case O_circumflex: - case O_virguilla: case O_diaeresis: case O_slash: - CASEMBC(0x14c) CASEMBC(0x14e) CASEMBC(0x150) - CASEMBC(0x1a0) CASEMBC(0x1d1) CASEMBC(0x1ea) - CASEMBC(0x1ec) CASEMBC(0x1ece) - EMIT2('O'); EMIT2(O_grave); EMIT2(O_acute); - EMIT2(O_circumflex); EMIT2(O_virguilla); - EMIT2(O_diaeresis); EMIT2(O_slash); - EMITMBC(0x14c) EMITMBC(0x14e) EMITMBC(0x150) - EMITMBC(0x1a0) EMITMBC(0x1d1) EMITMBC(0x1ea) - EMITMBC(0x1ec) EMITMBC(0x1ece) + case O_virguilla: case O_diaeresis: case O_slash: + case 0x14c: case 0x14e: case 0x150: case 0x19f: + case 0x1a0: case 0x1d1: case 0x1ea: case 0x1ec: + case 0x1fe: case 0x20c: case 0x20e: case 0x22a: + case 0x22c: case 0x22e: case 0x230: case 0x1e4c: + case 0x1e4e: case 0x1e50: case 0x1e52: case 0x1ecc: + case 0x1ece: case 0x1ed0: case 0x1ed2: case 0x1ed4: + case 0x1ed6: case 0x1ed8: case 0x1eda: case 0x1edc: + case 0x1ede: case 0x1ee0: case 0x1ee2: + EMIT2('O') EMIT2(O_grave) EMIT2(O_acute) + EMIT2(O_circumflex) EMIT2(O_virguilla) + EMIT2(O_diaeresis) EMIT2(O_slash) + EMIT2(0x14c) EMIT2(0x14e) EMIT2(0x150) + EMIT2(0x19f) EMIT2(0x1a0) EMIT2(0x1d1) + EMIT2(0x1ea) EMIT2(0x1ec) EMIT2(0x1fe) + EMIT2(0x20c) EMIT2(0x20e) EMIT2(0x22a) + EMIT2(0x22c) EMIT2(0x22e) EMIT2(0x230) + EMIT2(0x1e4c) EMIT2(0x1e4e) EMIT2(0x1e50) + EMIT2(0x1e52) EMIT2(0x1ecc) EMIT2(0x1ece) + EMIT2(0x1ed0) EMIT2(0x1ed2) EMIT2(0x1ed4) + EMIT2(0x1ed6) EMIT2(0x1ed8) EMIT2(0x1eda) + EMIT2(0x1edc) EMIT2(0x1ede) EMIT2(0x1ee0) + EMIT2(0x1ee2) + return; + + case 'P': case 0x1a4: case 0x1e54: case 0x1e56: case 0x2c63: + EMIT2('P') EMIT2(0x1a4) EMIT2(0x1e54) EMIT2(0x1e56) + EMIT2(0x2c63) return; - case 'P': case 0x1e54: case 0x1e56: - EMIT2('P'); EMITMBC(0x1e54) EMITMBC(0x1e56) + case 'Q': case 0x24a: + EMIT2('Q') EMIT2(0x24a) return; - case 'R': CASEMBC(0x154) CASEMBC(0x156) CASEMBC(0x158) - CASEMBC(0x1e58) CASEMBC(0x1e5e) - EMIT2('R'); EMITMBC(0x154) EMITMBC(0x156) EMITMBC(0x158) - EMITMBC(0x1e58) EMITMBC(0x1e5e) + case 'R': case 0x154: case 0x156: case 0x158: case 0x210: + case 0x212: case 0x24c: case 0x1e58: case 0x1e5a: + case 0x1e5c: case 0x1e5e: case 0x2c64: case 0xa7a6: + EMIT2('R') EMIT2(0x154) EMIT2(0x156) EMIT2(0x158) + EMIT2(0x210) EMIT2(0x212) EMIT2(0x24c) EMIT2(0x1e58) + EMIT2(0x1e5a) EMIT2(0x1e5c) EMIT2(0x1e5e) EMIT2(0x2c64) + EMIT2(0xa7a6) return; - case 'S': CASEMBC(0x15a) CASEMBC(0x15c) CASEMBC(0x15e) - CASEMBC(0x160) CASEMBC(0x1e60) - EMIT2('S'); EMITMBC(0x15a) EMITMBC(0x15c) EMITMBC(0x15e) - EMITMBC(0x160) EMITMBC(0x1e60) + case 'S': case 0x15a: case 0x15c: case 0x15e: case 0x160: + case 0x218: case 0x1e60: case 0x1e62: case 0x1e64: + case 0x1e66: case 0x1e68: case 0x2c7e: case 0xa7a8: + EMIT2('S') EMIT2(0x15a) EMIT2(0x15c) EMIT2(0x15e) + EMIT2(0x160) EMIT2(0x218) EMIT2(0x1e60) EMIT2(0x1e62) + EMIT2(0x1e64) EMIT2(0x1e66) EMIT2(0x1e68) EMIT2(0x2c7e) + EMIT2(0xa7a8) return; - case 'T': CASEMBC(0x162) CASEMBC(0x164) CASEMBC(0x166) - CASEMBC(0x1e6a) CASEMBC(0x1e6e) - EMIT2('T'); EMITMBC(0x162) EMITMBC(0x164) EMITMBC(0x166) - EMITMBC(0x1e6a) EMITMBC(0x1e6e) + case 'T': case 0x162: case 0x164: case 0x166: case 0x1ac: + case 0x1ae: case 0x21a: case 0x23e: case 0x1e6a: case 0x1e6c: + case 0x1e6e: case 0x1e70: + EMIT2('T') EMIT2(0x162) EMIT2(0x164) EMIT2(0x166) + EMIT2(0x1ac) EMIT2(0x1ae) EMIT2(0x23e) EMIT2(0x21a) + EMIT2(0x1e6a) EMIT2(0x1e6c) EMIT2(0x1e6e) EMIT2(0x1e70) return; case 'U': case U_grave: case U_acute: case U_diaeresis: - case U_circumflex: CASEMBC(0x168) CASEMBC(0x16a) - CASEMBC(0x16c) CASEMBC(0x16e) CASEMBC(0x170) - CASEMBC(0x172) CASEMBC(0x1af) CASEMBC(0x1d3) - CASEMBC(0x1ee6) - EMIT2('U'); EMIT2(U_grave); EMIT2(U_acute); - EMIT2(U_diaeresis); EMIT2(U_circumflex); - EMITMBC(0x168) EMITMBC(0x16a) - EMITMBC(0x16c) EMITMBC(0x16e) EMITMBC(0x170) - EMITMBC(0x172) EMITMBC(0x1af) EMITMBC(0x1d3) - EMITMBC(0x1ee6) + case U_circumflex: case 0x168: case 0x16a: case 0x16c: + case 0x16e: case 0x170: case 0x172: case 0x1af: + case 0x1d3: case 0x1d5: case 0x1d7: case 0x1d9: + case 0x1db: case 0x214: case 0x216: case 0x244: + case 0x1e72: case 0x1e74: case 0x1e76: case 0x1e78: + case 0x1e7a: case 0x1ee4: case 0x1ee6: case 0x1ee8: + case 0x1eea: case 0x1eec: case 0x1eee: case 0x1ef0: + EMIT2('U') EMIT2(U_grave) EMIT2(U_acute) + EMIT2(U_diaeresis) EMIT2(U_circumflex) + EMIT2(0x168) EMIT2(0x16a) + EMIT2(0x16c) EMIT2(0x16e) EMIT2(0x170) + EMIT2(0x172) EMIT2(0x1af) EMIT2(0x1d3) + EMIT2(0x1d5) EMIT2(0x1d7) EMIT2(0x1d9) + EMIT2(0x1db) EMIT2(0x214) EMIT2(0x216) + EMIT2(0x244) EMIT2(0x1e72) EMIT2(0x1e74) + EMIT2(0x1e76) EMIT2(0x1e78) EMIT2(0x1e7a) + EMIT2(0x1ee4) EMIT2(0x1ee6) EMIT2(0x1ee8) + EMIT2(0x1eea) EMIT2(0x1eec) EMIT2(0x1eee) + EMIT2(0x1ef0) return; - case 'V': CASEMBC(0x1e7c) - EMIT2('V'); EMITMBC(0x1e7c) + case 'V': case 0x1b2: case 0x1e7c: case 0x1e7e: + EMIT2('V') EMIT2(0x1b2) EMIT2(0x1e7c) EMIT2(0x1e7e) return; - case 'W': CASEMBC(0x174) CASEMBC(0x1e80) CASEMBC(0x1e82) - CASEMBC(0x1e84) CASEMBC(0x1e86) - EMIT2('W'); EMITMBC(0x174) EMITMBC(0x1e80) EMITMBC(0x1e82) - EMITMBC(0x1e84) EMITMBC(0x1e86) + case 'W': case 0x174: case 0x1e80: case 0x1e82: case 0x1e84: + case 0x1e86: case 0x1e88: + EMIT2('W') EMIT2(0x174) EMIT2(0x1e80) EMIT2(0x1e82) + EMIT2(0x1e84) EMIT2(0x1e86) EMIT2(0x1e88) return; - case 'X': CASEMBC(0x1e8a) CASEMBC(0x1e8c) - EMIT2('X'); EMITMBC(0x1e8a) EMITMBC(0x1e8c) + case 'X': case 0x1e8a: case 0x1e8c: + EMIT2('X') EMIT2(0x1e8a) EMIT2(0x1e8c) return; - case 'Y': case Y_acute: CASEMBC(0x176) CASEMBC(0x178) - CASEMBC(0x1e8e) CASEMBC(0x1ef2) CASEMBC(0x1ef6) - CASEMBC(0x1ef8) - EMIT2('Y'); EMIT2(Y_acute); - EMITMBC(0x176) EMITMBC(0x178) - EMITMBC(0x1e8e) EMITMBC(0x1ef2) EMITMBC(0x1ef6) - EMITMBC(0x1ef8) + case 'Y': case Y_acute: case 0x176: case 0x178: + case 0x1b3: case 0x232: case 0x24e: case 0x1e8e: + case 0x1ef2: case 0x1ef4: case 0x1ef6: case 0x1ef8: + EMIT2('Y') EMIT2(Y_acute) + EMIT2(0x176) EMIT2(0x178) EMIT2(0x1b3) + EMIT2(0x232) EMIT2(0x24e) EMIT2(0x1e8e) + EMIT2(0x1ef2) EMIT2(0x1ef4) EMIT2(0x1ef6) + EMIT2(0x1ef8) return; - case 'Z': CASEMBC(0x179) CASEMBC(0x17b) CASEMBC(0x17d) - CASEMBC(0x1b5) CASEMBC(0x1e90) CASEMBC(0x1e94) - EMIT2('Z'); EMITMBC(0x179) EMITMBC(0x17b) EMITMBC(0x17d) - EMITMBC(0x1b5) EMITMBC(0x1e90) EMITMBC(0x1e94) + case 'Z': case 0x179: case 0x17b: case 0x17d: + case 0x1b5: case 0x1e90: case 0x1e92: case 0x1e94: + case 0x2c6b: + EMIT2('Z') EMIT2(0x179) EMIT2(0x17b) EMIT2(0x17d) + EMIT2(0x1b5) EMIT2(0x1e90) EMIT2(0x1e92) + EMIT2(0x1e94) EMIT2(0x2c6b) return; case 'a': case a_grave: case a_acute: case a_circumflex: - case a_virguilla: case a_diaeresis: case a_ring: - CASEMBC(0x101) CASEMBC(0x103) CASEMBC(0x105) - CASEMBC(0x1ce) CASEMBC(0x1df) CASEMBC(0x1e1) - CASEMBC(0x1ea3) - EMIT2('a'); EMIT2(a_grave); EMIT2(a_acute); - EMIT2(a_circumflex); EMIT2(a_virguilla); - EMIT2(a_diaeresis); EMIT2(a_ring); - EMITMBC(0x101) EMITMBC(0x103) EMITMBC(0x105) - EMITMBC(0x1ce) EMITMBC(0x1df) EMITMBC(0x1e1) - EMITMBC(0x1ea3) + case a_virguilla: case a_diaeresis: case a_ring: + case 0x101: case 0x103: case 0x105: case 0x1ce: + case 0x1df: case 0x1e1: case 0x1fb: case 0x201: + case 0x203: case 0x227: case 0x1d8f: case 0x1e01: + case 0x1e9a: case 0x1ea1: case 0x1ea3: case 0x1ea5: + case 0x1ea7: case 0x1ea9: case 0x1eab: case 0x1ead: + case 0x1eaf: case 0x1eb1: case 0x1eb3: case 0x1eb5: + case 0x1eb7: case 0x2c65: + EMIT2('a') EMIT2(a_grave) EMIT2(a_acute) + EMIT2(a_circumflex) EMIT2(a_virguilla) + EMIT2(a_diaeresis) EMIT2(a_ring) + EMIT2(0x101) EMIT2(0x103) EMIT2(0x105) + EMIT2(0x1ce) EMIT2(0x1df) EMIT2(0x1e1) + EMIT2(0x1fb) EMIT2(0x201) EMIT2(0x203) + EMIT2(0x227) EMIT2(0x1d8f) EMIT2(0x1e01) + EMIT2(0x1e9a) EMIT2(0x1ea1) EMIT2(0x1ea3) + EMIT2(0x1ea5) EMIT2(0x1ea7) EMIT2(0x1ea9) + EMIT2(0x1eab) EMIT2(0x1ead) EMIT2(0x1eaf) + EMIT2(0x1eb1) EMIT2(0x1eb3) EMIT2(0x1eb5) + EMIT2(0x1eb7) EMIT2(0x2c65) return; - case 'b': CASEMBC(0x1e03) CASEMBC(0x1e07) - EMIT2('b'); EMITMBC(0x1e03) EMITMBC(0x1e07) + case 'b': case 0x180: case 0x253: case 0x1d6c: case 0x1d80: + case 0x1e03: case 0x1e05: case 0x1e07: + EMIT2('b') EMIT2(0x180) EMIT2(0x253) EMIT2(0x1d6c) + EMIT2(0x1d80) EMIT2(0x1e03) EMIT2(0x1e05) EMIT2(0x1e07) return; - case 'c': case c_cedilla: CASEMBC(0x107) CASEMBC(0x109) - CASEMBC(0x10b) CASEMBC(0x10d) - EMIT2('c'); EMIT2(c_cedilla); - EMITMBC(0x107) EMITMBC(0x109) - EMITMBC(0x10b) EMITMBC(0x10d) + case 'c': case c_cedilla: case 0x107: case 0x109: case 0x10b: + case 0x10d: case 0x188: case 0x23c: case 0x1e09: case 0xa793: + case 0xa794: + EMIT2('c') EMIT2(c_cedilla) + EMIT2(0x107) EMIT2(0x109) EMIT2(0x10b) + EMIT2(0x10d) EMIT2(0x188) EMIT2(0x23c) + EMIT2(0x1e09) EMIT2(0xa793) EMIT2(0xa794) return; - case 'd': CASEMBC(0x10f) CASEMBC(0x111) CASEMBC(0x1e0b) - CASEMBC(0x1e0f) CASEMBC(0x1e11) - EMIT2('d'); EMITMBC(0x10f) EMITMBC(0x111) EMITMBC(0x1e0b) - EMITMBC(0x1e0f) EMITMBC(0x1e11) + case 'd': case 0x10f: case 0x111: case 0x257: case 0x1d6d: + case 0x1d81: case 0x1d91: case 0x1e0b: case 0x1e0d: case 0x1e0f: + case 0x1e11: case 0x1e13: + EMIT2('d') EMIT2(0x10f) EMIT2(0x111) + EMIT2(0x257) EMIT2(0x1d6d) EMIT2(0x1d81) + EMIT2(0x1d91) EMIT2(0x1e0b) EMIT2(0x1e0d) + EMIT2(0x1e0f) EMIT2(0x1e11) EMIT2(0x1e13) return; case 'e': case e_grave: case e_acute: case e_circumflex: - case e_diaeresis: CASEMBC(0x113) CASEMBC(0x115) - CASEMBC(0x117) CASEMBC(0x119) CASEMBC(0x11b) - CASEMBC(0x1ebb) CASEMBC(0x1ebd) - EMIT2('e'); EMIT2(e_grave); EMIT2(e_acute); - EMIT2(e_circumflex); EMIT2(e_diaeresis); - EMITMBC(0x113) EMITMBC(0x115) - EMITMBC(0x117) EMITMBC(0x119) EMITMBC(0x11b) - EMITMBC(0x1ebb) EMITMBC(0x1ebd) + case e_diaeresis: case 0x113: case 0x115: case 0x117: + case 0x119: case 0x11b: case 0x205: case 0x207: + case 0x229: case 0x247: case 0x1d92: case 0x1e15: + case 0x1e17: case 0x1e19: case 0x1e1b: case 0x1e1d: + case 0x1eb9: case 0x1ebb: case 0x1ebd: case 0x1ebf: + case 0x1ec1: case 0x1ec3: case 0x1ec5: case 0x1ec7: + EMIT2('e') EMIT2(e_grave) EMIT2(e_acute) + EMIT2(e_circumflex) EMIT2(e_diaeresis) + EMIT2(0x113) EMIT2(0x115) + EMIT2(0x117) EMIT2(0x119) EMIT2(0x11b) + EMIT2(0x205) EMIT2(0x207) EMIT2(0x229) + EMIT2(0x247) EMIT2(0x1d92) EMIT2(0x1e15) + EMIT2(0x1e17) EMIT2(0x1e19) EMIT2(0x1e1b) + EMIT2(0x1e1d) EMIT2(0x1eb9) EMIT2(0x1ebb) + EMIT2(0x1ebd) EMIT2(0x1ebf) EMIT2(0x1ec1) + EMIT2(0x1ec3) EMIT2(0x1ec5) EMIT2(0x1ec7) return; - case 'f': CASEMBC(0x1e1f) - EMIT2('f'); EMITMBC(0x1e1f) + case 'f': case 0x192: case 0x1d6e: case 0x1d82: + case 0x1e1f: case 0xa799: + EMIT2('f') EMIT2(0x192) EMIT2(0x1d6e) EMIT2(0x1d82) + EMIT2(0x1e1f) EMIT2(0xa799) return; - case 'g': CASEMBC(0x11d) CASEMBC(0x11f) CASEMBC(0x121) - CASEMBC(0x123) CASEMBC(0x1e5) CASEMBC(0x1e7) - CASEMBC(0x1f5) CASEMBC(0x1e21) - EMIT2('g'); EMITMBC(0x11d) EMITMBC(0x11f) EMITMBC(0x121) - EMITMBC(0x123) EMITMBC(0x1e5) EMITMBC(0x1e7) - EMITMBC(0x1f5) EMITMBC(0x1e21) + case 'g': case 0x11d: case 0x11f: case 0x121: case 0x123: + case 0x1e5: case 0x1e7: case 0x1f5: case 0x260: case 0x1d83: + case 0x1e21: case 0xa7a1: + EMIT2('g') EMIT2(0x11d) EMIT2(0x11f) EMIT2(0x121) + EMIT2(0x123) EMIT2(0x1e5) EMIT2(0x1e7) + EMIT2(0x1f5) EMIT2(0x260) EMIT2(0x1d83) + EMIT2(0x1e21) EMIT2(0xa7a1) return; - case 'h': CASEMBC(0x125) CASEMBC(0x127) CASEMBC(0x1e23) - CASEMBC(0x1e27) CASEMBC(0x1e29) CASEMBC(0x1e96) - EMIT2('h'); EMITMBC(0x125) EMITMBC(0x127) EMITMBC(0x1e23) - EMITMBC(0x1e27) EMITMBC(0x1e29) EMITMBC(0x1e96) + case 'h': case 0x125: case 0x127: case 0x21f: case 0x1e23: + case 0x1e25: case 0x1e27: case 0x1e29: case 0x1e2b: + case 0x1e96: case 0x2c68: case 0xa795: + EMIT2('h') EMIT2(0x125) EMIT2(0x127) EMIT2(0x21f) + EMIT2(0x1e23) EMIT2(0x1e25) EMIT2(0x1e27) + EMIT2(0x1e29) EMIT2(0x1e2b) EMIT2(0x1e96) + EMIT2(0x2c68) EMIT2(0xa795) return; case 'i': case i_grave: case i_acute: case i_circumflex: - case i_diaeresis: CASEMBC(0x129) CASEMBC(0x12b) - CASEMBC(0x12d) CASEMBC(0x12f) CASEMBC(0x1d0) - CASEMBC(0x1ec9) - EMIT2('i'); EMIT2(i_grave); EMIT2(i_acute); - EMIT2(i_circumflex); EMIT2(i_diaeresis); - EMITMBC(0x129) EMITMBC(0x12b) - EMITMBC(0x12d) EMITMBC(0x12f) EMITMBC(0x1d0) - EMITMBC(0x1ec9) + case i_diaeresis: case 0x129: case 0x12b: case 0x12d: + case 0x12f: case 0x1d0: case 0x209: case 0x20b: + case 0x268: case 0x1d96: case 0x1e2d: case 0x1e2f: + case 0x1ec9: case 0x1ecb: + EMIT2('i') EMIT2(i_grave) EMIT2(i_acute) + EMIT2(i_circumflex) EMIT2(i_diaeresis) + EMIT2(0x129) EMIT2(0x12b) EMIT2(0x12d) + EMIT2(0x12f) EMIT2(0x1d0) EMIT2(0x209) + EMIT2(0x20b) EMIT2(0x268) EMIT2(0x1d96) + EMIT2(0x1e2d) EMIT2(0x1e2f) EMIT2(0x1ec9) + EMIT2(0x1ecb) EMIT2(0x1ecb) return; - case 'j': CASEMBC(0x135) CASEMBC(0x1f0) - EMIT2('j'); EMITMBC(0x135) EMITMBC(0x1f0) + case 'j': case 0x135: case 0x1f0: case 0x249: + EMIT2('j') EMIT2(0x135) EMIT2(0x1f0) EMIT2(0x249) return; - case 'k': CASEMBC(0x137) CASEMBC(0x1e9) CASEMBC(0x1e31) - CASEMBC(0x1e35) - EMIT2('k'); EMITMBC(0x137) EMITMBC(0x1e9) EMITMBC(0x1e31) - EMITMBC(0x1e35) + case 'k': case 0x137: case 0x199: case 0x1e9: case 0x1d84: + case 0x1e31: case 0x1e33: case 0x1e35: case 0x2c6a: case 0xa741: + EMIT2('k') EMIT2(0x137) EMIT2(0x199) EMIT2(0x1e9) + EMIT2(0x1d84) EMIT2(0x1e31) EMIT2(0x1e33) + EMIT2(0x1e35) EMIT2(0x2c6a) EMIT2(0xa741) return; - case 'l': CASEMBC(0x13a) CASEMBC(0x13c) CASEMBC(0x13e) - CASEMBC(0x140) CASEMBC(0x142) CASEMBC(0x1e3b) - EMIT2('l'); EMITMBC(0x13a) EMITMBC(0x13c) EMITMBC(0x13e) - EMITMBC(0x140) EMITMBC(0x142) EMITMBC(0x1e3b) + case 'l': case 0x13a: case 0x13c: case 0x13e: case 0x140: + case 0x142: case 0x19a: case 0x1e37: case 0x1e39: case 0x1e3b: + case 0x1e3d: case 0x2c61: + EMIT2('l') EMIT2(0x13a) EMIT2(0x13c) + EMIT2(0x13e) EMIT2(0x140) EMIT2(0x142) + EMIT2(0x19a) EMIT2(0x1e37) EMIT2(0x1e39) + EMIT2(0x1e3b) EMIT2(0x1e3d) EMIT2(0x2c61) return; - case 'm': CASEMBC(0x1e3f) CASEMBC(0x1e41) - EMIT2('m'); EMITMBC(0x1e3f) EMITMBC(0x1e41) + case 'm': case 0x1d6f: case 0x1e3f: case 0x1e41: case 0x1e43: + EMIT2('m') EMIT2(0x1d6f) EMIT2(0x1e3f) + EMIT2(0x1e41) EMIT2(0x1e43) return; - case 'n': case n_virguilla: CASEMBC(0x144) CASEMBC(0x146) - CASEMBC(0x148) CASEMBC(0x149) CASEMBC(0x1e45) - CASEMBC(0x1e49) - EMIT2('n'); EMIT2(n_virguilla); - EMITMBC(0x144) EMITMBC(0x146) - EMITMBC(0x148) EMITMBC(0x149) EMITMBC(0x1e45) - EMITMBC(0x1e49) + case 'n': case n_virguilla: case 0x144: case 0x146: case 0x148: + case 0x149: case 0x1f9: case 0x1d70: case 0x1d87: case 0x1e45: + case 0x1e47: case 0x1e49: case 0x1e4b: case 0xa7a5: + EMIT2('n') EMIT2(n_virguilla) + EMIT2(0x144) EMIT2(0x146) EMIT2(0x148) + EMIT2(0x149) EMIT2(0x1f9) EMIT2(0x1d70) + EMIT2(0x1d87) EMIT2(0x1e45) EMIT2(0x1e47) + EMIT2(0x1e49) EMIT2(0x1e4b) EMIT2(0xa7a5) return; case 'o': case o_grave: case o_acute: case o_circumflex: - case o_virguilla: case o_diaeresis: case o_slash: - CASEMBC(0x14d) CASEMBC(0x14f) CASEMBC(0x151) - CASEMBC(0x1a1) CASEMBC(0x1d2) CASEMBC(0x1eb) - CASEMBC(0x1ed) CASEMBC(0x1ecf) - EMIT2('o'); EMIT2(o_grave); EMIT2(o_acute); - EMIT2(o_circumflex); EMIT2(o_virguilla); - EMIT2(o_diaeresis); EMIT2(o_slash); - EMITMBC(0x14d) EMITMBC(0x14f) EMITMBC(0x151) - EMITMBC(0x1a1) EMITMBC(0x1d2) EMITMBC(0x1eb) - EMITMBC(0x1ed) EMITMBC(0x1ecf) + case o_virguilla: case o_diaeresis: case o_slash: + case 0x14d: case 0x14f: case 0x151: case 0x1a1: + case 0x1d2: case 0x1eb: case 0x1ed: case 0x1ff: + case 0x20d: case 0x20f: case 0x22b: case 0x22d: + case 0x22f: case 0x231: case 0x275: case 0x1e4d: + case 0x1e4f: case 0x1e51: case 0x1e53: case 0x1ecd: + case 0x1ecf: case 0x1ed1: case 0x1ed3: case 0x1ed5: + case 0x1ed7: case 0x1ed9: case 0x1edb: case 0x1edd: + case 0x1edf: case 0x1ee1: case 0x1ee3: + EMIT2('o') EMIT2(o_grave) EMIT2(o_acute) + EMIT2(o_circumflex) EMIT2(o_virguilla) + EMIT2(o_diaeresis) EMIT2(o_slash) + EMIT2(0x14d) EMIT2(0x14f) EMIT2(0x151) + EMIT2(0x1a1) EMIT2(0x1d2) EMIT2(0x1eb) + EMIT2(0x1ed) EMIT2(0x1ff) EMIT2(0x20d) + EMIT2(0x20f) EMIT2(0x22b) EMIT2(0x22d) + EMIT2(0x22f) EMIT2(0x231) EMIT2(0x275) + EMIT2(0x1e4d) EMIT2(0x1e4f) EMIT2(0x1e51) + EMIT2(0x1e53) EMIT2(0x1ecd) EMIT2(0x1ecf) + EMIT2(0x1ed1) EMIT2(0x1ed3) EMIT2(0x1ed5) + EMIT2(0x1ed7) EMIT2(0x1ed9) EMIT2(0x1edb) + EMIT2(0x1edd) EMIT2(0x1edf) EMIT2(0x1ee1) + EMIT2(0x1ee3) + return; + + case 'p': case 0x1a5: case 0x1d71: case 0x1d7d: case 0x1d88: + case 0x1e55: case 0x1e57: + EMIT2('p') EMIT2(0x1a5) EMIT2(0x1d71) EMIT2(0x1d7d) + EMIT2(0x1d88) EMIT2(0x1e55) EMIT2(0x1e57) return; - case 'p': CASEMBC(0x1e55) CASEMBC(0x1e57) - EMIT2('p'); EMITMBC(0x1e55) EMITMBC(0x1e57) + case 'q': case 0x24b: case 0x2a0: + EMIT2('q') EMIT2(0x24b) EMIT2(0x2a0) return; - case 'r': CASEMBC(0x155) CASEMBC(0x157) CASEMBC(0x159) - CASEMBC(0x1e59) CASEMBC(0x1e5f) - EMIT2('r'); EMITMBC(0x155) EMITMBC(0x157) EMITMBC(0x159) - EMITMBC(0x1e59) EMITMBC(0x1e5f) + case 'r': case 0x155: case 0x157: case 0x159: case 0x211: + case 0x213: case 0x24d: case 0x27d: case 0x1d72: case 0x1d73: + case 0x1d89: case 0x1e59: case 0x1e5b: case 0x1e5d: case 0x1e5f: + case 0xa7a7: + EMIT2('r') EMIT2(0x155) EMIT2(0x157) EMIT2(0x159) + EMIT2(0x211) EMIT2(0x213) EMIT2(0x24d) EMIT2(0x27d) + EMIT2(0x1d72) EMIT2(0x1d73) EMIT2(0x1d89) EMIT2(0x1e59) + EMIT2(0x1e5b) EMIT2(0x1e5d) EMIT2(0x1e5f) EMIT2(0xa7a7) return; - case 's': CASEMBC(0x15b) CASEMBC(0x15d) CASEMBC(0x15f) - CASEMBC(0x161) CASEMBC(0x1e61) - EMIT2('s'); EMITMBC(0x15b) EMITMBC(0x15d) EMITMBC(0x15f) - EMITMBC(0x161) EMITMBC(0x1e61) + case 's': case 0x15b: case 0x15d: case 0x15f: case 0x161: + case 0x219: case 0x23f: case 0x1d74: case 0x1d8a: case 0x1e61: + case 0x1e63: case 0x1e65: case 0x1e67: case 0x1e69: case 0xa7a9: + EMIT2('s') EMIT2(0x15b) EMIT2(0x15d) EMIT2(0x15f) + EMIT2(0x161) EMIT2(0x219) EMIT2(0x23f) EMIT2(0x1d74) + EMIT2(0x1d8a) EMIT2(0x1e61) EMIT2(0x1e63) EMIT2(0x1e65) + EMIT2(0x1e67) EMIT2(0x1e69) EMIT2(0xa7a9) return; - case 't': CASEMBC(0x163) CASEMBC(0x165) CASEMBC(0x167) - CASEMBC(0x1e6b) CASEMBC(0x1e6f) CASEMBC(0x1e97) - EMIT2('t'); EMITMBC(0x163) EMITMBC(0x165) EMITMBC(0x167) - EMITMBC(0x1e6b) EMITMBC(0x1e6f) EMITMBC(0x1e97) + case 't': case 0x163: case 0x165: case 0x167: case 0x1ab: + case 0x1ad: case 0x21b: case 0x288: case 0x1d75: case 0x1e6b: + case 0x1e6d: case 0x1e6f: case 0x1e71: case 0x1e97: case 0x2c66: + EMIT2('t') EMIT2(0x163) EMIT2(0x165) EMIT2(0x167) + EMIT2(0x1ab) EMIT2(0x1ad) EMIT2(0x21b) EMIT2(0x288) + EMIT2(0x1d75) EMIT2(0x1e6b) EMIT2(0x1e6d) EMIT2(0x1e6f) + EMIT2(0x1e71) EMIT2(0x1e97) EMIT2(0x2c66) return; case 'u': case u_grave: case u_acute: case u_circumflex: - case u_diaeresis: CASEMBC(0x169) CASEMBC(0x16b) - CASEMBC(0x16d) CASEMBC(0x16f) CASEMBC(0x171) - CASEMBC(0x173) CASEMBC(0x1b0) CASEMBC(0x1d4) - CASEMBC(0x1ee7) - EMIT2('u'); EMIT2(u_grave); EMIT2(u_acute); - EMIT2(u_circumflex); EMIT2(u_diaeresis); - EMITMBC(0x169) EMITMBC(0x16b) - EMITMBC(0x16d) EMITMBC(0x16f) EMITMBC(0x171) - EMITMBC(0x173) EMITMBC(0x1b0) EMITMBC(0x1d4) - EMITMBC(0x1ee7) + case u_diaeresis: case 0x169: case 0x16b: case 0x16d: + case 0x16f: case 0x171: case 0x173: case 0x1b0: case 0x1d4: + case 0x1d6: case 0x1d8: case 0x1da: case 0x1dc: case 0x215: + case 0x217: case 0x289: case 0x1d7e: case 0x1d99: case 0x1e73: + case 0x1e75: case 0x1e77: case 0x1e79: case 0x1e7b: + case 0x1ee5: case 0x1ee7: case 0x1ee9: case 0x1eeb: + case 0x1eed: case 0x1eef: case 0x1ef1: + EMIT2('u') EMIT2(u_grave) EMIT2(u_acute) + EMIT2(u_circumflex) EMIT2(u_diaeresis) + EMIT2(0x169) EMIT2(0x16b) + EMIT2(0x16d) EMIT2(0x16f) EMIT2(0x171) + EMIT2(0x173) EMIT2(0x1d6) EMIT2(0x1d8) + EMIT2(0x215) EMIT2(0x217) EMIT2(0x1b0) + EMIT2(0x1d4) EMIT2(0x1da) EMIT2(0x1dc) + EMIT2(0x289) EMIT2(0x1e73) EMIT2(0x1d7e) + EMIT2(0x1d99) EMIT2(0x1e75) EMIT2(0x1e77) + EMIT2(0x1e79) EMIT2(0x1e7b) EMIT2(0x1ee5) + EMIT2(0x1ee7) EMIT2(0x1ee9) EMIT2(0x1eeb) + EMIT2(0x1eed) EMIT2(0x1eef) EMIT2(0x1ef1) return; - case 'v': CASEMBC(0x1e7d) - EMIT2('v'); EMITMBC(0x1e7d) + case 'v': case 0x28b: case 0x1d8c: case 0x1e7d: case 0x1e7f: + EMIT2('v') EMIT2(0x28b) EMIT2(0x1d8c) EMIT2(0x1e7d) + EMIT2(0x1e7f) return; - case 'w': CASEMBC(0x175) CASEMBC(0x1e81) CASEMBC(0x1e83) - CASEMBC(0x1e85) CASEMBC(0x1e87) CASEMBC(0x1e98) - EMIT2('w'); EMITMBC(0x175) EMITMBC(0x1e81) EMITMBC(0x1e83) - EMITMBC(0x1e85) EMITMBC(0x1e87) EMITMBC(0x1e98) + case 'w': case 0x175: case 0x1e81: case 0x1e83: case 0x1e85: + case 0x1e87: case 0x1e89: case 0x1e98: + EMIT2('w') EMIT2(0x175) EMIT2(0x1e81) EMIT2(0x1e83) + EMIT2(0x1e85) EMIT2(0x1e87) EMIT2(0x1e89) EMIT2(0x1e98) return; - case 'x': CASEMBC(0x1e8b) CASEMBC(0x1e8d) - EMIT2('x'); EMITMBC(0x1e8b) EMITMBC(0x1e8d) + case 'x': case 0x1e8b: case 0x1e8d: + EMIT2('x') EMIT2(0x1e8b) EMIT2(0x1e8d) return; - case 'y': case y_acute: case y_diaeresis: CASEMBC(0x177) - CASEMBC(0x1e8f) CASEMBC(0x1e99) CASEMBC(0x1ef3) - CASEMBC(0x1ef7) CASEMBC(0x1ef9) - EMIT2('y'); EMIT2(y_acute); EMIT2(y_diaeresis); - EMITMBC(0x177) - EMITMBC(0x1e8f) EMITMBC(0x1e99) EMITMBC(0x1ef3) - EMITMBC(0x1ef7) EMITMBC(0x1ef9) + case 'y': case y_acute: case y_diaeresis: case 0x177: + case 0x1b4: case 0x233: case 0x24f: case 0x1e8f: + case 0x1e99: case 0x1ef3: case 0x1ef5: case 0x1ef7: + case 0x1ef9: + EMIT2('y') EMIT2(y_acute) EMIT2(y_diaeresis) + EMIT2(0x177) EMIT2(0x1b4) EMIT2(0x233) EMIT2(0x24f) + EMIT2(0x1e8f) EMIT2(0x1e99) EMIT2(0x1ef3) + EMIT2(0x1ef5) EMIT2(0x1ef7) EMIT2(0x1ef9) return; - case 'z': CASEMBC(0x17a) CASEMBC(0x17c) CASEMBC(0x17e) - CASEMBC(0x1b6) CASEMBC(0x1e91) CASEMBC(0x1e95) - EMIT2('z'); EMITMBC(0x17a) EMITMBC(0x17c) EMITMBC(0x17e) - EMITMBC(0x1b6) EMITMBC(0x1e91) EMITMBC(0x1e95) + case 'z': case 0x17a: case 0x17c: case 0x17e: case 0x1b6: + case 0x1d76: case 0x1d8e: case 0x1e91: case 0x1e93: + case 0x1e95: case 0x2c6c: + EMIT2('z') EMIT2(0x17a) EMIT2(0x17c) EMIT2(0x17e) + EMIT2(0x1b6) EMIT2(0x1d76) EMIT2(0x1d8e) EMIT2(0x1e91) + EMIT2(0x1e93) EMIT2(0x1e95) EMIT2(0x2c6c) return; - /* default: character itself */ + // default: character itself } } EMIT2(c); #undef EMIT2 -#undef EMITMBC } /* @@ -1484,10 +1639,20 @@ static int nfa_regatom(void) { int64_t n = 0; const int cmp = c; + bool cur = false; - if (c == '<' || c == '>') + if (c == '<' || c == '>') { + c = getchr(); + } + if (no_Magic(c) == '.') { + cur = true; c = getchr(); + } while (ascii_isdigit(c)) { + if (cur) { + semsg(_(e_regexp_number_after_dot_pos_search), no_Magic(c)); + return FAIL; + } if (n > (INT32_MAX - (c - '0')) / 10) { // overflow. emsg(_(e_value_too_large)); @@ -1500,6 +1665,9 @@ static int nfa_regatom(void) int32_t limit = INT32_MAX; if (c == 'l') { + if (cur) { + n = curwin->w_cursor.lnum; + } // \%{n}l \%{n}<l \%{n}>l EMIT(cmp == '<' ? NFA_LNUM_LT : cmp == '>' ? NFA_LNUM_GT : NFA_LNUM); @@ -1507,10 +1675,19 @@ static int nfa_regatom(void) at_start = true; } } else if (c == 'c') { + if (cur) { + n = curwin->w_cursor.col; + n++; + } // \%{n}c \%{n}<c \%{n}>c EMIT(cmp == '<' ? NFA_COL_LT : cmp == '>' ? NFA_COL_GT : NFA_COL); } else { + if (cur) { + colnr_T vcol = 0; + getvvcol(curwin, &curwin->w_cursor, NULL, NULL, &vcol); + n = ++vcol; + } // \%{n}v \%{n}<v \%{n}>v EMIT(cmp == '<' ? NFA_VCOL_LT : cmp == '>' ? NFA_VCOL_GT : NFA_VCOL); @@ -2014,7 +2191,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) @@ -2566,20 +2743,20 @@ static void nfa_print_state2(FILE *debugf, nfa_state_T *state, garray_T *indent) ga_concat(indent, (char_u *)"| "); else ga_concat(indent, (char_u *)" "); - ga_append(indent, '\0'); + ga_append(indent, NUL); nfa_print_state2(debugf, state->out, indent); /* replace last part of indent for state->out1 */ indent->ga_len -= 3; ga_concat(indent, (char_u *)" "); - ga_append(indent, '\0'); + ga_append(indent, NUL); nfa_print_state2(debugf, state->out1, indent); /* shrink indent */ indent->ga_len -= 3; - ga_append(indent, '\0'); + ga_append(indent, NUL); } /* @@ -4368,7 +4545,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; @@ -6072,7 +6249,16 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, case NFA_MARK_GT: case NFA_MARK_LT: { - pos_T *pos = getmark_buf(rex.reg_buf, t->state->val, false); + pos_T *pos; + size_t col = REG_MULTI ? rex.input - rex.line : 0; + + 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. diff --git a/src/nvim/runtime.c b/src/nvim/runtime.c index be365d4ab8..1102096b32 100644 --- a/src/nvim/runtime.c +++ b/src/nvim/runtime.c @@ -12,7 +12,6 @@ #include "nvim/ex_cmds.h" #include "nvim/ex_cmds2.h" #include "nvim/lua/executor.h" -#include "nvim/misc1.h" #include "nvim/option.h" #include "nvim/os/os.h" #include "nvim/runtime.h" @@ -25,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) @@ -147,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(); @@ -173,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 { @@ -269,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(); @@ -303,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; @@ -321,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) { @@ -334,9 +371,7 @@ ArrayOf(String) runtime_get_named(bool lua, Array pat, bool all) } } } - done: - runtime_search_path_unref(path, &ref); return rv; } @@ -570,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 2ce2be0bfd..296255ed8c 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -75,6 +75,7 @@ #include "nvim/cursor.h" #include "nvim/cursor_shape.h" #include "nvim/decoration.h" +#include "nvim/decoration_provider.h" #include "nvim/diff.h" #include "nvim/edit.h" #include "nvim/eval.h" @@ -87,18 +88,19 @@ #include "nvim/garray.h" #include "nvim/getchar.h" #include "nvim/highlight.h" +#include "nvim/highlight_group.h" #include "nvim/indent.h" #include "nvim/lib/kvec.h" #include "nvim/log.h" #include "nvim/lua/executor.h" #include "nvim/main.h" #include "nvim/mark.h" +#include "nvim/match.h" #include "nvim/mbyte.h" #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/menu.h" #include "nvim/message.h" -#include "nvim/misc1.h" #include "nvim/move.h" #include "nvim/normal.h" #include "nvim/option.h" @@ -127,8 +129,6 @@ #define MB_FILLER_CHAR '<' /* character used when a double-width character * doesn't fit. */ -typedef kvec_withinit_t(DecorProvider *, 4) Providers; - // temporary buffer for rendering a single screenline, so it can be // compared with previous contents to calculate smallest delta. // Per-cell attributes @@ -165,40 +165,9 @@ static bool resizing = false; #ifdef INCLUDE_GENERATED_DECLARATIONS # include "screen.c.generated.h" #endif -#define SEARCH_HL_PRIORITY 0 static char *provider_err = NULL; -static bool provider_invoke(NS ns_id, const char *name, LuaRef ref, Array args, bool default_true) -{ - Error err = ERROR_INIT; - - textlock++; - provider_active = true; - Object ret = nlua_call_ref(ref, name, args, true, &err); - provider_active = false; - textlock--; - - if (!ERROR_SET(&err) - && api_object_to_bool(ret, "provider %s retval", default_true, &err)) { - return true; - } - - if (ERROR_SET(&err)) { - const char *ns_name = describe_ns(ns_id); - ELOG("error in provider %s:%s: %s", ns_name, name, err.msg); - bool verbose_errs = true; // TODO(bfredl): - if (verbose_errs && provider_err == NULL) { - static char errbuf[IOSIZE]; - snprintf(errbuf, sizeof errbuf, "%s: %s", ns_name, err.msg); - provider_err = xstrdup(errbuf); - } - } - - api_free_object(ret); - return false; -} - /// Redraw a window later, with update_screen(type). /// /// Set must_redraw only if not already set to a higher value. @@ -314,6 +283,32 @@ void update_curbuf(int type) update_screen(type); } +/// called when the status bars for the buffer 'buf' need to be updated +void redraw_buf_status_later(buf_T *buf) +{ + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + if (wp->w_buffer == buf + && (wp->w_status_height || (wp == curwin && global_stl_height()))) { + wp->w_redr_status = true; + if (must_redraw < VALID) { + must_redraw = VALID; + } + } + } +} + +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 @@ -323,6 +318,7 @@ void update_curbuf(int type) int update_screen(int type) { static bool did_intro = false; + bool is_stl_global = global_stl_height() > 0; // Don't do anything if the screen structures are (not yet) valid. // A VimResized autocmd can invoke redrawing in the middle of a resize, @@ -405,10 +401,13 @@ int update_screen(int type) if (W_ENDROW(wp) > valid) { wp->w_redr_type = MAX(wp->w_redr_type, NOT_VALID); } - if (W_ENDROW(wp) + wp->w_status_height > valid) { + if (!is_stl_global && W_ENDROW(wp) + wp->w_status_height > valid) { wp->w_redr_status = true; } } + if (is_stl_global && Rows - p_ch - 1 > valid) { + curwin->w_redr_status = true; + } } msg_grid_set_pos(Rows-p_ch, false); msg_grid_invalid = false; @@ -430,13 +429,15 @@ int update_screen(int type) wp->w_redr_type = REDRAW_TOP; } else { wp->w_redr_type = NOT_VALID; - if (W_ENDROW(wp) + wp->w_status_height - <= msg_scrolled) { - wp->w_redr_status = TRUE; + if (!is_stl_global && W_ENDROW(wp) + wp->w_status_height <= msg_scrolled) { + wp->w_redr_status = true; } } } } + if (is_stl_global && Rows - p_ch - 1 <= msg_scrolled) { + curwin->w_redr_status = true; + } redraw_cmdline = true; redraw_tabline = true; } @@ -451,9 +452,11 @@ int update_screen(int type) // reset cmdline_row now (may have been changed temporarily) compute_cmdrow(); + bool hl_changed = false; // Check for changed highlighting if (need_highlight_changed) { highlight_changed(); + hl_changed = true; } if (type == CLEAR) { // first clear screen @@ -475,28 +478,8 @@ int update_screen(int type) ui_comp_set_screen_valid(true); - Providers providers; - kvi_init(providers); - for (size_t i = 0; i < kv_size(decor_providers); i++) { - DecorProvider *p = &kv_A(decor_providers, i); - if (!p->active) { - continue; - } - - bool active; - if (p->redraw_start != LUA_NOREF) { - FIXED_TEMP_ARRAY(args, 2); - args.items[0] = INTEGER_OBJ(display_tick); - args.items[1] = INTEGER_OBJ(type); - active = provider_invoke(p->ns_id, "start", p->redraw_start, args, true); - } else { - active = true; - } - - if (active) { - kvi_push(providers, p); - } - } + DecorProviders providers; + decor_providers_start(&providers, type, &provider_err); // "start" callback could have changed highlights for global elements if (win_check_ns_hl(NULL)) { @@ -554,7 +537,7 @@ int update_screen(int type) * buffer. Each buffer must only be done once. */ FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - update_window_hl(wp, type >= NOT_VALID); + update_window_hl(wp, type >= NOT_VALID || hl_changed); buf_T *buf = wp->w_buffer; if (buf->b_mod_set) { @@ -565,14 +548,7 @@ int update_screen(int type) } if (buf->b_mod_tick_decor < display_tick) { - for (size_t i = 0; i < kv_size(providers); i++) { - DecorProvider *p = kv_A(providers, i); - if (p && p->redraw_buf != LUA_NOREF) { - FIXED_TEMP_ARRAY(args, 1); - args.items[0] = BUFFER_OBJ(buf->handle); - provider_invoke(p->ns_id, "buf", p->redraw_buf, args, true); - } - } + decor_providers_invoke_buf(buf, &providers, &provider_err); buf->b_mod_tick_decor = display_tick; } } @@ -642,18 +618,7 @@ int update_screen(int type) } did_intro = true; - for (size_t i = 0; i < kv_size(providers); i++) { - DecorProvider *p = kv_A(providers, i); - if (!p->active) { - continue; - } - - if (p->redraw_end != LUA_NOREF) { - FIXED_TEMP_ARRAY(args, 1); - args.items[0] = INTEGER_OBJ(display_tick); - provider_invoke(p->ns_id, "end", p->redraw_end, args, true); - } - } + decor_providers_invoke_end(&providers, &provider_err); kvi_destroy(providers); @@ -702,15 +667,11 @@ void conceal_check_cursor_line(void) /// Whether cursorline is drawn in a special way /// -/// If true, both old and new cursorline will need -/// to be redrawn when moving cursor within windows. -/// TODO(bfredl): VIsual_active shouldn't be needed, but is used to fix a glitch -/// caused by scrolling. +/// If true, both old and new cursorline will need to be redrawn when moving cursor within windows. bool win_cursorline_standout(const win_T *wp) FUNC_ATTR_NONNULL_ALL { - return wp->w_p_cul - || (wp->w_p_cole > 0 && (VIsual_active || !conceal_cursor_line(wp))); + return wp->w_p_cul || (wp->w_p_cole > 0 && !conceal_cursor_line(wp)); } /* @@ -740,7 +701,7 @@ bool win_cursorline_standout(const win_T *wp) * mid: from mid_start to mid_end (update inversion or changed text) * bot: from bot_start to last row (when scrolled up) */ -static void win_update(win_T *wp, Providers *providers) +static void win_update(win_T *wp, DecorProviders *providers) { buf_T *buf = wp->w_buffer; int type; @@ -776,12 +737,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) { @@ -789,8 +744,11 @@ static void win_update(win_T *wp, Providers *providers) wp->w_lines_valid = 0; } - // Window is zero-height: nothing to draw. + // Window is zero-height: Only need to draw the separator if (wp->w_grid.Rows == 0) { + // draw the horizontal separator below this window + draw_hsep_win(wp); + draw_sep_connectors_win(wp); wp->w_redr_type = 0; return; } @@ -798,12 +756,15 @@ static void win_update(win_T *wp, Providers *providers) // Window is zero-width: Only need to draw the separator. if (wp->w_grid.Columns == 0) { // draw the vertical separator right of this window - draw_vsep_win(wp, 0); + draw_vsep_win(wp); + draw_sep_connectors_win(wp); wp->w_redr_type = 0; return; } - init_search_hl(wp); + redraw_win_signcol(wp); + + init_search_hl(wp, &search_hl); /* Force redraw when width of 'number' or 'relativenumber' column * changes. */ @@ -973,7 +934,7 @@ static void win_update(win_T *wp, Providers *providers) if (mod_top != 0 && wp->w_topline == mod_top && (!wp->w_lines[0].wl_valid - || wp->w_topline <= wp->w_lines[0].wl_lnum)) { + || wp->w_topline == wp->w_lines[0].wl_lnum)) { // w_topline is the first changed line and window is not scrolled, // the scrolling from changed lines will be done further down. } else if (wp->w_lines[0].wl_valid @@ -1201,19 +1162,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 @@ -1334,30 +1316,10 @@ static void win_update(win_T *wp, Providers *providers) decor_redraw_reset(buf, &decor_state); - Providers line_providers; - kvi_init(line_providers); - - linenr_T knownmax = ((wp->w_valid & VALID_BOTLINE) - ? wp->w_botline - : (wp->w_topline + wp->w_height_inner)); - - for (size_t k = 0; k < kv_size(*providers); k++) { - DecorProvider *p = kv_A(*providers, k); - if (p && p->redraw_win != LUA_NOREF) { - FIXED_TEMP_ARRAY(args, 4); - args.items[0] = WINDOW_OBJ(wp->handle); - args.items[1] = BUFFER_OBJ(buf->handle); - // TODO(bfredl): we are not using this, but should be first drawn line? - args.items[2] = INTEGER_OBJ(wp->w_topline-1); - args.items[3] = INTEGER_OBJ(knownmax); - if (provider_invoke(p->ns_id, "win", p->redraw_win, args, true)) { - kvi_push(line_providers, p); - } - } - } - - win_check_ns_hl(wp); + DecorProviders line_providers; + decor_providers_invoke_win(wp, providers, &line_providers, &provider_err); + bool cursorline_standout = win_cursorline_standout(wp); for (;;) { /* stop updating when reached the end of the window (check for _past_ @@ -1403,8 +1365,8 @@ static void win_update(win_T *wp, Providers *providers) // if lines were inserted or deleted || (wp->w_match_head != NULL && buf->b_mod_xlines != 0))))) - || (wp->w_p_cul && (lnum == wp->w_cursor.lnum - || lnum == wp->w_last_cursorline))) { + || (cursorline_standout && lnum == wp->w_cursor.lnum) + || lnum == wp->w_last_cursorline) { if (lnum == mod_top) { top_to_mod = false; } @@ -1569,7 +1531,7 @@ static void win_update(win_T *wp, Providers *providers) // will draw "@ " lines below. row = wp->w_grid.Rows + 1; } else { - prepare_search_hl(wp, lnum); + prepare_search_hl(wp, &search_hl, lnum); // Let the syntax stuff know we skipped a few lines. if (syntax_last_parsed != 0 && syntax_last_parsed + 1 < lnum && syntax_present(wp)) { @@ -1581,17 +1543,17 @@ static void win_update(win_T *wp, Providers *providers) foldinfo.fi_lines ? srow : wp->w_grid.Rows, mod_top == 0, false, foldinfo, &line_providers); - wp->w_lines[idx].wl_folded = foldinfo.fi_lines != 0; - wp->w_lines[idx].wl_lastlnum = lnum; - did_update = DID_LINE; - - if (foldinfo.fi_lines > 0) { - did_update = DID_FOLD; + if (foldinfo.fi_lines == 0) { + wp->w_lines[idx].wl_folded = false; + wp->w_lines[idx].wl_lastlnum = lnum; + did_update = DID_LINE; + syntax_last_parsed = lnum; + } else { foldinfo.fi_lines--; + wp->w_lines[idx].wl_folded = true; wp->w_lines[idx].wl_lastlnum = lnum + foldinfo.fi_lines; + did_update = DID_FOLD; } - - syntax_last_parsed = lnum; } wp->w_lines[idx].wl_lnum = lnum; @@ -1611,9 +1573,9 @@ static void win_update(win_T *wp, Providers *providers) idx++; lnum += foldinfo.fi_lines + 1; } else { - if (wp->w_p_rnu) { - // 'relativenumber' set: The text doesn't need to be drawn, but - // the number column nearly always does. + if (wp->w_p_rnu && wp->w_last_cursor_lnum_rnu != wp->w_cursor.lnum) { + // 'relativenumber' set and cursor moved vertically: The + // text doesn't need to be drawn, but the number column does. foldinfo_T info = fold_info(wp, lnum); (void)win_line(wp, lnum, srow, wp->w_grid.Rows, true, true, info, &line_providers); @@ -1637,6 +1599,11 @@ static void win_update(win_T *wp, Providers *providers) * End of loop over all window lines. */ + // Now that the window has been redrawn with the old and new cursor line, + // update w_last_cursorline. + wp->w_last_cursorline = cursorline_standout ? wp->w_cursor.lnum : 0; + + wp->w_last_cursor_lnum_rnu = wp->w_p_rnu ? wp->w_cursor.lnum : 0; if (idx > wp->w_lines_valid) { wp->w_lines_valid = idx; @@ -1672,16 +1639,18 @@ static void win_update(win_T *wp, Providers *providers) int scr_row = wp->w_grid.Rows - 1; // Last line isn't finished: Display "@@@" in the last screen line. - grid_puts_len(&wp->w_grid, (char_u *)"@@", 2, scr_row, 0, at_attr); + grid_puts_len(&wp->w_grid, (char_u *)"@@", MIN(wp->w_grid.Columns, 2), scr_row, 0, at_attr); grid_fill(&wp->w_grid, scr_row, scr_row + 1, 2, wp->w_grid.Columns, '@', ' ', at_attr); set_empty_rows(wp, srow); wp->w_botline = lnum; } else if (dy_flags & DY_LASTLINE) { // 'display' has "lastline" + int start_col = wp->w_grid.Columns - 3; + // Last line isn't finished: Display "@@@" at the end. grid_fill(&wp->w_grid, wp->w_grid.Rows - 1, wp->w_grid.Rows, - wp->w_grid.Columns - 3, wp->w_grid.Columns, '@', '@', at_attr); + MAX(start_col, 0), wp->w_grid.Columns, '@', '@', at_attr); set_empty_rows(wp, srow); wp->w_botline = lnum; } else { @@ -1692,7 +1661,7 @@ static void win_update(win_T *wp, Providers *providers) if (eof) { // we hit the end of the file wp->w_botline = buf->b_ml.ml_line_count + 1; j = win_get_fill(wp, wp->w_botline); - if (j > 0 && !wp->w_botfill) { + if (j > 0 && !wp->w_botfill && row < wp->w_grid.Rows) { // Display filler text below last line. win_line() will check // for ml_line_count+1 and only draw filler lines foldinfo_T info = FOLDINFO_INIT; @@ -1712,7 +1681,9 @@ static void win_update(win_T *wp, Providers *providers) kvi_destroy(line_providers); if (wp->w_redr_type >= REDRAW_TOP) { - draw_vsep_win(wp, 0); + draw_vsep_win(wp); + draw_hsep_win(wp); + draw_sep_connectors_win(wp); } syn_set_timeout(NULL); @@ -1811,7 +1782,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)); @@ -1946,7 +1917,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; @@ -1985,6 +1956,38 @@ static size_t fill_foldcolumn(char_u *p, win_T *wp, foldinfo_T foldinfo, linenr_ return MAX(char_counter + (fdc-i), (size_t)fdc); } +static inline void provider_err_virt_text(linenr_T lnum, char *err) +{ + Decoration err_decor = DECORATION_INIT; + int hl_err = syn_check_group(S_LEN("ErrorMsg")); + kv_push(err_decor.virt_text, + ((VirtTextChunk){ .text = provider_err, + .hl_id = hl_err })); + err_decor.virt_text_width = mb_string2cells((char_u *)err); + decor_add_ephemeral(lnum-1, 0, lnum-1, 0, &err_decor); +} + +static inline void get_line_number_str(win_T *wp, linenr_T lnum, char_u *buf, size_t buf_len) +{ + long num; + char *fmt = "%*ld "; + + if (wp->w_p_nu && !wp->w_p_rnu) { + // 'number' + 'norelativenumber' + num = (long)lnum; + } else { + // 'relativenumber', don't use negative numbers + num = labs((long)get_cursor_rel_lnum(wp, lnum)); + if (num == 0 && wp->w_p_nu && wp->w_p_rnu) { + // 'number' + 'relativenumber' + num = lnum; + fmt = "%-*ld "; + } + } + + snprintf((char *)buf, buf_len, fmt, number_width(wp), num); +} + /// Display line "lnum" of window 'wp' on the screen. /// wp->w_virtcol needs to be valid. /// @@ -2000,7 +2003,7 @@ static size_t fill_foldcolumn(char_u *p, win_T *wp, foldinfo_T foldinfo, linenr_ /// /// @return the number of last row the line occupies. static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool nochange, - bool number_only, foldinfo_T foldinfo, Providers *providers) + bool number_only, foldinfo_T foldinfo, DecorProviders *providers) { int c = 0; // init for GCC long vcol = 0; // virtual column (for tabs) @@ -2097,13 +2100,6 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool noc int line_attr_save; int line_attr_lowprio = 0; // low-priority attribute for the line int line_attr_lowprio_save; - matchitem_T *cur; // points to the match list - match_T *shl; // points to search_hl or a match - bool shl_flag; // flag to indicate whether search_hl - // has been processed or not - bool prevcol_hl_flag; // flag to indicate whether prevcol - // equals startcol of search_hl or one - // of the matches int prev_c = 0; // previous Arabic character int prev_c1 = 0; // first composing char for prev_c @@ -2125,13 +2121,13 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool noc // draw_state: items that are drawn in sequence: #define WL_START 0 // nothing done yet -#define WL_CMDLINE WL_START + 1 // cmdline window column -#define WL_FOLD WL_CMDLINE + 1 // 'foldcolumn' -#define WL_SIGN WL_FOLD + 1 // column for signs -#define WL_NR WL_SIGN + 1 // line number -#define WL_BRI WL_NR + 1 // 'breakindent' -#define WL_SBR WL_BRI + 1 // 'showbreak' or 'diff' -#define WL_LINE WL_SBR + 1 // text in the line +#define WL_CMDLINE (WL_START + 1) // cmdline window column +#define WL_FOLD (WL_CMDLINE + 1) // 'foldcolumn' +#define WL_SIGN (WL_FOLD + 1) // column for signs +#define WL_NR (WL_SIGN + 1) // line number +#define WL_BRI (WL_NR + 1) // 'breakindent' +#define WL_SBR (WL_BRI + 1) // 'showbreak' or 'diff' +#define WL_LINE (WL_SBR + 1) // text in the line int draw_state = WL_START; // what to draw next int syntax_flags = 0; @@ -2169,7 +2165,8 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool noc // To speed up the loop below, set extra_check when there is linebreak, // trailing white space and/or syntax processing to be done. extra_check = wp->w_p_lbr; - if (syntax_present(wp) && !wp->w_s->b_syn_error && !wp->w_s->b_syn_slow) { + if (syntax_present(wp) && !wp->w_s->b_syn_error && !wp->w_s->b_syn_slow + && !has_fold && !end_fill) { // Prepare for syntax highlighting in this line. When there is an // error, stop syntax highlighting. save_did_emsg = did_emsg; @@ -2186,37 +2183,14 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool noc } } - has_decor = decor_redraw_line(wp->w_buffer, lnum-1, - &decor_state); + has_decor = decor_redraw_line(buf, lnum-1, &decor_state); - for (size_t k = 0; k < kv_size(*providers); k++) { - DecorProvider *p = kv_A(*providers, k); - if (p && p->redraw_line != LUA_NOREF) { - FIXED_TEMP_ARRAY(args, 3); - args.items[0] = WINDOW_OBJ(wp->handle); - args.items[1] = BUFFER_OBJ(buf->handle); - args.items[2] = INTEGER_OBJ(lnum-1); - if (provider_invoke(p->ns_id, "line", p->redraw_line, args, true)) { - has_decor = true; - } else { - // return 'false' or error: skip rest of this window - kv_A(*providers, k) = NULL; - } - - win_check_ns_hl(wp); - } - } + providers_invoke_line(wp, providers, lnum-1, &has_decor, &provider_err); if (provider_err) { - Decoration err_decor = DECORATION_INIT; - int hl_err = syn_check_group(S_LEN("ErrorMsg")); - kv_push(err_decor.virt_text, - ((VirtTextChunk){ .text = provider_err, - .hl_id = hl_err })); - err_decor.virt_text_width = mb_string2cells((char_u *)provider_err); - decor_add_ephemeral(lnum-1, 0, lnum-1, 0, &err_decor); - provider_err = NULL; + provider_err_virt_text(lnum, provider_err); has_decor = true; + provider_err = NULL; } if (has_decor) { @@ -2411,12 +2385,11 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool noc } area_highlighting = true; } - // Update w_last_cursorline even if Visual mode is active. - wp->w_last_cursorline = wp->w_cursor.lnum; } memset(sattrs, 0, sizeof(sattrs)); num_signs = buf_get_signattrs(wp->w_buffer, lnum, sattrs); + decor_redraw_signs(buf, lnum-1, &num_signs, sattrs); // If this line has a sign with line highlighting set line_attr. // TODO(bfredl, vigoux): this should not take priority over decoration! @@ -2623,69 +2596,12 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool noc } } - /* - * Handle highlighting the last used search pattern and matches. - * Do this for both search_hl and the match list. - */ - cur = wp->w_match_head; - shl_flag = false; - while ((cur != NULL || !shl_flag) && !number_only - && !has_fold && !end_fill) { - if (!shl_flag) { - shl = &search_hl; - shl_flag = true; - } else { - shl = &cur->hl; // -V595 - } - shl->startcol = MAXCOL; - shl->endcol = MAXCOL; - shl->attr_cur = 0; - shl->is_addpos = false; - v = (ptr - line); - if (cur != NULL) { - cur->pos.cur = 0; - } - next_search_hl(wp, shl, lnum, (colnr_T)v, - shl == &search_hl ? NULL : cur); - if (wp->w_s->b_syn_slow) { - has_syntax = false; - } - - // Need to get the line again, a multi-line regexp may have made it - // invalid. - line = ml_get_buf(wp->w_buffer, lnum, false); - ptr = line + v; - - if (shl->lnum != 0 && shl->lnum <= lnum) { - if (shl->lnum == lnum) { - shl->startcol = shl->rm.startpos[0].col; - } else { - shl->startcol = 0; - } - if (lnum == shl->lnum + shl->rm.endpos[0].lnum - - shl->rm.startpos[0].lnum) { - shl->endcol = shl->rm.endpos[0].col; - } else { - shl->endcol = MAXCOL; - } - // Highlight one character for an empty match. - if (shl->startcol == shl->endcol) { - if (line[shl->endcol] != NUL) { - shl->endcol += utfc_ptr2len(line + shl->endcol); - } else { - ++shl->endcol; - } - } - if ((long)shl->startcol < v) { // match at leftcol - shl->attr_cur = shl->attr; - search_attr = shl->attr; - search_attr_from_match = shl != &search_hl; - } - area_highlighting = true; - } - if (shl != &search_hl && cur != NULL) { - cur = cur->next; - } + if (!number_only && !has_fold && !end_fill) { + v = ptr - line; + area_highlighting |= prepare_search_hl_line(wp, lnum, (colnr_T)v, + &line, &search_hl, &search_attr, + &search_attr_from_match); + ptr = line + v; // "line" may have been updated } unsigned off = 0; // Offset relative start of line @@ -2709,6 +2625,8 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool noc // Repeat for the whole displayed line. for (;;) { int has_match_conc = 0; ///< match wants to conceal + int decor_conceal = 0; + bool did_decrement_ptr = false; // Skip this quickly when working on the text. @@ -2744,7 +2662,11 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool noc p_extra = p_extra_free; c_extra = NUL; c_final = NUL; - char_attr = win_hl_attr(wp, HLF_FC); + if (use_cursor_line_sign(wp, lnum)) { + char_attr = win_hl_attr(wp, HLF_CLF); + } else { + char_attr = win_hl_attr(wp, HLF_FC); + } } } @@ -2753,13 +2675,17 @@ 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) { - get_sign_display_info(false, wp, sattrs, row, - startrow, filler_lines, filler_todo, count, + if (wp->w_scwidth > 0) { + get_sign_display_info(false, wp, lnum, sattrs, row, + startrow, filler_lines, filler_todo, &c_extra, &c_final, extra, sizeof(extra), - &p_extra, &n_extra, - &char_attr, &draw_state, &sign_idx); + &p_extra, &n_extra, &char_attr, sign_idx); + sign_idx++; + if (sign_idx < wp->w_scwidth) { + draw_state = WL_SIGN - 1; + } else { + sign_idx = 0; + } } } @@ -2774,34 +2700,15 @@ 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); - get_sign_display_info(true, wp, sattrs, row, - startrow, filler_lines, filler_todo, count, + && 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, &c_extra, &c_final, extra, sizeof(extra), - &p_extra, &n_extra, - &char_attr, &draw_state, &sign_idx); + &p_extra, &n_extra, &char_attr, sign_idx); } else { + // Draw the line number (empty space after wrapping). if (row == startrow + filler_lines) { - // Draw the line number (empty space after wrapping). */ - long num; - char *fmt = "%*ld "; - - if (wp->w_p_nu && !wp->w_p_rnu) { - // 'number' + 'norelativenumber' - num = (long)lnum; - } else { - // 'relativenumber', don't use negative numbers - num = labs((long)get_cursor_rel_lnum(wp, lnum)); - if (num == 0 && wp->w_p_nu && wp->w_p_rnu) { - // 'number' + 'relativenumber' - num = lnum; - fmt = "%-*ld "; - } - } - - snprintf((char *)extra, sizeof(extra), - fmt, number_width(wp), num); + get_line_number_str(wp, lnum, (char_u *)extra, sizeof(extra)); if (wp->w_skipcol > 0) { for (p_extra = extra; *p_extra == ' '; p_extra++) { *p_extra = '-'; @@ -2819,41 +2726,12 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool noc } p_extra = extra; c_extra = NUL; - c_final = NUL; } else { c_extra = ' '; - c_final = NUL; } + c_final = NUL; n_extra = number_width(wp) + 1; - char_attr = win_hl_attr(wp, HLF_N); - - if (wp->w_p_rnu && lnum < wp->w_cursor.lnum) { - // Use LineNrAbove - char_attr = win_hl_attr(wp, HLF_LNA); - } - if (wp->w_p_rnu && lnum > wp->w_cursor.lnum) { - // Use LineNrBelow - char_attr = win_hl_attr(wp, HLF_LNB); - } - - sign_attrs_T *num_sattr = sign_get_attr(SIGN_NUMHL, sattrs, 0, 1); - if (num_sattr != NULL) { - // :sign defined with "numhl" highlight. - char_attr = num_sattr->sat_numhl; - } else if (wp->w_p_cul - && lnum == wp->w_cursor.lnum - && (wp->w_p_culopt_flags & CULOPT_NBR) - && (row == startrow - || wp->w_p_culopt_flags & CULOPT_LINE) - && filler_todo == 0) { - // When 'cursorline' is set highlight the line number of - // the current line differently. - // When 'cursorlineopt' has "screenline" only highlight - // the line number itself. - // TODO(vim): Can we use CursorLine instead of CursorLineNr - // when CursorLineNr isn't set? - char_attr = win_hl_attr(wp, HLF_CLN); - } + char_attr = get_line_number_attr(wp, lnum, row, startrow, filler_lines, sattrs); } } } @@ -3078,115 +2956,13 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool noc } if (!n_extra) { - /* - * Check for start/end of search pattern match. - * After end, check for start/end of next match. - * When another match, have to check for start again. - * Watch out for matching an empty string! - * Do this for 'search_hl' and the match list (ordered by - * priority). - */ + // Check for start/end of 'hlsearch' and other matches. + // After end, check for start/end of next match. + // When another match, have to check for start again. v = (ptr - line); - cur = wp->w_match_head; - shl_flag = false; - while (cur != NULL || !shl_flag) { - if (!shl_flag - && (cur == NULL || cur->priority > SEARCH_HL_PRIORITY)) { - shl = &search_hl; - shl_flag = true; - } else { - shl = &cur->hl; - } - if (cur != NULL) { - cur->pos.cur = 0; - } - bool pos_inprogress = true; // mark that a position match search is - // in progress - while (shl->rm.regprog != NULL - || (cur != NULL && pos_inprogress)) { - if (shl->startcol != MAXCOL - && v >= (long)shl->startcol - && v < (long)shl->endcol) { - int tmp_col = v + utfc_ptr2len(ptr); - - if (shl->endcol < tmp_col) { - shl->endcol = tmp_col; - } - shl->attr_cur = shl->attr; - // Match with the "Conceal" group results in hiding - // the match. - if (cur != NULL - && shl != &search_hl - && syn_name2id("Conceal") == cur->hlg_id) { - has_match_conc = v == (long)shl->startcol ? 2 : 1; - match_conc = cur->conceal_char; - } else { - has_match_conc = 0; - } - } else if (v == (long)shl->endcol) { - shl->attr_cur = 0; - - next_search_hl(wp, shl, lnum, (colnr_T)v, - shl == &search_hl ? NULL : cur); - pos_inprogress = !(cur == NULL || cur->pos.cur == 0); - - // Need to get the line again, a multi-line regexp - // may have made it invalid. - line = ml_get_buf(wp->w_buffer, lnum, false); - ptr = line + v; - - if (shl->lnum == lnum) { - shl->startcol = shl->rm.startpos[0].col; - if (shl->rm.endpos[0].lnum == 0) { - shl->endcol = shl->rm.endpos[0].col; - } else { - shl->endcol = MAXCOL; - } - - if (shl->startcol == shl->endcol) { - // highlight empty match, try again after it - shl->endcol += utfc_ptr2len(line + shl->endcol); - } - - // Loop to check if the match starts at the - // current position - continue; - } - } - break; - } - if (shl != &search_hl && cur != NULL) { - cur = cur->next; - } - } - - /* Use attributes from match with highest priority among - * 'search_hl' and the match list. */ - search_attr_from_match = false; - search_attr = search_hl.attr_cur; - cur = wp->w_match_head; - shl_flag = false; - while (cur != NULL || !shl_flag) { - if (!shl_flag - && (cur == NULL || cur->priority > SEARCH_HL_PRIORITY)) { - shl = &search_hl; - shl_flag = true; - } else { - shl = &cur->hl; - } - if (shl->attr_cur != 0) { - search_attr = shl->attr_cur; - search_attr_from_match = shl != &search_hl; - } - if (shl != &search_hl && cur != NULL) { - cur = cur->next; - } - } - // Only highlight one character after the last column. - if (*ptr == NUL - && (wp->w_p_list && lcs_eol_one == -1)) { - search_attr = 0; - } + search_attr = update_search_hl(wp, lnum, (colnr_T)v, &line, &search_hl, &has_match_conc, + &match_conc, lcs_eol_one, &search_attr_from_match); + ptr = line + v; // "line" may have been changed // Do not allow a conceal over EOL otherwise EOL will be missed // and bad things happen. @@ -3447,6 +3223,10 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool noc did_emsg = save_did_emsg; } + if (wp->w_s->b_syn_slow) { + has_syntax = false; + } + // Need to get the line again, a multi-line regexp may // have made it invalid. line = ml_get_buf(wp->w_buffer, lnum, false); @@ -3569,6 +3349,11 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool noc char_attr = hl_combine_attr(extmark_attr, char_attr); } } + + decor_conceal = decor_state.conceal; + if (decor_conceal && decor_state.conceal_char) { + decor_conceal = 2; // really?? + } } // Found last space before word: check for line break. @@ -3702,10 +3487,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; } @@ -3722,13 +3510,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; @@ -3753,7 +3539,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; @@ -3871,19 +3657,25 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool noc if (wp->w_p_cole > 0 && (wp != curwin || lnum != wp->w_cursor.lnum || conceal_cursor_line(wp)) - && ((syntax_flags & HL_CONCEAL) != 0 || has_match_conc > 0) + && ((syntax_flags & HL_CONCEAL) != 0 || has_match_conc > 0 || decor_conceal > 0) && !(lnum_in_visual_area && vim_strchr(wp->w_p_cocu, 'v') == NULL)) { char_attr = conceal_attr; - if ((prev_syntax_id != syntax_seqnr || has_match_conc > 1) + if ((prev_syntax_id != syntax_seqnr || has_match_conc > 1 || decor_conceal > 1) && (syn_get_sub_char() != NUL || (has_match_conc && match_conc) + || (decor_conceal && decor_state.conceal_char) || wp->w_p_cole == 1) && wp->w_p_cole != 3) { // First time at this concealed item: display one // character. if (has_match_conc && match_conc) { c = match_conc; + } else if (decor_conceal && decor_state.conceal_char) { + c = decor_state.conceal_char; + if (decor_state.conceal_attr) { + char_attr = decor_state.conceal_attr; + } } else if (syn_get_sub_char() != NUL) { c = syn_get_sub_char(); } else if (wp->w_p_lcs_chars.conceal != NUL) { @@ -3988,30 +3780,15 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool noc // At end of the text line or just after the last character. if (c == NUL && eol_hl_off == 0) { - long prevcol = (ptr - line) - 1; - - // we're not really at that column when skipping some text - if ((long)(wp->w_p_wrap ? wp->w_skipcol : wp->w_leftcol) > prevcol) { - prevcol++; - } + // flag to indicate whether prevcol equals startcol of search_hl or + // one of the matches + bool prevcol_hl_flag = get_prevcol_hl_flag(wp, &search_hl, + (long)(ptr - line) - 1); // Invert at least one char, used for Visual and empty line or // highlight match at end of line. If it's beyond the last // char on the screen, just overwrite that one (tricky!) Not // needed when a '$' was displayed for 'list'. - prevcol_hl_flag = false; - if (!search_hl.is_addpos && prevcol == (long)search_hl.startcol) { - prevcol_hl_flag = true; - } else { - cur = wp->w_match_head; - while (cur != NULL) { - if (!cur->hl.is_addpos && prevcol == (long)cur->hl.startcol) { - prevcol_hl_flag = true; - break; - } - cur = cur->next; - } - } if (wp->w_p_lcs_chars.eol == lcs_eol_one && ((area_attr != 0 && vcol == fromcol && (VIsual_mode != Ctrl_V @@ -4042,25 +3819,7 @@ static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, bool noc if (area_attr == 0 && !has_fold) { // Use attributes from match with highest priority among // 'search_hl' and the match list. - char_attr = search_hl.attr; - cur = wp->w_match_head; - shl_flag = false; - while (cur != NULL || !shl_flag) { - if (!shl_flag - && (cur == NULL || cur->priority > SEARCH_HL_PRIORITY)) { - shl = &search_hl; - shl_flag = true; - } else { - shl = &cur->hl; - } - if ((ptr - line) - 1 == (long)shl->startcol - && (shl == &search_hl || !shl->is_addpos)) { - char_attr = shl->attr; - } - if (shl != &search_hl && cur != NULL) { - cur = cur->next; - } - } + get_search_match_hl(wp, &search_hl, (long)(ptr - line), &char_attr); } int eol_attr = char_attr; @@ -4112,7 +3871,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)) { @@ -4214,6 +3973,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 @@ -4276,7 +4036,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--; } @@ -4409,7 +4169,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) @@ -4572,6 +4333,9 @@ static int draw_virt_text_item(buf_T *buf, int col, VirtText vt, HlMode hl_mode, break; } } + if (!*s.p) { + continue; + } int attr; bool through = false; if (hl_mode == kHlModeCombine) { @@ -4615,18 +4379,73 @@ void screen_adjust_grid(ScreenGrid **grid, int *row_off, int *col_off) } } +// Return true if CursorLineSign highlight is to be used. +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); +} + +/// Return true if CursorLineNr highlight is to be used for the number column. +/// +/// - 'cursorline' must be set +/// - lnum must be the cursor line +/// - 'cursorlineopt' has "number" +/// - don't highlight filler lines (when in diff mode) +/// - When line is wrapped and 'cursorlineopt' does not have "line", only highlight the line number +/// itself on the first screenline of the wrapped line, otherwise highlight the number column of +/// all screenlines of the wrapped line. +static bool use_cursor_line_nr(win_T *wp, linenr_T lnum, int row, int startrow, int filler_lines) +{ + return wp->w_p_cul + && lnum == wp->w_cursor.lnum + && (wp->w_p_culopt_flags & CULOPT_NBR) + && (row == startrow + filler_lines + || (row > startrow + filler_lines + && (wp->w_p_culopt_flags & CULOPT_LINE))); +} + +static int get_line_number_attr(win_T *wp, linenr_T lnum, int row, int startrow, int filler_lines, + sign_attrs_T *sattrs) +{ + sign_attrs_T *num_sattr = sign_get_attr(SIGN_NUMHL, sattrs, 0, 1); + if (num_sattr != NULL) { + // :sign defined with "numhl" highlight. + return num_sattr->sat_numhl; + } + + if (wp->w_p_rnu) { + if (lnum < wp->w_cursor.lnum) { + // Use LineNrAbove + return win_hl_attr(wp, HLF_LNA); + } + if (lnum > wp->w_cursor.lnum) { + // Use LineNrBelow + return win_hl_attr(wp, HLF_LNB); + } + } + + if (use_cursor_line_nr(wp, lnum, row, startrow, filler_lines)) { + // TODO(vim): Can we use CursorLine instead of CursorLineNr + // when CursorLineNr isn't set? + return win_hl_attr(wp, HLF_CLN); + } + + return win_hl_attr(wp, HLF_N); +} + // Get information needed to display the sign in line 'lnum' in window 'wp'. // If 'nrcol' is TRUE, the sign is going to be displayed in the number column. // Otherwise the sign is going to be displayed in the sign column. // // @param count max number of signs // @param[out] n_extrap number of characters from pp_extra to display -// @param[in, out] sign_idxp Index of the displayed sign -static void get_sign_display_info(bool nrcol, win_T *wp, sign_attrs_T sattrs[], int row, - int startrow, int filler_lines, int filler_todo, int count, +// @param 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 *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) + char_u **pp_extra, int *n_extrap, int *char_attrp, int sign_idx) { // Draw cells with the sign value or blank. *c_extrap = ' '; @@ -4634,12 +4453,16 @@ static void get_sign_display_info(bool nrcol, win_T *wp, sign_attrs_T sattrs[], if (nrcol) { *n_extrap = number_width(wp) + 1; } else { - *char_attrp = win_hl_attr(wp, HLF_SC); + if (use_cursor_line_sign(wp, lnum)) { + *char_attrp = win_hl_attr(wp, HLF_CLS); + } else { + *char_attrp = win_hl_attr(wp, HLF_SC); + } *n_extrap = win_signcol_width(wp); } if (row == startrow + filler_lines && filler_todo <= 0) { - sign_attrs_T *sattr = sign_get_attr(SIGN_TEXT, sattrs, *sign_idxp, count); + sign_attrs_T *sattr = sign_get_attr(SIGN_TEXT, sattrs, sign_idx, wp->w_scwidth); if (sattr != NULL) { *pp_extra = sattr->sat_text; if (*pp_extra != NULL) { @@ -4674,15 +4497,13 @@ static void get_sign_display_info(bool nrcol, win_T *wp, sign_attrs_T sattrs[], (*pp_extra)[*n_extrap] = NUL; } } - *char_attrp = sattr->sat_texthl; - } - } - (*sign_idxp)++; - if (*sign_idxp < count) { - *draw_statep = WL_SIGN - 1; - } else { - *sign_idxp = 0; + if (use_cursor_line_sign(wp, lnum) && sattr->sat_culhl > 0) { + *char_attrp = sattr->sat_culhl; + } else { + *char_attrp = sattr->sat_texthl; + } + } } } @@ -4799,9 +4620,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) @@ -4899,10 +4720,15 @@ void rl_mirror(char_u *str) */ void status_redraw_all(void) { - FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - if (wp->w_status_height) { - wp->w_redr_status = true; - redraw_later(wp, VALID); + if (global_stl_height()) { + curwin->w_redr_status = true; + redraw_later(curwin, VALID); + } else { + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + if (wp->w_status_height) { + wp->w_redr_status = true; + redraw_later(wp, VALID); + } } } } @@ -4916,10 +4742,15 @@ void status_redraw_curbuf(void) /// Marks all status lines of the specified buffer for redraw. void status_redraw_buf(buf_T *buf) { - FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - if (wp->w_status_height != 0 && wp->w_buffer == buf) { - wp->w_redr_status = true; - redraw_later(wp, VALID); + if (global_stl_height() != 0 && curwin->w_buffer == buf) { + curwin->w_redr_status = true; + redraw_later(curwin, VALID); + } else { + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + if (wp->w_status_height != 0 && wp->w_buffer == buf) { + wp->w_redr_status = true; + redraw_later(wp, VALID); + } } } } @@ -4961,10 +4792,8 @@ void win_redraw_last_status(const frame_T *frp) } } -/* - * Draw the verticap separator right of window "wp" starting with line "row". - */ -static void draw_vsep_win(win_T *wp, int row) +/// Draw the vertical separator right of window "wp" +static void draw_vsep_win(win_T *wp) { int hl; int c; @@ -4972,15 +4801,97 @@ static void draw_vsep_win(win_T *wp, int row) if (wp->w_vsep_width) { // draw the vertical separator right of this window c = fillchar_vsep(wp, &hl); - grid_fill(&default_grid, wp->w_winrow + row, W_ENDROW(wp), + grid_fill(&default_grid, wp->w_winrow, W_ENDROW(wp), W_ENDCOL(wp), W_ENDCOL(wp) + 1, c, ' ', hl); } } +/// Draw the horizontal separator below window "wp" +static void draw_hsep_win(win_T *wp) +{ + int hl; + int c; -/* - * Get the length of an item as it will be shown in the status line. - */ + if (wp->w_hsep_height) { + // draw the horizontal separator below this window + c = fillchar_hsep(wp, &hl); + grid_fill(&default_grid, W_ENDROW(wp), W_ENDROW(wp) + 1, + wp->w_wincol, W_ENDCOL(wp), c, c, hl); + } +} + +/// Get the separator connector for specified window corner of window "wp" +static int get_corner_sep_connector(win_T *wp, WindowCorner corner) +{ + // It's impossible for windows to be connected neither vertically nor horizontally + // So if they're not vertically connected, assume they're horizontally connected + if (vsep_connected(wp, corner)) { + if (hsep_connected(wp, corner)) { + return wp->w_p_fcs_chars.verthoriz; + } else if (corner == WC_TOP_LEFT || corner == WC_BOTTOM_LEFT) { + return wp->w_p_fcs_chars.vertright; + } else { + return wp->w_p_fcs_chars.vertleft; + } + } else if (corner == WC_TOP_LEFT || corner == WC_TOP_RIGHT) { + return wp->w_p_fcs_chars.horizdown; + } else { + return wp->w_p_fcs_chars.horizup; + } +} + +/// Draw seperator connecting characters on the corners of window "wp" +static void draw_sep_connectors_win(win_T *wp) +{ + // Don't draw separator connectors unless global statusline is enabled and the window has + // either a horizontal or vertical separator + if (global_stl_height() == 0 || !(wp->w_hsep_height == 1 || wp->w_vsep_width == 1)) { + return; + } + + int hl = win_hl_attr(wp, HLF_C); + + // Determine which edges of the screen the window is located on so we can avoid drawing separators + // on corners contained in those edges + bool win_at_top; + bool win_at_bottom = wp->w_hsep_height == 0; + bool win_at_left; + bool win_at_right = wp->w_vsep_width == 0; + frame_T *frp; + + for (frp = wp->w_frame; frp->fr_parent != NULL; frp = frp->fr_parent) { + if (frp->fr_parent->fr_layout == FR_COL && frp->fr_prev != NULL) { + break; + } + } + win_at_top = frp->fr_parent == NULL; + for (frp = wp->w_frame; frp->fr_parent != NULL; frp = frp->fr_parent) { + if (frp->fr_parent->fr_layout == FR_ROW && frp->fr_prev != NULL) { + break; + } + } + win_at_left = frp->fr_parent == NULL; + + // Draw the appropriate separator connector in every corner where drawing them is necessary + if (!(win_at_top || win_at_left)) { + grid_putchar(&default_grid, get_corner_sep_connector(wp, WC_TOP_LEFT), + wp->w_winrow - 1, wp->w_wincol - 1, hl); + } + if (!(win_at_top || win_at_right)) { + grid_putchar(&default_grid, get_corner_sep_connector(wp, WC_TOP_RIGHT), + wp->w_winrow - 1, W_ENDCOL(wp), hl); + } + if (!(win_at_bottom || win_at_left)) { + grid_putchar(&default_grid, get_corner_sep_connector(wp, WC_BOTTOM_LEFT), + W_ENDROW(wp), wp->w_wincol - 1, hl); + } + if (!(win_at_bottom || win_at_right)) { + grid_putchar(&default_grid, get_corner_sep_connector(wp, WC_BOTTOM_RIGHT), + W_ENDROW(wp), W_ENDCOL(wp), hl); + } +} + +/// Get the length of an item as it will be shown in the status line. static int status_match_len(expand_T *xp, char_u *s) { int len = 0; @@ -5181,7 +5092,7 @@ void win_redr_status_matches(expand_T *xp, int num_matches, char_u **matches, in // Create status line if needed by setting 'laststatus' to 2. // Set 'winminheight' to zero to avoid that the window is // resized. - if (lastwin->w_status_height == 0) { + if (lastwin->w_status_height == 0 && global_stl_height() == 0) { save_p_ls = p_ls; save_p_wmh = p_wmh; p_ls = 2; @@ -5217,12 +5128,15 @@ void win_redr_status_matches(expand_T *xp, int num_matches, char_u **matches, in static void win_redr_status(win_T *wp) { int row; + int col; char_u *p; int len; int fillchar; int attr; + int width; int this_ru_col; - static int busy = FALSE; + bool is_stl_global = global_stl_height() > 0; + static int busy = false; // May get here recursively when 'statusline' (indirectly) // invokes ":redrawstatus". Simply ignore the call then. @@ -5233,9 +5147,9 @@ static void win_redr_status(win_T *wp) } busy = true; - wp->w_redr_status = FALSE; - if (wp->w_status_height == 0) { - // no status line, can only be last window + wp->w_redr_status = false; + if (wp->w_status_height == 0 && !(is_stl_global && wp == curwin)) { + // no status line, either global statusline is enabled or the window is a last window redraw_cmdline = true; } else if (!redrawing()) { // Don't redraw right now, do it later. Don't update status line when @@ -5246,6 +5160,7 @@ static void win_redr_status(win_T *wp) redraw_custom_statusline(wp); } else { fillchar = fillchar_status(&attr, wp); + width = is_stl_global ? Columns : wp->w_width; get_trans_bufname(wp->w_buffer); p = NameBuff; @@ -5274,9 +5189,9 @@ static void win_redr_status(win_T *wp) // len += (int)STRLEN(p + len); // dead assignment } - this_ru_col = ru_col - (Columns - wp->w_width); - if (this_ru_col < (wp->w_width + 1) / 2) { - this_ru_col = (wp->w_width + 1) / 2; + this_ru_col = ru_col - (Columns - width); + if (this_ru_col < (width + 1) / 2) { + this_ru_col = (width + 1) / 2; } if (this_ru_col <= 1) { p = (char_u *)"<"; // No room for file name! @@ -5301,10 +5216,11 @@ static void win_redr_status(win_T *wp) } } - row = W_ENDROW(wp); - grid_puts(&default_grid, p, row, wp->w_wincol, attr); - grid_fill(&default_grid, row, row + 1, len + wp->w_wincol, - this_ru_col + wp->w_wincol, fillchar, fillchar, attr); + row = is_stl_global ? (Rows - p_ch - 1) : W_ENDROW(wp); + col = is_stl_global ? 0 : wp->w_wincol; + grid_puts(&default_grid, p, row, col, attr); + grid_fill(&default_grid, row, row + 1, len + col, + this_ru_col + col, fillchar, fillchar, attr); if (get_keymap_str(wp, (char_u *)"<%s>", NameBuff, MAXPATHL) && this_ru_col - len > (int)(STRLEN(NameBuff) + 1)) { @@ -5383,6 +5299,76 @@ bool stl_connected(win_T *wp) return false; } +/// Check if horizontal separator of window "wp" at specified window corner is connected to the +/// horizontal separator of another window +/// Assumes global statusline is enabled +static bool hsep_connected(win_T *wp, WindowCorner corner) +{ + bool before = (corner == WC_TOP_LEFT || corner == WC_BOTTOM_LEFT); + int sep_row = (corner == WC_TOP_LEFT || corner == WC_TOP_RIGHT) + ? wp->w_winrow - 1 : W_ENDROW(wp); + frame_T *fr = wp->w_frame; + + while (fr->fr_parent != NULL) { + if (fr->fr_parent->fr_layout == FR_ROW && (before ? fr->fr_prev : fr->fr_next) != NULL) { + fr = before ? fr->fr_prev : fr->fr_next; + break; + } + fr = fr->fr_parent; + } + if (fr->fr_parent == NULL) { + return false; + } + while (fr->fr_layout != FR_LEAF) { + fr = fr->fr_child; + if (fr->fr_parent->fr_layout == FR_ROW && before) { + while (fr->fr_next != NULL) { + fr = fr->fr_next; + } + } else { + while (fr->fr_next != NULL && frame2win(fr)->w_winrow + fr->fr_height < sep_row) { + fr = fr->fr_next; + } + } + } + + return (sep_row == fr->fr_win->w_winrow - 1 || sep_row == W_ENDROW(fr->fr_win)); +} + +/// Check if vertical separator of window "wp" at specified window corner is connected to the +/// vertical separator of another window +static bool vsep_connected(win_T *wp, WindowCorner corner) +{ + bool before = (corner == WC_TOP_LEFT || corner == WC_TOP_RIGHT); + int sep_col = (corner == WC_TOP_LEFT || corner == WC_BOTTOM_LEFT) + ? wp->w_wincol - 1 : W_ENDCOL(wp); + frame_T *fr = wp->w_frame; + + while (fr->fr_parent != NULL) { + if (fr->fr_parent->fr_layout == FR_COL && (before ? fr->fr_prev : fr->fr_next) != NULL) { + fr = before ? fr->fr_prev : fr->fr_next; + break; + } + fr = fr->fr_parent; + } + if (fr->fr_parent == NULL) { + return false; + } + while (fr->fr_layout != FR_LEAF) { + fr = fr->fr_child; + if (fr->fr_parent->fr_layout == FR_COL && before) { + while (fr->fr_next != NULL) { + fr = fr->fr_next; + } + } else { + while (fr->fr_next != NULL && frame2win(fr)->w_wincol + fr->fr_width < sep_col) { + fr = fr->fr_next; + } + } + } + + return (sep_col == fr->fr_win->w_wincol - 1 || sep_col == W_ENDCOL(fr->fr_win)); +} /// Get the value to show for the language mappings, active 'keymap'. /// @@ -5449,6 +5435,7 @@ static void win_redr_custom(win_T *wp, bool draw_ruler) int use_sandbox = false; win_T *ewp; int p_crb_save; + bool is_stl_global = global_stl_height() > 0; ScreenGrid *grid = &default_grid; @@ -5470,9 +5457,9 @@ static void win_redr_custom(win_T *wp, bool draw_ruler) maxwidth = Columns; use_sandbox = was_set_insecurely(wp, "tabline", 0); } else { - row = W_ENDROW(wp); + row = is_stl_global ? (Rows - p_ch - 1) : W_ENDROW(wp); fillchar = fillchar_status(&attr, wp); - maxwidth = wp->w_width; + maxwidth = is_stl_global ? Columns : wp->w_width; if (draw_ruler) { stl = p_ruf; @@ -5490,12 +5477,12 @@ static void win_redr_custom(win_T *wp, bool draw_ruler) stl = p_ruf; } } - col = ru_col - (Columns - wp->w_width); - if (col < (wp->w_width + 1) / 2) { - col = (wp->w_width + 1) / 2; + col = ru_col - (Columns - maxwidth); + if (col < (maxwidth + 1) / 2) { + col = (maxwidth + 1) / 2; } - maxwidth = wp->w_width - col; - if (!wp->w_status_height) { + maxwidth = maxwidth - col; + if (!wp->w_status_height && !is_stl_global) { grid = &msg_grid_adj; row = Rows - 1; maxwidth--; // writing in last column may cause scrolling @@ -5513,7 +5500,7 @@ static void win_redr_custom(win_T *wp, bool draw_ruler) use_sandbox = was_set_insecurely(wp, "statusline", *wp->w_p_stl == NUL ? 0 : OPT_LOCAL); } - col += wp->w_wincol; + col += is_stl_global ? 0 : wp->w_wincol; } if (maxwidth <= 0) { @@ -5852,8 +5839,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; @@ -5898,6 +5885,8 @@ void grid_puts_len(ScreenGrid *grid, char_u *text, int textlen, int row, int col // Only 1 cell left, but character requires 2 cells: // display a '>' in the last column to avoid wrapping. */ c = '>'; + u8c = '>'; + u8cc[0] = 0; mbyte_cells = 1; } @@ -5913,9 +5902,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 @@ -5928,6 +5917,13 @@ void grid_puts_len(ScreenGrid *grid, char_u *text, int textlen, int row, int col clear_next_cell = true; } + // When at the start of the text and overwriting the right half of a + // two-cell character in the same grid, truncate that into a '>'. + if (ptr == text && col > 0 && grid->chars[off][0] == 0) { + grid->chars[off - 1][0] = '>'; + grid->chars[off - 1][1] = 0; + } + schar_copy(grid->chars[off], buf); grid->attrs[off] = attr; if (mbyte_cells == 2) { @@ -6007,280 +6003,9 @@ static void end_search_hl(void) } -/* - * Init for calling prepare_search_hl(). - */ -static void init_search_hl(win_T *wp) - FUNC_ATTR_NONNULL_ALL -{ - // Setup for match and 'hlsearch' highlighting. Disable any previous - // match - matchitem_T *cur = wp->w_match_head; - while (cur != NULL) { - cur->hl.rm = cur->match; - if (cur->hlg_id == 0) { - cur->hl.attr = 0; - } else { - cur->hl.attr = syn_id2attr(cur->hlg_id); - } - cur->hl.buf = wp->w_buffer; - cur->hl.lnum = 0; - cur->hl.first_lnum = 0; - // Set the time limit to 'redrawtime'. - cur->hl.tm = profile_setlimit(p_rdt); - cur = cur->next; - } - search_hl.buf = wp->w_buffer; - search_hl.lnum = 0; - search_hl.first_lnum = 0; - search_hl.attr = win_hl_attr(wp, HLF_L); - - // time limit is set at the toplevel, for all windows -} - -/* - * Advance to the match in window "wp" line "lnum" or past it. - */ -static void prepare_search_hl(win_T *wp, linenr_T lnum) - FUNC_ATTR_NONNULL_ALL -{ - matchitem_T *cur; // points to the match list - match_T *shl; // points to search_hl or a match - bool shl_flag; // flag to indicate whether search_hl - // has been processed or not - - // When using a multi-line pattern, start searching at the top - // of the window or just after a closed fold. - // Do this both for search_hl and the match list. - cur = wp->w_match_head; - shl_flag = false; - while (cur != NULL || shl_flag == false) { - if (shl_flag == false) { - shl = &search_hl; - shl_flag = true; - } else { - shl = &cur->hl; // -V595 - } - if (shl->rm.regprog != NULL - && shl->lnum == 0 - && re_multiline(shl->rm.regprog)) { - if (shl->first_lnum == 0) { - for (shl->first_lnum = lnum; - shl->first_lnum > wp->w_topline; - shl->first_lnum--) { - if (hasFoldingWin(wp, shl->first_lnum - 1, NULL, NULL, true, NULL)) { - break; - } - } - } - if (cur != NULL) { - cur->pos.cur = 0; - } - bool pos_inprogress = true; // mark that a position match search is - // in progress - int n = 0; - while (shl->first_lnum < lnum && (shl->rm.regprog != NULL - || (cur != NULL && pos_inprogress))) { - next_search_hl(wp, shl, shl->first_lnum, (colnr_T)n, - shl == &search_hl ? NULL : cur); - pos_inprogress = !(cur == NULL || cur->pos.cur == 0); - if (shl->lnum != 0) { - shl->first_lnum = shl->lnum - + shl->rm.endpos[0].lnum - - shl->rm.startpos[0].lnum; - n = shl->rm.endpos[0].col; - } else { - ++shl->first_lnum; - n = 0; - } - } - } - if (shl != &search_hl && cur != NULL) { - cur = cur->next; - } - } -} - -/// Search for a next 'hlsearch' or match. -/// Uses shl->buf. -/// Sets shl->lnum and shl->rm contents. -/// Note: Assumes a previous match is always before "lnum", unless -/// shl->lnum is zero. -/// Careful: Any pointers for buffer lines will become invalid. -/// -/// @param shl points to search_hl or a match -/// @param mincol minimal column for a match -/// @param cur to retrieve match positions if any -static void next_search_hl(win_T *win, match_T *shl, linenr_T lnum, colnr_T mincol, - matchitem_T *cur) - FUNC_ATTR_NONNULL_ARG(2) -{ - linenr_T l; - colnr_T matchcol; - long nmatched = 0; - int save_called_emsg = called_emsg; - - // for :{range}s/pat only highlight inside the range - if (lnum < search_first_line || lnum > search_last_line) { - shl->lnum = 0; - return; - } - - if (shl->lnum != 0) { - // Check for three situations: - // 1. If the "lnum" is below a previous match, start a new search. - // 2. If the previous match includes "mincol", use it. - // 3. Continue after the previous match. - l = shl->lnum + shl->rm.endpos[0].lnum - shl->rm.startpos[0].lnum; - if (lnum > l) { - shl->lnum = 0; - } else if (lnum < l || shl->rm.endpos[0].col > mincol) { - return; - } - } - - /* - * Repeat searching for a match until one is found that includes "mincol" - * or none is found in this line. - */ - called_emsg = FALSE; - for (;;) { - // Stop searching after passing the time limit. - if (profile_passed_limit(shl->tm)) { - shl->lnum = 0; // no match found in time - break; - } - // Three situations: - // 1. No useful previous match: search from start of line. - // 2. Not Vi compatible or empty match: continue at next character. - // Break the loop if this is beyond the end of the line. - // 3. Vi compatible searching: continue at end of previous match. - if (shl->lnum == 0) { - matchcol = 0; - } else if (vim_strchr(p_cpo, CPO_SEARCH) == NULL - || (shl->rm.endpos[0].lnum == 0 - && shl->rm.endpos[0].col <= shl->rm.startpos[0].col)) { - char_u *ml; - - matchcol = shl->rm.startpos[0].col; - ml = ml_get_buf(shl->buf, lnum, false) + matchcol; - if (*ml == NUL) { - ++matchcol; - shl->lnum = 0; - break; - } - matchcol += utfc_ptr2len(ml); - } else { - matchcol = shl->rm.endpos[0].col; - } - - shl->lnum = lnum; - if (shl->rm.regprog != NULL) { - // Remember whether shl->rm is using a copy of the regprog in - // cur->match. - bool regprog_is_copy = (shl != &search_hl - && cur != NULL - && shl == &cur->hl - && cur->match.regprog == cur->hl.rm.regprog); - int timed_out = false; - - nmatched = vim_regexec_multi(&shl->rm, win, shl->buf, lnum, matchcol, - &(shl->tm), &timed_out); - // Copy the regprog, in case it got freed and recompiled. - if (regprog_is_copy) { - cur->match.regprog = cur->hl.rm.regprog; - } - if (called_emsg || got_int || timed_out) { - // Error while handling regexp: stop using this regexp. - if (shl == &search_hl) { - // don't free regprog in the match list, it's a copy - vim_regfree(shl->rm.regprog); - set_no_hlsearch(true); - } - shl->rm.regprog = NULL; - shl->lnum = 0; - got_int = FALSE; // avoid the "Type :quit to exit Vim" message - break; - } - } else if (cur != NULL) { - nmatched = next_search_hl_pos(shl, lnum, &(cur->pos), matchcol); - } - if (nmatched == 0) { - shl->lnum = 0; // no match found - break; - } - if (shl->rm.startpos[0].lnum > 0 - || shl->rm.startpos[0].col >= mincol - || nmatched > 1 - || shl->rm.endpos[0].col > mincol) { - shl->lnum += shl->rm.startpos[0].lnum; - break; // useful match found - } - - // Restore called_emsg for assert_fails(). - called_emsg = save_called_emsg; - } -} - -/// @param shl points to a match. Fill on match. -/// @param posmatch match positions -/// @param mincol minimal column for a match -/// -/// @return one on match, otherwise return zero. -static int next_search_hl_pos(match_T *shl, linenr_T lnum, posmatch_T *posmatch, colnr_T mincol) - FUNC_ATTR_NONNULL_ALL -{ - int i; - int found = -1; - - shl->lnum = 0; - for (i = posmatch->cur; i < MAXPOSMATCH; i++) { - llpos_T *pos = &posmatch->pos[i]; - - if (pos->lnum == 0) { - break; - } - if (pos->len == 0 && pos->col < mincol) { - continue; - } - if (pos->lnum == lnum) { - if (found >= 0) { - // if this match comes before the one at "found" then swap - // them - if (pos->col < posmatch->pos[found].col) { - llpos_T tmp = *pos; - - *pos = posmatch->pos[found]; - posmatch->pos[found] = tmp; - } - } else { - found = i; - } - } - } - posmatch->cur = 0; - if (found >= 0) { - colnr_T start = posmatch->pos[found].col == 0 - ? 0: posmatch->pos[found].col - 1; - colnr_T end = posmatch->pos[found].col == 0 - ? MAXCOL : start + posmatch->pos[found].len; - - shl->lnum = lnum; - shl->rm.startpos[0].lnum = 0; - shl->rm.startpos[0].col = start; - shl->rm.endpos[0].lnum = 0; - shl->rm.endpos[0].col = end; - shl->is_addpos = true; - posmatch->cur = found + 1; - return 1; - } - return 0; -} - - -/// Fill the grid from 'start_row' to 'end_row', from 'start_col' to 'end_col' -/// with character 'c1' in first column followed by 'c2' in the other columns. -/// Use attributes 'attr'. +/// Fill the grid from "start_row" to "end_row" (exclusive), from "start_col" +/// to "end_col" (exclusive) with character "c1" in first column followed by +/// "c2" in the other columns. Use attributes "attr". void grid_fill(ScreenGrid *grid, int start_row, int end_row, int start_col, int end_col, int c1, int c2, int attr) { @@ -6307,9 +6032,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); @@ -6718,7 +6443,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) @@ -6847,8 +6572,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. @@ -6899,8 +6622,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; } @@ -6930,7 +6651,7 @@ int showmode(void) do_mode = ((p_smd && msg_silent == 0) && ((State & TERM_FOCUS) || (State & INSERT) - || restart_edit + || restart_edit != NUL || VIsual_active)); if (do_mode || reg_recording != 0) { // Don't show mode right now, when not redrawing or inside a mapping. @@ -7010,7 +6731,7 @@ int showmode(void) } msg_puts_attr(_(" INSERT"), attr); } else if (restart_edit == 'I' || restart_edit == 'i' - || restart_edit == 'a') { + || restart_edit == 'a' || restart_edit == 'A') { msg_puts_attr(_(" (insert)"), attr); } else if (restart_edit == 'R') { msg_puts_attr(_(" (replace)"), attr); @@ -7090,10 +6811,10 @@ int showmode(void) clear_showcmd(); } - // If the last window has no status line, the ruler is after the mode - // message and must be redrawn + // If the last window has no status line and global statusline is disabled, + // the ruler is after the mode message and must be redrawn win_T *last = lastwin_nofloating(); - if (redrawing() && last->w_status_height == 0) { + if (redrawing() && last->w_status_height == 0 && global_stl_height() == 0) { win_redr_ruler(last, true); } redraw_cmdline = false; @@ -7282,7 +7003,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) { @@ -7408,16 +7129,22 @@ int fillchar_status(int *attr, win_T *wp) return '='; } -/* - * Get the character to use in a separator between vertically split windows. - * Get its attributes in "*attr". - */ +/// Get the character to use in a separator between vertically split windows. +/// Get its attributes in "*attr". static int fillchar_vsep(win_T *wp, int *attr) { *attr = win_hl_attr(wp, HLF_C); return wp->w_p_fcs_chars.vert; } +/// Get the character to use in a separator between horizontally split windows. +/// Get its attributes in "*attr". +static int fillchar_hsep(win_T *wp, int *attr) +{ + *attr = win_hl_attr(wp, HLF_C); + return wp->w_p_fcs_chars.horiz; +} + /* * Return TRUE if redrawing should currently be done. */ @@ -7443,7 +7170,8 @@ void showruler(bool always) if (!always && !redrawing()) { return; } - if ((*p_stl != NUL || *curwin->w_p_stl != NUL) && curwin->w_status_height) { + if ((*p_stl != NUL || *curwin->w_p_stl != NUL) + && (curwin->w_status_height || global_stl_height())) { redraw_custom_statusline(curwin); } else { win_redr_ruler(curwin, always); @@ -7462,6 +7190,7 @@ void showruler(bool always) static void win_redr_ruler(win_T *wp, bool always) { + bool is_stl_global = global_stl_height() > 0; static bool did_show_ext_ruler = false; // If 'ruler' off or redrawing disabled, don't do anything @@ -7479,7 +7208,7 @@ static void win_redr_ruler(win_T *wp, bool always) // Don't draw the ruler while doing insert-completion, it might overwrite // the (long) mode message. - if (wp == lastwin && lastwin->w_status_height == 0) { + if (wp == lastwin && lastwin->w_status_height == 0 && !is_stl_global) { if (edit_submode != NULL) { return; } @@ -7534,6 +7263,12 @@ static void win_redr_ruler(win_T *wp, bool always) off = wp->w_wincol; width = wp->w_width; part_of_status = true; + } else if (is_stl_global) { + row = Rows - p_ch - 1; + fillchar = fillchar_status(&attr, wp); + off = 0; + width = Columns; + part_of_status = true; } else { row = Rows - 1; fillchar = ' '; @@ -7573,7 +7308,7 @@ static void win_redr_ruler(win_T *wp, bool always) int i = (int)STRLEN(buffer); get_rel_pos(wp, buffer + i + 1, RULER_BUF_LEN - i - 1); int o = i + vim_strsize(buffer + i + 1); - if (wp->w_status_height == 0) { // can't use last char of screen + if (wp->w_status_height == 0 && !is_stl_global) { // can't use last char of screen o++; } int this_ru_col = ru_col - (Columns - width); @@ -7885,4 +7620,3 @@ win_T *get_win_by_grid_handle(handle_T handle) } return NULL; } - diff --git a/src/nvim/screen.h b/src/nvim/screen.h index d704a6eb8a..5e86dacd25 100644 --- a/src/nvim/screen.h +++ b/src/nvim/screen.h @@ -8,12 +8,10 @@ #include "nvim/pos.h" #include "nvim/types.h" -/* - * flags for update_screen() - * The higher the value, the higher the priority - */ -#define VALID 10 /* buffer not changed, or changes marked - with b_mod_* */ +// flags for update_screen() +// The higher the value, the higher the priority +#define VALID 10 // buffer not changed, or changes marked + // with b_mod_* #define INVERTED 20 // redisplay inverted part that changed #define INVERTED_ALL 25 // redisplay whole inverted part #define REDRAW_TOP 30 // display first w_upd_rows screen lines @@ -21,6 +19,14 @@ #define NOT_VALID 40 // buffer needs complete redraw #define CLEAR 50 // screen messed up, clear it +/// corner value flags for hsep_connected and vsep_connected +typedef enum { + WC_TOP_LEFT = 0, + WC_TOP_RIGHT, + WC_BOTTOM_LEFT, + WC_BOTTOM_RIGHT +} WindowCorner; + /// By default, all widows are draw on a single rectangular grid, represented by /// this ScreenGrid instance. In multigrid mode each window will have its own /// grid, then this is only used for global screen elements that hasn't been @@ -59,8 +65,8 @@ extern StlClickDefinition *tab_page_click_defs; /// Size of the tab_page_click_defs array extern long tab_page_click_defs_size; -#define W_ENDCOL(wp) (wp->w_wincol + wp->w_width) -#define W_ENDROW(wp) (wp->w_winrow + wp->w_height) +#define W_ENDCOL(wp) ((wp)->w_wincol + (wp)->w_width) +#define W_ENDROW(wp) ((wp)->w_winrow + (wp)->w_height) #ifdef INCLUDE_GENERATED_DECLARATIONS # include "screen.h.generated.h" diff --git a/src/nvim/search.c b/src/nvim/search.c index f45ae102d2..c30e69391f 100644 --- a/src/nvim/search.c +++ b/src/nvim/search.c @@ -13,6 +13,7 @@ #include "nvim/ascii.h" #include "nvim/buffer.h" +#include "nvim/change.h" #include "nvim/charset.h" #include "nvim/cursor.h" #include "nvim/edit.h" @@ -25,17 +26,18 @@ #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" #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/message.h" -#include "nvim/misc1.h" #include "nvim/mouse.h" #include "nvim/move.h" #include "nvim/normal.h" #include "nvim/option.h" +#include "nvim/os/input.h" #include "nvim/os/time.h" #include "nvim/path.h" #include "nvim/regexp.h" @@ -1069,7 +1071,7 @@ int do_search(oparg_T *oap, int dirc, int search_delim, char_u *pat, long count, * Find out the direction of the search. */ if (dirc == 0) { - dirc = spats[0].off.dir; + dirc = (char_u)spats[0].off.dir; } else { spats[0].off.dir = dirc; set_vv_searchforward(); @@ -1353,6 +1355,10 @@ int do_search(oparg_T *oap, int dirc, int search_delim, char_u *pat, long count, } retval = 1; // pattern found + if (sia && sia->sa_wrapped) { + apply_autocmds(EVENT_SEARCHWRAPPED, NULL, NULL, false, NULL); + } + /* * Add character and/or line offset */ @@ -1808,6 +1814,9 @@ pos_T *findmatchlimit(oparg_T *oap, int initc, int flags, int64_t maxtravel) initc = NUL; } else if (initc != '#' && initc != NUL) { find_mps_values(&initc, &findc, &backwards, true); + if (dir) { + backwards = (dir == FORWARD) ? false : true; + } if (findc == NUL) { return NULL; } @@ -2305,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 @@ -2330,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++; @@ -2340,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; @@ -3427,12 +3436,22 @@ int current_block(oparg_T *oap, long count, int include, int what, int other) // user wants. save_cpo = p_cpo; p_cpo = (char_u *)(vim_strchr(p_cpo, CPO_MATCHBSL) != NULL ? "%M" : "%"); - while (count-- > 0) { - if ((pos = findmatch(NULL, what)) == NULL) { - break; + if ((pos = findmatch(NULL, what)) != NULL) { + while (count-- > 0) { + if ((pos = findmatch(NULL, what)) == NULL) { + break; + } + curwin->w_cursor = *pos; + start_pos = *pos; // the findmatch for end_pos will overwrite *pos + } + } else { + while (count-- > 0) { + if ((pos = findmatchlimit(NULL, what, FM_FORWARD, 0)) == NULL) { + break; + } + curwin->w_cursor = *pos; + start_pos = *pos; // the findmatch for end_pos will overwrite *pos } - curwin->w_cursor = *pos; - start_pos = *pos; // the findmatch for end_pos will overwrite *pos } p_cpo = save_cpo; @@ -4745,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. /// @@ -5140,7 +5689,7 @@ search_line: // we read a line, set "already" to check this "line" later // if depth >= 0 we'll increase files[depth].lnum far - // bellow -- Acevedo + // below -- Acevedo already = aux = p = skipwhite(line); p = find_word_start(p); p = find_word_end(p); @@ -5235,6 +5784,9 @@ search_line: if (depth == -1) { // match in current file if (l_g_do_tagpreview != 0) { + if (!win_valid(curwin_save)) { + break; + } if (!GETFILE_SUCCESS(getfile(curwin_save->w_buffer->b_fnum, NULL, NULL, true, lnum, false))) { break; // failed to jump to file 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/sha256.c b/src/nvim/sha256.c index 9d6a2f2c41..2c6199bf8b 100644 --- a/src/nvim/sha256.c +++ b/src/nvim/sha256.c @@ -73,8 +73,8 @@ static void sha256_process(context_sha256_T *ctx, const char_u data[SHA256_BUFFE GET_UINT32(W[14], data, 56); GET_UINT32(W[15], data, 60); -#define SHR(x, n) ((x & 0xFFFFFFFF) >> n) -#define ROTR(x, n) (SHR(x, n) | (x << (32 - n))) +#define SHR(x, n) (((x) & 0xFFFFFFFF) >> (n)) +#define ROTR(x, n) (SHR(x, n) | ((x) << (32 - (n)))) #define S0(x) (ROTR(x, 7) ^ ROTR(x, 18) ^ SHR(x, 3)) #define S1(x) (ROTR(x, 17) ^ ROTR(x, 19) ^ SHR(x, 10)) @@ -82,17 +82,16 @@ static void sha256_process(context_sha256_T *ctx, const char_u data[SHA256_BUFFE #define S2(x) (ROTR(x, 2) ^ ROTR(x, 13) ^ ROTR(x, 22)) #define S3(x) (ROTR(x, 6) ^ ROTR(x, 11) ^ ROTR(x, 25)) -#define F0(x, y, z) ((x & y) | (z & (x | y))) -#define F1(x, y, z) (z ^ (x & (y ^ z))) +#define F0(x, y, z) (((x) & (y)) | ((z) & ((x) | (y)))) +#define F1(x, y, z) ((z) ^ ((x) & ((y) ^ (z)))) #define R(t) \ - (W[t] = S1(W[t - 2]) + W[t - 7] + \ - S0(W[t - 15]) + W[t - 16]) + (W[t] = S1(W[(t) - 2]) + W[(t) - 7] + S0(W[(t) - 15]) + W[(t) - 16]) #define P(a, b, c, d, e, f, g, h, x, K) { \ - temp1 = h + S3(e) + F1(e, f, g) + K + x; \ + temp1 = (h) + S3(e) + F1(e, f, g) + (K) + (x); \ temp2 = S2(a) + F0(a, b, c); \ - d += temp1; h = temp1 + temp2; \ + (d) += temp1; (h) = temp1 + temp2; \ } A = ctx->state[0]; diff --git a/src/nvim/shada.c b/src/nvim/shada.c index e75a244031..6c0add87d3 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, @@ -1238,7 +1238,7 @@ static void shada_read(ShaDaReadDef *const sd_reader, const int flags) // string is close to useless: you can only use it with :& or :~ and // that’s all because s//~ is not available until the first call to // regtilde. Vim was not calling this for some reason. - (void)(char *)regtilde((char_u *)cur_entry.data.sub_string.sub, p_magic); + (void)(char *)regtilde((char_u *)cur_entry.data.sub_string.sub, p_magic, false); // Do not free shada entry: its allocated memory was saved above. break; case kSDItemHistoryEntry: @@ -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 d8eea7f942..a50b4a5a99 100644 --- a/src/nvim/sign.c +++ b/src/nvim/sign.c @@ -13,6 +13,7 @@ #include "nvim/edit.h" #include "nvim/ex_docmd.h" #include "nvim/fold.h" +#include "nvim/highlight_group.h" #include "nvim/move.h" #include "nvim/option.h" #include "nvim/screen.h" @@ -31,6 +32,7 @@ struct sign { char_u *sn_text; // text used instead of pixmap int sn_line_hl; // highlight ID for line int sn_text_hl; // highlight ID for text + int sn_cul_hl; // highlight ID for text on current line when 'cursorline' is set int sn_num_hl; // highlight ID for line number }; @@ -80,7 +82,7 @@ static signgroup_T *sign_group_ref(const char_u *groupname) hi = hash_lookup(&sg_table, (char *)groupname, STRLEN(groupname), hash); if (HASHITEM_EMPTY(hi)) { // new group - group = xmalloc((unsigned)(sizeof(signgroup_T) + STRLEN(groupname))); + group = xmalloc(sizeof(signgroup_T) + STRLEN(groupname)); STRCPY(group->sg_name, groupname); group->sg_refcount = 1; @@ -117,7 +119,7 @@ static void sign_group_unref(char_u *groupname) /// @return true if 'sign' is in 'group'. /// A sign can either be in the global group (sign->group == NULL) /// or in a named group. If 'group' is '*', then the sign is part of the group. -bool sign_in_group(sign_entry_T *sign, const char_u *group) +static bool sign_in_group(sign_entry_T *sign, const char_u *group) { return ((group != NULL && STRCMP(group, "*") == 0) || (group == NULL && sign->se_group == NULL) @@ -126,7 +128,7 @@ bool sign_in_group(sign_entry_T *sign, const char_u *group) } /// Get the next free sign identifier in the specified group -int sign_group_get_next_signid(buf_T *buf, const char_u *groupname) +static int sign_group_get_next_signid(buf_T *buf, const char_u *groupname) { int id = 1; signgroup_T *group = NULL; @@ -195,7 +197,8 @@ static void insert_sign(buf_T *buf, sign_entry_T *prev, sign_entry_T *next, int if (next != NULL) { next->se_prev = newsign; } - buf->b_signcols_valid = false; + + buf_signcols_add_check(buf, newsign); if (prev == NULL) { // When adding first sign need to redraw the windows to create the @@ -243,8 +246,21 @@ static void insert_sign_by_lnum_prio(buf_T *buf, sign_entry_T *prev, int id, con insert_sign(buf, prev, sign, id, group, prio, lnum, typenr, has_text_or_icon); } +/// Lookup a sign by typenr. Returns NULL if sign is not found. +static sign_T *find_sign_by_typenr(int typenr) +{ + sign_T *sp; + + for (sp = first_sign; sp != NULL; sp = sp->sn_next) { + if (sp->sn_typenr == typenr) { + return sp; + } + } + return NULL; +} + /// Get the name of a sign by its typenr. -char_u *sign_typenr2name(int typenr) +static char_u *sign_typenr2name(int typenr) { sign_T *sp; @@ -257,7 +273,7 @@ char_u *sign_typenr2name(int typenr) } /// Return information about a sign in a Dict -dict_T *sign_get_info(sign_entry_T *sign) +static dict_T *sign_get_info(sign_entry_T *sign) { dict_T *d = tv_dict_alloc(); tv_dict_add_nr(d, S_LEN("id"), sign->se_id); @@ -355,8 +371,8 @@ static void sign_sort_by_prio_on_line(buf_T *buf, sign_entry_T *sign) /// @param lnum line number which gets the mark /// @param typenr typenr of sign we are adding /// @param has_text_or_icon sign has text or icon -void buf_addsign(buf_T *buf, int id, const char_u *groupname, int prio, linenr_T lnum, int typenr, - bool has_text_or_icon) +static void buf_addsign(buf_T *buf, int id, const char_u *groupname, int prio, linenr_T lnum, + int typenr, bool has_text_or_icon) { sign_entry_T *sign; // a sign in the signlist sign_entry_T *prev; // the previous sign @@ -402,7 +418,8 @@ void buf_addsign(buf_T *buf, int id, const char_u *groupname, int prio, linenr_T /// @param group sign group /// @param typenr typenr of sign we are adding /// @param prio sign priority -linenr_T buf_change_sign_type(buf_T *buf, int markId, const char_u *group, int typenr, int prio) +static linenr_T buf_change_sign_type(buf_T *buf, int markId, const char_u *group, int typenr, + int prio) { sign_entry_T *sign; // a sign in the signlist @@ -453,19 +470,6 @@ sign_attrs_T *sign_get_attr(SignType type, sign_attrs_T sattrs[], int idx, int m return NULL; } -/// Lookup a sign by typenr. Returns NULL if sign is not found. -static sign_T *find_sign_by_typenr(int typenr) -{ - sign_T *sp; - - for (sp = first_sign; sp != NULL; sp = sp->sn_next) { - if (sp->sn_typenr == typenr) { - return sp; - } - } - return NULL; -} - /// Return the attributes of all the signs placed on line 'lnum' in buffer /// 'buf'. Used when refreshing the screen. Returns the number of signs. /// @param buf Buffer in which to search @@ -499,9 +503,14 @@ int buf_get_signattrs(buf_T *buf, linenr_T lnum, sign_attrs_T sattrs[]) if (sp->sn_line_hl != 0) { sattr.sat_linehl = syn_id2attr(sp->sn_line_hl); } + if (sp->sn_cul_hl != 0) { + sattr.sat_culhl = syn_id2attr(sp->sn_cul_hl); + } if (sp->sn_num_hl != 0) { sattr.sat_numhl = syn_id2attr(sp->sn_num_hl); } + // Store the priority so we can mesh in extmark signs later + sattr.sat_prio = sign->se_priority; } sattrs[nr_matches] = sattr; @@ -528,14 +537,13 @@ int buf_get_signattrs(buf_T *buf, linenr_T lnum, sign_attrs_T sattrs[]) /// /// @return the line number of the deleted sign. If multiple signs are deleted, /// then returns the line number of the last sign deleted. -linenr_T buf_delsign(buf_T *buf, linenr_T atlnum, int id, char_u *group) +static linenr_T buf_delsign(buf_T *buf, linenr_T atlnum, int id, char_u *group) { sign_entry_T **lastp; // pointer to pointer to current sign sign_entry_T *sign; // a sign in a b_signlist sign_entry_T *next; // the next sign in a b_signlist linenr_T lnum; // line number whose sign was deleted - buf->b_signcols_valid = false; lastp = &buf->b_signlist; lnum = 0; for (sign = buf->b_signlist; sign != NULL; sign = next) { @@ -548,6 +556,7 @@ linenr_T buf_delsign(buf_T *buf, linenr_T atlnum, int id, char_u *group) next->se_prev = sign->se_prev; } lnum = sign->se_lnum; + buf_signcols_del_check(buf, lnum, lnum); if (sign->se_group != NULL) { sign_group_unref(sign->se_group->sg_name); } @@ -585,7 +594,7 @@ linenr_T buf_delsign(buf_T *buf, linenr_T atlnum, int id, char_u *group) /// @param buf buffer to store sign in /// @param id sign ID /// @param group sign group -int buf_findsign(buf_T *buf, int id, char_u *group) +static int buf_findsign(buf_T *buf, int id, char_u *group) { sign_entry_T *sign; // a sign in the signlist @@ -628,7 +637,7 @@ static sign_entry_T *buf_getsign_at_line(buf_T *buf, linenr_T lnum, char_u *grou /// @param buf buffer whose sign we are searching for /// @param lnum line number of sign /// @param groupname sign group name -int buf_findsign_id(buf_T *buf, linenr_T lnum, char_u *groupname) +static int buf_findsign_id(buf_T *buf, linenr_T lnum, char_u *groupname) { sign_entry_T *sign; // a sign in the signlist @@ -669,11 +678,11 @@ void buf_delete_signs(buf_T *buf, char_u *group) lastp = &sign->se_next; } } - buf->b_signcols_valid = false; + buf_signcols_del_check(buf, 1, MAXLNUM); } /// List placed signs for "rbuf". If "rbuf" is NULL do it for all buffers. -void sign_list_placed(buf_T *rbuf, char_u *sign_group) +static void sign_list_placed(buf_T *rbuf, char_u *sign_group) { buf_T *buf; sign_entry_T *sign; @@ -731,14 +740,19 @@ void sign_mark_adjust(linenr_T line1, linenr_T line2, long amount, long amount_a int is_fixed = 0; int signcol = win_signcol_configured(curwin, &is_fixed); - curbuf->b_signcols_valid = false; + bool delete = amount == MAXLNUM; + + if (delete) { + buf_signcols_del_check(curbuf, line1, line2); + } + lastp = &curbuf->b_signlist; for (sign = curbuf->b_signlist; sign != NULL; sign = next) { next = sign->se_next; new_lnum = sign->se_lnum; if (sign->se_lnum >= line1 && sign->se_lnum <= line2) { - if (amount != MAXLNUM) { + if (!delete) { new_lnum += amount; } else if (!is_fixed || signcol >= 2) { *lastp = next; @@ -900,8 +914,8 @@ static int sign_define_init_text(sign_T *sp, char_u *text) } /// Define a new sign or update an existing sign -int sign_define_by_name(char_u *name, char_u *icon, char_u *linehl, char_u *text, char_u *texthl, - char *numhl) +static int sign_define_by_name(char_u *name, char_u *icon, char_u *linehl, char_u *text, + char_u *texthl, char_u *culhl, char *numhl) { sign_T *sp_prev; sign_T *sp; @@ -939,22 +953,42 @@ int sign_define_by_name(char_u *name, char_u *icon, char_u *linehl, char_u *text } if (linehl != NULL) { - sp->sn_line_hl = syn_check_group((char *)linehl, (int)STRLEN(linehl)); + if (*linehl == NUL) { + sp->sn_line_hl = 0; + } else { + sp->sn_line_hl = syn_check_group((char *)linehl, STRLEN(linehl)); + } } if (texthl != NULL) { - sp->sn_text_hl = syn_check_group((char *)texthl, (int)STRLEN(texthl)); + if (*texthl == NUL) { + sp->sn_text_hl = 0; + } else { + sp->sn_text_hl = syn_check_group((char *)texthl, STRLEN(texthl)); + } + } + + if (culhl != NULL) { + if (*culhl == NUL) { + sp->sn_cul_hl = 0; + } else { + sp->sn_cul_hl = syn_check_group((char *)culhl, STRLEN(culhl)); + } } if (numhl != NULL) { - sp->sn_num_hl = syn_check_group(numhl, (int)STRLEN(numhl)); + if (*numhl == NUL) { + sp->sn_num_hl = 0; + } else { + sp->sn_num_hl = syn_check_group(numhl, STRLEN(numhl)); + } } return OK; } /// Free the sign specified by 'name'. -int sign_undefine_by_name(const char_u *name) +static int sign_undefine_by_name(const char_u *name) { sign_T *sp_prev; sign_T *sp; @@ -969,17 +1003,6 @@ int sign_undefine_by_name(const char_u *name) return OK; } -static void may_force_numberwidth_recompute(buf_T *buf, int unplace) -{ - FOR_ALL_TAB_WINDOWS(tp, wp) - if (wp->w_buffer == buf - && (wp->w_p_nu || wp->w_p_rnu) - && (unplace || wp->w_nrwidth_width < 2) - && (*wp->w_p_scl == 'n' && *(wp->w_p_scl + 1) == 'u')) { - wp->w_nrwidth_line_count = 0; - } -} - /// List the signs matching 'name' static void sign_list_by_name(char_u *name) { @@ -993,10 +1016,20 @@ static void sign_list_by_name(char_u *name) } } +static void may_force_numberwidth_recompute(buf_T *buf, int unplace) +{ + FOR_ALL_TAB_WINDOWS(tp, wp) + if (wp->w_buffer == buf + && (wp->w_p_nu || wp->w_p_rnu) + && (unplace || wp->w_nrwidth_width < 2) + && (*wp->w_p_scl == 'n' && *(wp->w_p_scl + 1) == 'u')) { + wp->w_nrwidth_line_count = 0; + } +} /// Place a sign at the specified file location or update a sign. -int sign_place(int *sign_id, const char_u *sign_group, const char_u *sign_name, buf_T *buf, - linenr_T lnum, int prio) +static int sign_place(int *sign_id, const char_u *sign_group, const char_u *sign_name, buf_T *buf, + linenr_T lnum, int prio) { sign_T *sp; @@ -1048,7 +1081,7 @@ int sign_place(int *sign_id, const char_u *sign_group, const char_u *sign_name, } /// Unplace the specified sign -int sign_unplace(int sign_id, char_u *sign_group, buf_T *buf, linenr_T atlnum) +static int sign_unplace(int sign_id, char_u *sign_group, buf_T *buf, linenr_T atlnum) { if (buf->b_signlist == NULL) { // No signs in the buffer return OK; @@ -1092,7 +1125,7 @@ static void sign_unplace_at_cursor(char_u *groupname) } /// Jump to a sign. -linenr_T sign_jump(int sign_id, char_u *sign_group, buf_T *buf) +static linenr_T sign_jump(int sign_id, char_u *sign_group, buf_T *buf) { linenr_T lnum; @@ -1133,6 +1166,7 @@ static void sign_define_cmd(char_u *sign_name, char_u *cmdline) char_u *text = NULL; char_u *linehl = NULL; char_u *texthl = NULL; + char_u *culhl = NULL; char_u *numhl = NULL; int failed = false; @@ -1155,6 +1189,9 @@ static void sign_define_cmd(char_u *sign_name, char_u *cmdline) } else if (STRNCMP(arg, "texthl=", 7) == 0) { arg += 7; texthl = vim_strnsave(arg, (size_t)(p - arg)); + } else if (STRNCMP(arg, "culhl=", 6) == 0) { + arg += 6; + culhl = vim_strnsave(arg, (size_t)(p - arg)); } else if (STRNCMP(arg, "numhl=", 6) == 0) { arg += 6; numhl = vim_strnsave(arg, (size_t)(p - arg)); @@ -1166,13 +1203,14 @@ static void sign_define_cmd(char_u *sign_name, char_u *cmdline) } if (!failed) { - sign_define_by_name(sign_name, icon, linehl, text, texthl, (char *)numhl); + sign_define_by_name(sign_name, icon, linehl, text, texthl, culhl, (char *)numhl); } xfree(icon); xfree(text); xfree(linehl); xfree(texthl); + xfree(culhl); xfree(numhl); } @@ -1472,27 +1510,34 @@ 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"), 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); } } /// If 'name' is NULL, return a list of all the defined signs. /// Otherwise, return information about the specified sign. -void sign_getlist(const char_u *name, list_T *retlist) +static void sign_getlist(const char_u *name, list_T *retlist) { sign_T *sp = first_sign; dict_T *dict; @@ -1562,8 +1607,8 @@ static void sign_get_placed_in_buf(buf_T *buf, linenr_T lnum, int sign_id, const /// Get a list of signs placed in buffer 'buf'. If 'num' is non-zero, return the /// sign placed at the line number. If 'lnum' is zero, return all the signs /// placed in 'buf'. If 'buf' is NULL, return signs placed in all the buffers. -void sign_get_placed(buf_T *buf, linenr_T lnum, int sign_id, const char_u *sign_group, - list_T *retlist) +static void sign_get_placed(buf_T *buf, linenr_T lnum, int sign_id, const char_u *sign_group, + list_T *retlist) { if (buf != NULL) { sign_get_placed_in_buf(buf, lnum, sign_id, sign_group, retlist); @@ -1609,6 +1654,16 @@ static void sign_list_defined(sign_T *sp) msg_puts(p); } } + if (sp->sn_cul_hl > 0) { + msg_puts(" culhl="); + const char *const p = get_highlight_name_ext(NULL, + sp->sn_cul_hl - 1, false); + if (p == NULL) { + msg_puts("NONE"); + } else { + msg_puts(p); + } + } if (sp->sn_num_hl > 0) { msg_puts(" numhl="); const char *const p = get_highlight_name_ext(NULL, @@ -1694,7 +1749,7 @@ char_u *get_sign_name(expand_T *xp, int idx) case EXP_SUBCMD: return (char_u *)cmds[idx]; case EXP_DEFINE: { - char *define_arg[] = { "icon=", "linehl=", "text=", "texthl=", "numhl=", + char *define_arg[] = { "culhl=", "icon=", "linehl=", "numhl=", "text=", "texthl=", NULL }; return (char_u *)define_arg[idx]; } @@ -1803,6 +1858,7 @@ void set_context_in_sign_cmd(expand_T *xp, char_u *arg) case SIGNCMD_DEFINE: if (STRNCMP(last, "texthl", 6) == 0 || STRNCMP(last, "linehl", 6) == 0 + || STRNCMP(last, "culhl", 5) == 0 || STRNCMP(last, "numhl", 5) == 0) { xp->xp_context = EXPAND_HIGHLIGHT; } else if (STRNCMP(last, "icon", 4) == 0) { @@ -1840,13 +1896,14 @@ void set_context_in_sign_cmd(expand_T *xp, char_u *arg) /// Define a sign using the attributes in 'dict'. Returns 0 on success and -1 on /// failure. -int sign_define_from_dict(const char *name_arg, dict_T *dict) +static int sign_define_from_dict(const char *name_arg, dict_T *dict) { char *name = NULL; char *icon = NULL; char *linehl = NULL; char *text = NULL; char *texthl = NULL; + char *culhl = NULL; char *numhl = NULL; int retval = -1; @@ -1866,11 +1923,12 @@ int sign_define_from_dict(const char *name_arg, dict_T *dict) linehl = tv_dict_get_string(dict, "linehl", true); text = tv_dict_get_string(dict, "text", true); texthl = tv_dict_get_string(dict, "texthl", true); + culhl = tv_dict_get_string(dict, "culhl", true); numhl = tv_dict_get_string(dict, "numhl", true); } if (sign_define_by_name((char_u *)name, (char_u *)icon, (char_u *)linehl, - (char_u *)text, (char_u *)texthl, numhl) + (char_u *)text, (char_u *)texthl, (char_u *)culhl, numhl) == OK) { retval = 0; } @@ -1881,6 +1939,7 @@ cleanup: xfree(linehl); xfree(text); xfree(texthl); + xfree(culhl); xfree(numhl); return retval; @@ -1888,7 +1947,7 @@ cleanup: /// Define multiple signs using attributes from list 'l' and store the return /// values in 'retlist'. -void sign_define_multiple(list_T *l, list_T *retlist) +static void sign_define_multiple(list_T *l, list_T *retlist) { int retval; @@ -1903,10 +1962,156 @@ void sign_define_multiple(list_T *l, list_T *retlist) }); } +/// "sign_define()" function +void f_sign_define(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + const char *name; + + if (argvars[0].v_type == VAR_LIST && argvars[1].v_type == VAR_UNKNOWN) { + // Define multiple signs + tv_list_alloc_ret(rettv, kListLenMayKnow); + + sign_define_multiple(argvars[0].vval.v_list, rettv->vval.v_list); + return; + } + + // Define a single sign + rettv->vval.v_number = -1; + + name = tv_get_string_chk(&argvars[0]); + if (name == NULL) { + return; + } + + if (argvars[1].v_type != VAR_UNKNOWN && argvars[1].v_type != VAR_DICT) { + emsg(_(e_dictreq)); + return; + } + + rettv->vval.v_number = sign_define_from_dict(name, + argvars[1].v_type == + VAR_DICT ? argvars[1].vval.v_dict : NULL); +} + +/// "sign_getdefined()" function +void f_sign_getdefined(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + const char *name = NULL; + + tv_list_alloc_ret(rettv, 0); + + if (argvars[0].v_type != VAR_UNKNOWN) { + name = tv_get_string(&argvars[0]); + } + + sign_getlist((const char_u *)name, rettv->vval.v_list); +} + +/// "sign_getplaced()" function +void f_sign_getplaced(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + buf_T *buf = NULL; + dict_T *dict; + dictitem_T *di; + linenr_T lnum = 0; + int sign_id = 0; + const char *group = NULL; + bool notanum = false; + + tv_list_alloc_ret(rettv, 0); + + if (argvars[0].v_type != VAR_UNKNOWN) { + // get signs placed in the specified buffer + buf = get_buf_arg(&argvars[0]); + if (buf == NULL) { + return; + } + + if (argvars[1].v_type != VAR_UNKNOWN) { + if (argvars[1].v_type != VAR_DICT + || ((dict = argvars[1].vval.v_dict) == NULL)) { + emsg(_(e_dictreq)); + return; + } + if ((di = tv_dict_find(dict, "lnum", -1)) != NULL) { + // get signs placed at this line + lnum = (linenr_T)tv_get_number_chk(&di->di_tv, ¬anum); + if (notanum) { + return; + } + (void)lnum; + lnum = tv_get_lnum(&di->di_tv); + } + if ((di = tv_dict_find(dict, "id", -1)) != NULL) { + // get sign placed with this identifier + sign_id = (int)tv_get_number_chk(&di->di_tv, ¬anum); + if (notanum) { + return; + } + } + if ((di = tv_dict_find(dict, "group", -1)) != NULL) { + group = tv_get_string_chk(&di->di_tv); + if (group == NULL) { + return; + } + if (*group == '\0') { // empty string means global group + group = NULL; + } + } + } + } + + sign_get_placed(buf, lnum, sign_id, (const char_u *)group, + rettv->vval.v_list); +} + +/// "sign_jump()" function +void f_sign_jump(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + int sign_id; + char *sign_group = NULL; + buf_T *buf; + bool notanum = false; + + rettv->vval.v_number = -1; + + // Sign identifier + sign_id = (int)tv_get_number_chk(&argvars[0], ¬anum); + if (notanum) { + return; + } + if (sign_id <= 0) { + emsg(_(e_invarg)); + return; + } + + // Sign group + const char *sign_group_chk = tv_get_string_chk(&argvars[1]); + if (sign_group_chk == NULL) { + return; + } + if (sign_group_chk[0] == '\0') { + sign_group = NULL; // global sign group + } else { + sign_group = xstrdup(sign_group_chk); + } + + // Buffer to place the sign + buf = get_buf_arg(&argvars[2]); + if (buf == NULL) { + goto cleanup; + } + + rettv->vval.v_number = sign_jump(sign_id, (char_u *)sign_group, buf); + +cleanup: + xfree(sign_group); +} + /// Place a new sign using the values specified in dict 'dict'. Returns the sign /// identifier if successfully placed, otherwise returns 0. -int sign_place_from_dict(typval_T *id_tv, typval_T *group_tv, typval_T *name_tv, typval_T *buf_tv, - dict_T *dict) +static int sign_place_from_dict(typval_T *id_tv, typval_T *group_tv, typval_T *name_tv, + typval_T *buf_tv, dict_T *dict) { int sign_id = 0; char_u *group = NULL; @@ -2018,8 +2223,50 @@ cleanup: return ret_sign_id; } +/// "sign_place()" function +void f_sign_place(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + dict_T *dict = NULL; + + rettv->vval.v_number = -1; + + if (argvars[4].v_type != VAR_UNKNOWN + && (argvars[4].v_type != VAR_DICT + || ((dict = argvars[4].vval.v_dict) == NULL))) { + emsg(_(e_dictreq)); + return; + } + + rettv->vval.v_number = sign_place_from_dict(&argvars[0], &argvars[1], &argvars[2], &argvars[3], + dict); +} + +/// "sign_placelist()" function. Place multiple signs. +void f_sign_placelist(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + int sign_id; + + tv_list_alloc_ret(rettv, kListLenMayKnow); + + if (argvars[0].v_type != VAR_LIST) { + emsg(_(e_listreq)); + return; + } + + // Process the List of sign attributes + TV_LIST_ITER_CONST(argvars[0].vval.v_list, li, { + sign_id = -1; + if (TV_LIST_ITEM_TV(li)->v_type == VAR_DICT) { + sign_id = sign_place_from_dict(NULL, NULL, NULL, NULL, TV_LIST_ITEM_TV(li)->vval.v_dict); + } else { + emsg(_(e_dictreq)); + } + tv_list_append_number(rettv->vval.v_list, sign_id); + }); +} + /// Undefine multiple signs -void sign_undefine_multiple(list_T *l, list_T *retlist) +static void sign_undefine_multiple(list_T *l, list_T *retlist) { char_u *name; int retval; @@ -2034,9 +2281,41 @@ void sign_undefine_multiple(list_T *l, list_T *retlist) }); } +/// "sign_undefine()" function +void f_sign_undefine(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + const char *name; + + if (argvars[0].v_type == VAR_LIST && argvars[1].v_type == VAR_UNKNOWN) { + // Undefine multiple signs + tv_list_alloc_ret(rettv, kListLenMayKnow); + + sign_undefine_multiple(argvars[0].vval.v_list, rettv->vval.v_list); + return; + } + + rettv->vval.v_number = -1; + + if (argvars[0].v_type == VAR_UNKNOWN) { + // Free all the signs + free_signs(); + rettv->vval.v_number = 0; + } else { + // Free only the specified sign + name = tv_get_string_chk(&argvars[0]); + if (name == NULL) { + return; + } + + if (sign_undefine_by_name((const char_u *)name) == OK) { + rettv->vval.v_number = 0; + } + } +} + /// Unplace the sign with attributes specified in 'dict'. Returns 0 on success /// and -1 on failure. -int sign_unplace_from_dict(typval_T *group_tv, dict_T *dict) +static int sign_unplace_from_dict(typval_T *group_tv, dict_T *dict) { dictitem_T *di; int sign_id = 0; @@ -2092,3 +2371,48 @@ cleanup: return retval; } +/// "sign_unplace()" function +void f_sign_unplace(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + dict_T *dict = NULL; + + rettv->vval.v_number = -1; + + if (argvars[0].v_type != VAR_STRING) { + emsg(_(e_invarg)); + return; + } + + if (argvars[1].v_type != VAR_UNKNOWN) { + if (argvars[1].v_type != VAR_DICT) { + emsg(_(e_dictreq)); + return; + } + dict = argvars[1].vval.v_dict; + } + + rettv->vval.v_number = sign_unplace_from_dict(&argvars[0], dict); +} + +/// "sign_unplacelist()" function +void f_sign_unplacelist(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + int retval; + + tv_list_alloc_ret(rettv, kListLenMayKnow); + + if (argvars[0].v_type != VAR_LIST) { + emsg(_(e_listreq)); + return; + } + + TV_LIST_ITER_CONST(argvars[0].vval.v_list, li, { + retval = -1; + if (TV_LIST_ITEM_TV(li)->v_type == VAR_DICT) { + retval = sign_unplace_from_dict(NULL, TV_LIST_ITEM_TV(li)->vval.v_dict); + } else { + emsg(_(e_dictreq)); + } + tv_list_append_number(rettv->vval.v_list, retval); + }); +} diff --git a/src/nvim/sign.h b/src/nvim/sign.h index 9044c2d0bb..6e75a4e62b 100644 --- a/src/nvim/sign.h +++ b/src/nvim/sign.h @@ -4,6 +4,7 @@ #include <stdbool.h> #include "nvim/buffer_defs.h" +#include "nvim/eval/funcs.h" #include "nvim/ex_cmds_defs.h" #include "nvim/sign_defs.h" diff --git a/src/nvim/sign_defs.h b/src/nvim/sign_defs.h index 46436b2c8e..6d28c1849a 100644 --- a/src/nvim/sign_defs.h +++ b/src/nvim/sign_defs.h @@ -38,7 +38,9 @@ typedef struct sign_attrs_S { char_u *sat_text; int sat_texthl; int sat_linehl; + int sat_culhl; int sat_numhl; + int sat_prio; // Used for inserting extmark signs } sign_attrs_T; #define SIGN_SHOW_MAX 9 diff --git a/src/nvim/spell.c b/src/nvim/spell.c index 85d1e139bf..9fa594bc96 100644 --- a/src/nvim/spell.c +++ b/src/nvim/spell.c @@ -62,11 +62,11 @@ // Disadvantage: When "the" is typed as "hte" it sounds quite different ("@" // vs "ht") and goes down in the list. // Used when 'spellsuggest' is set to "best". -#define RESCORE(word_score, sound_score) ((3 * word_score + sound_score) / 4) +#define RESCORE(word_score, sound_score) ((3 * (word_score) + (sound_score)) / 4) // Do the opposite: based on a maximum end score and a known sound score, // compute the maximum word score that can be used. -#define MAXSCORE(word_score, sound_score) ((4 * word_score - sound_score) / 3) +#define MAXSCORE(word_score, sound_score) ((4 * (word_score) - (sound_score)) / 3) #include <assert.h> #include <inttypes.h> @@ -94,12 +94,12 @@ #include "nvim/garray.h" #include "nvim/getchar.h" #include "nvim/hashtab.h" +#include "nvim/input.h" #include "nvim/mark.h" #include "nvim/mbyte.h" #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/message.h" -#include "nvim/misc1.h" #include "nvim/normal.h" #include "nvim/option.h" #include "nvim/os/input.h" @@ -122,7 +122,7 @@ #define WF_CAPMASK (WF_ONECAP | WF_ALLCAP | WF_KEEPCAP | WF_FIXCAP) // Result values. Lower number is accepted over higher one. -#define SP_BANNED -1 +#define SP_BANNED (-1) #define SP_RARE 0 #define SP_OK 1 #define SP_LOCAL 2 @@ -176,7 +176,7 @@ typedef struct { #define SUG(ga, i) (((suggest_T *)(ga).ga_data)[i]) // True if a word appears in the list of banned words. -#define WAS_BANNED(su, word) (!HASHITEM_EMPTY(hash_find(&su->su_banned, word))) +#define WAS_BANNED(su, word) (!HASHITEM_EMPTY(hash_find(&(su)->su_banned, word))) // Number of suggestions kept when cleaning up. We need to keep more than // what is displayed, because when rescore_suggestions() is called the score @@ -219,13 +219,13 @@ 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 #define SCORE_SFMAX3 400 // maximum score for third try -#define SCORE_BIG SCORE_INS * 3 // big difference +#define SCORE_BIG (SCORE_INS * 3) // big difference #define SCORE_MAXMAX 999999 // accept any score #define SCORE_LIMITMAX 350 // for spell_edit_score_limit() @@ -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) @@ -3066,7 +3066,7 @@ void spell_suggest(int count) ml_replace(curwin->w_cursor.lnum, p, false); curwin->w_cursor.col = c; - changed_bytes(curwin->w_cursor.lnum, c); + inserted_bytes(curwin->w_cursor.lnum, c, stp->st_orglen, stp->st_wordlen); } else { curwin->w_cursor = prev_cursor; } @@ -3690,7 +3690,7 @@ static void suggest_try_change(suginfo_T *su) // Check the maximum score, if we go over it we won't try this change. #define TRY_DEEPER(su, stack, depth, add) \ - (stack[depth].ts_score + (add) < su->su_maxscore) + ((depth) < MAXWLEN - 1 && (stack)[depth].ts_score + (add) < (su)->su_maxscore) // Try finding suggestions by adding/removing/swapping letters. // @@ -3794,6 +3794,10 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so } } + // The loop may take an indefinite amount of time. Break out after five + // sectonds. TODO(vim): add an option for the time limit. + proftime_T time_limit = profile_setlimit(5000); + // Loop to find all suggestions. At each round we either: // - For the current state try one operation, advance "ts_curi", // increase "depth". @@ -3824,7 +3828,7 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so // At end of a prefix or at start of prefixtree: check for // following word. - if (byts[arridx] == 0 || n == (int)STATE_NOPREFIX) { + if (depth < MAXWLEN - 1 && (byts[arridx] == 0 || n == STATE_NOPREFIX)) { // Set su->su_badflags to the caps type at this position. // Use the caps type until here for the prefix itself. n = nofold_len(fword, sp->ts_fidx, su->su_badptr); @@ -4082,7 +4086,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)) { @@ -4927,6 +4931,9 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so if (--breakcheckcount == 0) { os_breakcheck(); breakcheckcount = 1000; + if (profile_passed_limit(time_limit)) { + got_int = true; + } } } } @@ -5284,7 +5291,7 @@ static int stp_sal_score(suggest_T *stp, suginfo_T *su, slang_T *slang, char_u * } static sftword_T dumsft; -#define HIKEY2SFT(p) ((sftword_T *)(p - (dumsft.sft_word - (char_u *)&dumsft))) +#define HIKEY2SFT(p) ((sftword_T *)((p) - (dumsft.sft_word - (char_u *)&dumsft))) #define HI2SFT(hi) HIKEY2SFT((hi)->hi_key) // Prepare for calling suggest_try_soundalike(). @@ -6106,7 +6113,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 +7064,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/spell_defs.h b/src/nvim/spell_defs.h index 220f51cd2f..12e44cb7ff 100644 --- a/src/nvim/spell_defs.h +++ b/src/nvim/spell_defs.h @@ -93,9 +93,9 @@ typedef int salfirst_T; // Values for SP_*ERROR are negative, positive values are used by // read_cnt_string(). -#define SP_TRUNCERROR -1 // spell file truncated error -#define SP_FORMERROR -2 // format error in spell file -#define SP_OTHERERROR -3 // other error while reading spell file +#define SP_TRUNCERROR (-1) // spell file truncated error +#define SP_FORMERROR (-2) // format error in spell file +#define SP_OTHERERROR (-3) // other error while reading spell file // Structure used to store words and other info for one language, loaded from // a .spl file. diff --git a/src/nvim/spellfile.c b/src/nvim/spellfile.c index ae4514dd30..3d3e6e728c 100644 --- a/src/nvim/spellfile.c +++ b/src/nvim/spellfile.c @@ -237,8 +237,8 @@ #include "nvim/fileio.h" #include "nvim/memline.h" #include "nvim/memory.h" -#include "nvim/misc1.h" #include "nvim/option.h" +#include "nvim/os/input.h" #include "nvim/os/os.h" #include "nvim/path.h" #include "nvim/regexp.h" @@ -1850,7 +1850,7 @@ static void spell_reload_one(char_u *fname, bool added_word) // In the postponed prefixes tree wn_flags is used to store the WFP_ flags, // but it must be negative to indicate the prefix tree to tree_add_word(). // Use a negative number with the lower 8 bits zero. -#define PFX_FLAGS -256 +#define PFX_FLAGS (-256) // flags for "condit" argument of store_aff_word() #define CONDIT_COMB 1 // affix must combine @@ -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 @@ -4446,10 +4446,10 @@ static int write_vim_spell(spellinfo_T *spin, char_u *fname) putc(SN_PREFCOND, fd); // <sectionID> putc(SNF_REQUIRED, fd); // <sectionflags> - size_t l = (size_t)write_spell_prefcond(NULL, &spin->si_prefcond); + size_t l = (size_t)write_spell_prefcond(NULL, &spin->si_prefcond, &fwv); put_bytes(fd, l, 4); // <sectionlen> - write_spell_prefcond(fd, &spin->si_prefcond); + write_spell_prefcond(fd, &spin->si_prefcond, &fwv); } // SN_REP: <repcount> <rep> ... @@ -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; } @@ -5580,6 +5580,9 @@ void spell_add_word(char_u *word, int len, SpellAddType what, int idx, bool undo while (!vim_fgets(line, MAXWLEN * 2, fd)) { fpos = fpos_next; fpos_next = ftell(fd); + if (fpos_next < 0) { + break; // should never happen + } if (STRNCMP(word, line, len) == 0 && (line[len] == '/' || line[len] < ' ')) { // Found duplicate word. Remove it by writing a '#' at @@ -5654,7 +5657,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); @@ -5793,7 +5796,7 @@ static int set_spell_finish(spelltab_T *new_st) // Write the table with prefix conditions to the .spl file. // When "fd" is NULL only count the length of what is written. -static int write_spell_prefcond(FILE *fd, garray_T *gap) +static int write_spell_prefcond(FILE *fd, garray_T *gap, size_t *fwv) { assert(gap->ga_len >= 0); @@ -5801,8 +5804,7 @@ static int write_spell_prefcond(FILE *fd, garray_T *gap) put_bytes(fd, (uintmax_t)gap->ga_len, 2); // <prefcondcnt> } size_t totlen = 2 + (size_t)gap->ga_len; // <prefcondcnt> and <condlen> bytes - size_t x = 1; // collect return value of fwrite() - for (int i = 0; i < gap->ga_len; ++i) { + for (int i = 0; i < gap->ga_len; i++) { // <prefcond> : <condlen> <condstr> char_u *p = ((char_u **)gap->ga_data)[i]; if (p != NULL) { @@ -5810,7 +5812,7 @@ static int write_spell_prefcond(FILE *fd, garray_T *gap) if (fd != NULL) { assert(len <= INT_MAX); fputc((int)len, fd); - x &= fwrite(p, len, 1, fd); + *fwv &= fwrite(p, len, 1, fd); } totlen += len; } else if (fd != NULL) { diff --git a/src/nvim/state.c b/src/nvim/state.c index 4eb0073873..056828574f 100644 --- a/src/nvim/state.c +++ b/src/nvim/state.c @@ -4,14 +4,18 @@ #include <assert.h> #include "nvim/ascii.h" +#include "nvim/autocmd.h" #include "nvim/edit.h" +#include "nvim/eval.h" #include "nvim/ex_docmd.h" #include "nvim/getchar.h" #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/screen.h" #include "nvim/state.h" #include "nvim/ui.h" #include "nvim/vim.h" @@ -36,15 +40,27 @@ 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. + // Apply 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 key = K_EVENT; } else { + // Duplicate display updating logic in vgetorpeek() + if (((State & INSERT) != 0 || p_lz) && (State & CMDLINE) == 0 + && must_redraw != 0 && !need_wait_return) { + update_screen(0); + setcursor(); // put cursor back where it belongs + } // Flush screen updates before blocking ui_flush(); // Call `os_inchar` directly to block for events or user input without @@ -52,12 +68,17 @@ 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) { + // An event handler may use the value of reg_executing. + // Clear it if it should be cleared when getting the next character. + check_end_reg_executing(true); may_sync_undo(); } @@ -105,15 +126,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 @@ -133,72 +156,105 @@ int get_real_state(void) return State; } -/// @returns[allocated] mode string -char *get_mode(void) +/// Returns the current mode as a string in "buf[MODE_MAX_LENGTH]", NUL +/// terminated. +/// The first character represents the major mode, the following ones the minor +/// ones. +void get_mode(char *buf) { - char *buf = xcalloc(4, sizeof(char)); + int i = 0; if (VIsual_active) { if (VIsual_select) { - buf[0] = (char)(VIsual_mode + 's' - 'v'); + buf[i++] = (char)(VIsual_mode + 's' - 'v'); } else { - buf[0] = (char)VIsual_mode; + buf[i++] = (char)VIsual_mode; if (restart_VIsual_select) { - buf[1] = 's'; + buf[i++] = 's'; } } } else if (State == HITRETURN || State == ASKMORE || State == SETWSIZE || State == CONFIRM) { - buf[0] = 'r'; + buf[i++] = 'r'; if (State == ASKMORE) { - buf[1] = 'm'; + buf[i++] = 'm'; } else if (State == CONFIRM) { - buf[1] = '?'; + buf[i++] = '?'; } } else if (State == EXTERNCMD) { - buf[0] = '!'; + buf[i++] = '!'; } else if (State & INSERT) { if (State & VREPLACE_FLAG) { - buf[0] = 'R'; - buf[1] = 'v'; + buf[i++] = 'R'; + buf[i++] = 'v'; if (ins_compl_active()) { - buf[2] = 'c'; + buf[i++] = 'c'; } else if (ctrl_x_mode_not_defined_yet()) { - buf[2] = 'x'; + buf[i++] = 'x'; } } else { if (State & REPLACE_FLAG) { - buf[0] = 'R'; + buf[i++] = 'R'; } else { - buf[0] = 'i'; + buf[i++] = 'i'; } if (ins_compl_active()) { - buf[1] = 'c'; + buf[i++] = 'c'; } else if (ctrl_x_mode_not_defined_yet()) { - buf[1] = 'x'; + buf[i++] = 'x'; } } - } else if (State & CMDLINE) { - buf[0] = 'c'; + } else if ((State & CMDLINE) || exmode_active) { + buf[i++] = 'c'; if (exmode_active) { - buf[1] = 'v'; + buf[i++] = 'v'; } } else if (State & TERM_FOCUS) { - buf[0] = 't'; + buf[i++] = 't'; } else { - buf[0] = 'n'; + buf[i++] = 'n'; if (finish_op) { - buf[1] = 'o'; + buf[i++] = 'o'; // to be able to detect force-linewise/blockwise/charwise operations - buf[2] = (char)motion_force; + buf[i++] = (char)motion_force; } else if (restart_edit == 'I' || restart_edit == 'R' || restart_edit == 'V') { - buf[1] = 'i'; - buf[2] = (char)restart_edit; + buf[i++] = 'i'; + buf[i++] = (char)restart_edit; } else if (curbuf->terminal) { - buf[1] = 't'; + buf[i++] = 't'; } } - return buf; + buf[i] = NUL; +} + +/// Fires a ModeChanged autocmd if appropriate. +void may_trigger_modechanged(void) +{ + if (!has_event(EVENT_MODECHANGED)) { + return; + } + + char curr_mode[MODE_MAX_LENGTH]; + char_u pattern_buf[2 * MODE_MAX_LENGTH]; + + get_mode(curr_mode); + if (STRCMP(curr_mode, last_mode) == 0) { + return; + } + + save_v_event_T save_v_event; + dict_T *v_event = get_v_event(&save_v_event); + tv_dict_add_str(v_event, S_LEN("new_mode"), curr_mode); + tv_dict_add_str(v_event, S_LEN("old_mode"), last_mode); + tv_dict_set_keys_readonly(v_event); + + // concatenate modes in format "old_mode:new_mode" + vim_snprintf((char *)pattern_buf, sizeof(pattern_buf), "%s:%s", last_mode, curr_mode); + + apply_autocmds(EVENT_MODECHANGED, pattern_buf, NULL, false, curbuf); + STRCPY(last_mode, curr_mode); + + restore_v_event(v_event, &save_v_event); } diff --git a/src/nvim/strings.c b/src/nvim/strings.c index c58e052ae9..9d4c64e4b1 100644 --- a/src/nvim/strings.c +++ b/src/nvim/strings.c @@ -31,7 +31,6 @@ #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/message.h" -#include "nvim/misc1.h" #include "nvim/move.h" #include "nvim/ops.h" #include "nvim/option.h" @@ -355,7 +354,7 @@ char *strcase_save(const char *const orig, bool upper) int l = utf_ptr2len((const char_u *)p); if (c == 0) { // overlong sequence, use only the first byte - c = *p; + c = (char_u)(*p); l = 1; } int uc = upper ? mb_toupper(c) : mb_tolower(c); @@ -394,6 +393,18 @@ void del_trailing_spaces(char_u *ptr) } } +#if !defined(HAVE_STRNLEN) +size_t xstrnlen(const char *s, size_t n) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE +{ + const char *end = memchr(s, '\0', n); + if (end == NULL) { + return n; + } + return end - s; +} +#endif + #if (!defined(HAVE_STRCASECMP) && !defined(HAVE_STRICMP)) /* * Compare two strings, ignoring case, using current locale. @@ -990,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 13833c256e..d884ad704b 100644 --- a/src/nvim/syntax.c +++ b/src/nvim/syntax.c @@ -25,15 +25,17 @@ #include "nvim/garray.h" #include "nvim/hashtab.h" #include "nvim/highlight.h" +#include "nvim/highlight_group.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" #include "nvim/memory.h" #include "nvim/message.h" -#include "nvim/misc1.h" #include "nvim/option.h" +#include "nvim/os/input.h" #include "nvim/os/os.h" #include "nvim/os/time.h" #include "nvim/os_unix.h" @@ -50,56 +52,6 @@ static bool did_syntax_onoff = false; -/// Structure that stores information about a highlight group. -/// The ID of a highlight group is also called group ID. It is the index in -/// the highlight_ga array PLUS ONE. -typedef struct hl_group { - char_u *sg_name; ///< highlight group name - char *sg_name_u; ///< uppercase of sg_name - bool sg_cleared; ///< "hi clear" was used - int sg_attr; ///< Screen attr @see ATTR_ENTRY - int sg_link; ///< link to this highlight group ID - int sg_deflink; ///< default link; restored in highlight_clear() - int sg_set; ///< combination of flags in \ref SG_SET - sctx_T sg_deflink_sctx; ///< script where the default link was set - sctx_T sg_script_ctx; ///< script in which the group was last set - // for terminal UIs - int sg_cterm; ///< "cterm=" highlighting attr - ///< (combination of \ref HlAttrFlags) - int sg_cterm_fg; ///< terminal fg color number + 1 - int sg_cterm_bg; ///< terminal bg color number + 1 - bool sg_cterm_bold; ///< bold attr was set for light color - // for RGB UIs - int sg_gui; ///< "gui=" highlighting attributes - ///< (combination of \ref HlAttrFlags) - RgbValue sg_rgb_fg; ///< RGB foreground color - RgbValue sg_rgb_bg; ///< RGB background color - RgbValue sg_rgb_sp; ///< RGB special color - char *sg_rgb_fg_name; ///< RGB foreground color name - char *sg_rgb_bg_name; ///< RGB background color name - char *sg_rgb_sp_name; ///< RGB special color name - - int sg_blend; ///< blend level (0-100 inclusive), -1 if unset -} HlGroup; - -/// \addtogroup SG_SET -/// @{ -#define SG_CTERM 2 // cterm has been set -#define SG_GUI 4 // gui has been set -#define SG_LINK 8 // link has been set -/// @} - -// builtin |highlight-groups| -static garray_T highlight_ga = GA_EMPTY_INIT_VALUE; -Map(cstr_t, int) highlight_unames = MAP_INIT; - -static inline struct hl_group *HL_TABLE(void) -{ - return ((struct hl_group *)((highlight_ga.ga_data))); -} - -#define MAX_HL_ID 20000 // maximum value for a highlight ID. - // different types of offsets that are possible #define SPO_MS_OFF 0 // match start offset #define SPO_ME_OFF 1 // match end offset @@ -110,20 +62,6 @@ static inline struct hl_group *HL_TABLE(void) #define SPO_LC_OFF 6 // leading context offset #define SPO_COUNT 7 -// Flags to indicate an additional string for highlight name completion. -static int include_none = 0; // when 1 include "nvim/None" -static int include_default = 0; // when 1 include "nvim/default" -static int include_link = 0; // when 2 include "nvim/link" and "clear" - -/// The "term", "cterm" and "gui" arguments can be any combination of the -/// following names, separated by commas (but no spaces!). -static char *(hl_name_table[]) = -{ "bold", "standout", "underline", "undercurl", - "italic", "reverse", "inverse", "strikethrough", "nocombine", "NONE" }; -static int hl_attr_table[] = -{ HL_BOLD, HL_STANDOUT, HL_UNDERLINE, HL_UNDERCURL, HL_ITALIC, HL_INVERSE, - HL_INVERSE, HL_STRIKETHROUGH, HL_NOCOMBINE, 0 }; - static char e_illegal_arg[] = N_("E390: Illegal argument: %s"); // The patterns that are being searched for are stored in a syn_pattern. @@ -243,7 +181,7 @@ static char *(spo_name_tab[SPO_COUNT]) = #define SYN_ITEMS(buf) ((synpat_T *)((buf)->b_syn_patterns.ga_data)) -#define NONE_IDX -2 // value of sp_sync_idx for "NONE" +#define NONE_IDX (-2) // value of sp_sync_idx for "NONE" /* * Flags for b_syn_sync_flags: @@ -329,9 +267,9 @@ static int keepend_level = -1; static char msg_no_items[] = N_("No Syntax items defined for this buffer"); // value of si_idx for keywords -#define KEYWORD_IDX -1 +#define KEYWORD_IDX (-1) // valid of si_cont_list for containing all but contained groups -#define ID_LIST_ALL (int16_t *)-1 +#define ID_LIST_ALL ((int16_t *)-1) static int next_seqnr = 1; // value to use for si_seqnr @@ -2351,55 +2289,52 @@ static void check_state_ends(void) next_match_idx = 0; next_match_col = MAXCOL; break; - } else { - // handle next_list, unless at end of line and no "skipnl" or - // "skipempty" - current_next_list = cur_si->si_next_list; - current_next_flags = cur_si->si_flags; - if (!(current_next_flags & (HL_SKIPNL | HL_SKIPEMPTY)) - && syn_getcurline()[current_col] == NUL) { - current_next_list = NULL; - } + } - // When the ended item has "extend", another item with - // "keepend" now needs to check for its end. - had_extend = (cur_si->si_flags & HL_EXTEND); + // handle next_list, unless at end of line and no "skipnl" or + // "skipempty" + current_next_list = cur_si->si_next_list; + current_next_flags = cur_si->si_flags; + if (!(current_next_flags & (HL_SKIPNL | HL_SKIPEMPTY)) + && syn_getcurline()[current_col] == NUL) { + current_next_list = NULL; + } - pop_current_state(); + // When the ended item has "extend", another item with + // "keepend" now needs to check for its end. + had_extend = (cur_si->si_flags & HL_EXTEND); + pop_current_state(); + + if (GA_EMPTY(¤t_state)) { + break; + } + + if (had_extend && keepend_level >= 0) { + syn_update_ends(false); if (GA_EMPTY(¤t_state)) { break; } + } - if (had_extend && keepend_level >= 0) { - syn_update_ends(false); - if (GA_EMPTY(¤t_state)) { - break; - } - } - - cur_si = &CUR_STATE(current_state.ga_len - 1); + cur_si = &CUR_STATE(current_state.ga_len - 1); - /* - * Only for a region the search for the end continues after - * the end of the contained item. If the contained match - * included the end-of-line, break here, the region continues. - * Don't do this when: - * - "keepend" is used for the contained item - * - not at the end of the line (could be end="x$"me=e-1). - * - "excludenl" is used (HL_HAS_EOL won't be set) - */ - if (cur_si->si_idx >= 0 - && SYN_ITEMS(syn_block)[cur_si->si_idx].sp_type - == SPTYPE_START - && !(cur_si->si_flags & (HL_MATCH | HL_KEEPEND))) { - update_si_end(cur_si, (int)current_col, true); - check_keepend(); - if ((current_next_flags & HL_HAS_EOL) - && keepend_level < 0 - && syn_getcurline()[current_col] == NUL) { - break; - } + // Only for a region the search for the end continues after + // the end of the contained item. If the contained match + // included the end-of-line, break here, the region continues. + // Don't do this when: + // - "keepend" is used for the contained item + // - not at the end of the line (could be end="x$"me=e-1). + // - "excludenl" is used (HL_HAS_EOL won't be set) + if (cur_si->si_idx >= 0 + && SYN_ITEMS(syn_block)[cur_si->si_idx].sp_type == SPTYPE_START + && !(cur_si->si_flags & (HL_MATCH | HL_KEEPEND))) { + update_si_end(cur_si, (int)current_col, true); + check_keepend(); + if ((current_next_flags & HL_HAS_EOL) + && keepend_level < 0 + && syn_getcurline()[current_col] == NUL) { + break; } } } else { @@ -3110,9 +3045,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; @@ -3139,9 +3074,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; @@ -3166,9 +3101,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; } @@ -3207,11 +3142,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; @@ -3243,7 +3178,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")); @@ -3606,7 +3541,7 @@ static void syn_cmd_list(exarg_T *eap, int syncing) /* * No argument: List all group IDs and all syntax clusters. */ - for (int id = 1; id <= highlight_ga.ga_len && !got_int; id++) { + for (int id = 1; id <= highlight_num_groups() && !got_int; id++) { syn_list_one(id, syncing, false); } for (int id = 0; id < curwin->w_s->b_syn_clusters.ga_len && !got_int; ++id) { @@ -3764,8 +3699,8 @@ static void syn_list_one(const int id, const bool syncing, const bool link_only) } msg_putchar(' '); if (spp->sp_sync_idx >= 0) { - msg_outtrans(HL_TABLE()[SYN_ITEMS(curwin->w_s) - [spp->sp_sync_idx].sp_syn.id - 1].sg_name); + msg_outtrans(highlight_group_name(SYN_ITEMS(curwin->w_s) + [spp->sp_sync_idx].sp_syn.id - 1)); } else { msg_puts("NONE"); } @@ -3774,11 +3709,11 @@ static void syn_list_one(const int id, const bool syncing, const bool link_only) } // list the link, if there is one - if (HL_TABLE()[id - 1].sg_link && (did_header || link_only) && !got_int) { + if (highlight_link_id(id - 1) && (did_header || link_only) && !got_int) { (void)syn_list_header(did_header, 0, id, true); msg_puts_attr("links to", attr); msg_putchar(' '); - msg_outtrans(HL_TABLE()[HL_TABLE()[id - 1].sg_link - 1].sg_name); + msg_outtrans(highlight_group_name(highlight_link_id(id - 1) - 1)); } } @@ -3842,7 +3777,7 @@ static void put_id_list(const char *const name, const int16_t *const list, const msg_putchar('@'); msg_outtrans(SYN_CLSTR(curwin->w_s)[scl_id].scl_name); } else { - msg_outtrans(HL_TABLE()[*p - 1].sg_name); + msg_outtrans(highlight_group_name(*p - 1)); } if (p[1]) { msg_putchar(','); @@ -3864,7 +3799,7 @@ static void put_pattern(const char *const s, const int c, const synpat_T *const if (last_matchgroup == 0) { msg_outtrans((char_u *)"NONE"); } else { - msg_outtrans(HL_TABLE()[last_matchgroup - 1].sg_name); + msg_outtrans(highlight_group_name(last_matchgroup - 1)); } msg_putchar(' '); } @@ -4205,7 +4140,7 @@ static char_u *get_syn_options(char_u *arg, syn_opt_arg_T *opt, int *conceal_cha p = flagtab[fidx].name; int i; for (i = 0, len = 0; p[i] != NUL; i += 2, ++len) { - if (arg[len] != p[i] && arg[len] != p[i + 1]) { + if (arg[len] != (char_u)p[i] && arg[len] != (char_u)p[i + 1]) { break; } } @@ -5401,7 +5336,7 @@ static int get_id_list(char_u **const arg, const int keylen, int16_t **const lis do { for (end = p; *end && !ascii_iswhite(*end) && *end != ','; end++) { } - char_u *const name = xmalloc((int)(end - p + 3)); // leave room for "^$" + char_u *const name = xmalloc(end - p + 3); // leave room for "^$" STRLCPY(name + 1, p, end - p + 1); if (STRCMP(name + 1, "ALLBUT") == 0 || STRCMP(name + 1, "ALL") == 0 @@ -5456,8 +5391,8 @@ static int get_id_list(char_u **const arg, const int keylen, int16_t **const lis regmatch.rm_ic = TRUE; id = 0; - for (int i = highlight_ga.ga_len; --i >= 0;) { - if (vim_regexec(®match, HL_TABLE()[i].sg_name, (colnr_T)0)) { + for (int i = highlight_num_groups(); --i >= 0;) { + if (vim_regexec(®match, highlight_group_name(i), (colnr_T)0)) { if (round == 2) { // Got more items than expected; can happen // when adding items that match: @@ -6118,7 +6053,7 @@ static void syntime_report(void) msg_puts(profile_msg(p->average)); msg_puts(" "); msg_advance(50); - msg_outtrans(HL_TABLE()[p->id - 1].sg_name); + msg_outtrans(highlight_group_name(p->id - 1)); msg_puts(" "); msg_advance(69); @@ -6143,2617 +6078,3 @@ static void syntime_report(void) msg_puts("\n"); } } - -/************************************** -* Highlighting stuff * -**************************************/ - -// The default highlight groups. These are compiled-in for fast startup and -// they still work when the runtime files can't be found. -// -// When making changes here, also change runtime/colors/default.vim! - -static const char *highlight_init_both[] = { - "Conceal ctermbg=DarkGrey ctermfg=LightGrey guibg=DarkGrey guifg=LightGrey", - "Cursor guibg=fg guifg=bg", - "lCursor guibg=fg guifg=bg", - "DiffText cterm=bold ctermbg=Red gui=bold guibg=Red", - "ErrorMsg ctermbg=DarkRed ctermfg=White guibg=Red guifg=White", - "IncSearch cterm=reverse gui=reverse", - "ModeMsg cterm=bold gui=bold", - "NonText ctermfg=Blue gui=bold guifg=Blue", - "Normal cterm=NONE gui=NONE", - "PmenuSbar ctermbg=Grey guibg=Grey", - "StatusLine cterm=reverse,bold gui=reverse,bold", - "StatusLineNC cterm=reverse gui=reverse", - "TabLineFill cterm=reverse gui=reverse", - "TabLineSel cterm=bold gui=bold", - "TermCursor cterm=reverse gui=reverse", - "VertSplit cterm=reverse gui=reverse", - "WildMenu ctermbg=Yellow ctermfg=Black guibg=Yellow guifg=Black", - "default link EndOfBuffer NonText", - "default link LineNrAbove LineNr", - "default link LineNrBelow LineNr", - "default link QuickFixLine Search", - "default link Substitute Search", - "default link Whitespace NonText", - "default link MsgSeparator StatusLine", - "default link NormalFloat Pmenu", - "default link FloatBorder VertSplit", - "default FloatShadow blend=80 guibg=Black", - "default FloatShadowThrough blend=100 guibg=Black", - "RedrawDebugNormal cterm=reverse gui=reverse", - "RedrawDebugClear ctermbg=Yellow guibg=Yellow", - "RedrawDebugComposed ctermbg=Green guibg=Green", - "RedrawDebugRecompose ctermbg=Red guibg=Red", - "Error term=reverse cterm=NONE ctermfg=White ctermbg=Red gui=NONE guifg=White guibg=Red", - "Todo term=standout cterm=NONE ctermfg=Black ctermbg=Yellow gui=NONE guifg=Blue guibg=Yellow", - "default link String Constant", - "default link Character Constant", - "default link Number Constant", - "default link Boolean Constant", - "default link Float Number", - "default link Function Identifier", - "default link Conditional Statement", - "default link Repeat Statement", - "default link Label Statement", - "default link Operator Statement", - "default link Keyword Statement", - "default link Exception Statement", - "default link Include PreProc", - "default link Define PreProc", - "default link Macro PreProc", - "default link PreCondit PreProc", - "default link StorageClass Type", - "default link Structure Type", - "default link Typedef Type", - "default link Tag Special", - "default link SpecialChar Special", - "default link Delimiter Special", - "default link SpecialComment Special", - "default link Debug Special", - "default DiagnosticError ctermfg=1 guifg=Red", - "default DiagnosticWarn ctermfg=3 guifg=Orange", - "default DiagnosticInfo ctermfg=4 guifg=LightBlue", - "default DiagnosticHint ctermfg=7 guifg=LightGrey", - "default DiagnosticUnderlineError cterm=underline gui=underline guisp=Red", - "default DiagnosticUnderlineWarn cterm=underline gui=underline guisp=Orange", - "default DiagnosticUnderlineInfo cterm=underline gui=underline guisp=LightBlue", - "default DiagnosticUnderlineHint cterm=underline gui=underline guisp=LightGrey", - "default link DiagnosticVirtualTextError DiagnosticError", - "default link DiagnosticVirtualTextWarn DiagnosticWarn", - "default link DiagnosticVirtualTextInfo DiagnosticInfo", - "default link DiagnosticVirtualTextHint DiagnosticHint", - "default link DiagnosticFloatingError DiagnosticError", - "default link DiagnosticFloatingWarn DiagnosticWarn", - "default link DiagnosticFloatingInfo DiagnosticInfo", - "default link DiagnosticFloatingHint DiagnosticHint", - "default link DiagnosticSignError DiagnosticError", - "default link DiagnosticSignWarn DiagnosticWarn", - "default link DiagnosticSignInfo DiagnosticInfo", - "default link DiagnosticSignHint DiagnosticHint", - NULL -}; - -// Default colors only used with a light background. -static const char *highlight_init_light[] = { - "ColorColumn ctermbg=LightRed guibg=LightRed", - "CursorColumn ctermbg=LightGrey guibg=Grey90", - "CursorLine cterm=underline guibg=Grey90", - "CursorLineNr cterm=underline ctermfg=Brown gui=bold guifg=Brown", - "DiffAdd ctermbg=LightBlue guibg=LightBlue", - "DiffChange ctermbg=LightMagenta guibg=LightMagenta", - "DiffDelete ctermfg=Blue ctermbg=LightCyan gui=bold guifg=Blue guibg=LightCyan", - "Directory ctermfg=DarkBlue guifg=Blue", - "FoldColumn ctermbg=Grey ctermfg=DarkBlue guibg=Grey guifg=DarkBlue", - "Folded ctermbg=Grey ctermfg=DarkBlue guibg=LightGrey guifg=DarkBlue", - "LineNr ctermfg=Brown guifg=Brown", - "MatchParen ctermbg=Cyan guibg=Cyan", - "MoreMsg ctermfg=DarkGreen gui=bold guifg=SeaGreen", - "Pmenu ctermbg=LightMagenta ctermfg=Black guibg=LightMagenta", - "PmenuSel ctermbg=LightGrey ctermfg=Black guibg=Grey", - "PmenuThumb ctermbg=Black guibg=Black", - "Question ctermfg=DarkGreen gui=bold guifg=SeaGreen", - "Search ctermbg=Yellow ctermfg=NONE guibg=Yellow guifg=NONE", - "SignColumn ctermbg=Grey ctermfg=DarkBlue guibg=Grey guifg=DarkBlue", - "SpecialKey ctermfg=DarkBlue guifg=Blue", - "SpellBad ctermbg=LightRed guisp=Red gui=undercurl", - "SpellCap ctermbg=LightBlue guisp=Blue gui=undercurl", - "SpellLocal ctermbg=Cyan guisp=DarkCyan gui=undercurl", - "SpellRare ctermbg=LightMagenta guisp=Magenta gui=undercurl", - "TabLine cterm=underline ctermfg=black ctermbg=LightGrey gui=underline guibg=LightGrey", - "Title ctermfg=DarkMagenta gui=bold guifg=Magenta", - "Visual guibg=LightGrey", - "WarningMsg ctermfg=DarkRed guifg=Red", - "Comment term=bold cterm=NONE ctermfg=DarkBlue ctermbg=NONE gui=NONE guifg=Blue guibg=NONE", - "Constant term=underline cterm=NONE ctermfg=DarkRed ctermbg=NONE gui=NONE guifg=Magenta guibg=NONE", - "Special term=bold cterm=NONE ctermfg=DarkMagenta ctermbg=NONE gui=NONE guifg=#6a5acd guibg=NONE", - "Identifier term=underline cterm=NONE ctermfg=DarkCyan ctermbg=NONE gui=NONE guifg=DarkCyan guibg=NONE", - "Statement term=bold cterm=NONE ctermfg=Brown ctermbg=NONE gui=bold guifg=Brown guibg=NONE", - "PreProc term=underline cterm=NONE ctermfg=DarkMagenta ctermbg=NONE gui=NONE guifg=#6a0dad guibg=NONE", - "Type term=underline cterm=NONE ctermfg=DarkGreen ctermbg=NONE gui=bold guifg=SeaGreen guibg=NONE", - "Underlined term=underline cterm=underline ctermfg=DarkMagenta gui=underline guifg=SlateBlue", - "Ignore term=NONE cterm=NONE ctermfg=white ctermbg=NONE gui=NONE guifg=bg guibg=NONE", - NULL -}; - -// Default colors only used with a dark background. -static const char *highlight_init_dark[] = { - "ColorColumn ctermbg=DarkRed guibg=DarkRed", - "CursorColumn ctermbg=DarkGrey guibg=Grey40", - "CursorLine cterm=underline guibg=Grey40", - "CursorLineNr cterm=underline ctermfg=Yellow gui=bold guifg=Yellow", - "DiffAdd ctermbg=DarkBlue guibg=DarkBlue", - "DiffChange ctermbg=DarkMagenta guibg=DarkMagenta", - "DiffDelete ctermfg=Blue ctermbg=DarkCyan gui=bold guifg=Blue guibg=DarkCyan", - "Directory ctermfg=LightCyan guifg=Cyan", - "FoldColumn ctermbg=DarkGrey ctermfg=Cyan guibg=Grey guifg=Cyan", - "Folded ctermbg=DarkGrey ctermfg=Cyan guibg=DarkGrey guifg=Cyan", - "LineNr ctermfg=Yellow guifg=Yellow", - "MatchParen ctermbg=DarkCyan guibg=DarkCyan", - "MoreMsg ctermfg=LightGreen gui=bold guifg=SeaGreen", - "Pmenu ctermbg=Magenta ctermfg=Black guibg=Magenta", - "PmenuSel ctermbg=Black ctermfg=DarkGrey guibg=DarkGrey", - "PmenuThumb ctermbg=White guibg=White", - "Question ctermfg=LightGreen gui=bold guifg=Green", - "Search ctermbg=Yellow ctermfg=Black guibg=Yellow guifg=Black", - "SignColumn ctermbg=DarkGrey ctermfg=Cyan guibg=Grey guifg=Cyan", - "SpecialKey ctermfg=LightBlue guifg=Cyan", - "SpellBad ctermbg=Red guisp=Red gui=undercurl", - "SpellCap ctermbg=Blue guisp=Blue gui=undercurl", - "SpellLocal ctermbg=Cyan guisp=Cyan gui=undercurl", - "SpellRare ctermbg=Magenta guisp=Magenta gui=undercurl", - "TabLine cterm=underline ctermfg=white ctermbg=DarkGrey gui=underline guibg=DarkGrey", - "Title ctermfg=LightMagenta gui=bold guifg=Magenta", - "Visual guibg=DarkGrey", - "WarningMsg ctermfg=LightRed guifg=Red", - "Comment term=bold cterm=NONE ctermfg=Cyan ctermbg=NONE gui=NONE guifg=#80a0ff guibg=NONE", - "Constant term=underline cterm=NONE ctermfg=Magenta ctermbg=NONE gui=NONE guifg=#ffa0a0 guibg=NONE", - "Special term=bold cterm=NONE ctermfg=LightRed ctermbg=NONE gui=NONE guifg=Orange guibg=NONE", - "Identifier term=underline cterm=bold ctermfg=Cyan ctermbg=NONE gui=NONE guifg=#40ffff guibg=NONE", - "Statement term=bold cterm=NONE ctermfg=Yellow ctermbg=NONE gui=bold guifg=#ffff60 guibg=NONE", - "PreProc term=underline cterm=NONE ctermfg=LightBlue ctermbg=NONE gui=NONE guifg=#ff80ff guibg=NONE", - "Type term=underline cterm=NONE ctermfg=LightGreen ctermbg=NONE gui=bold guifg=#60ff60 guibg=NONE", - "Underlined term=underline cterm=underline ctermfg=LightBlue gui=underline guifg=#80a0ff", - "Ignore term=NONE cterm=NONE ctermfg=black ctermbg=NONE gui=NONE guifg=bg guibg=NONE", - NULL -}; - -const char *const highlight_init_cmdline[] = { - // XXX When modifying a list modify it in both valid and invalid halves. - // TODO(ZyX-I): merge valid and invalid groups via a macros. - - // NvimInternalError should appear only when highlighter has a bug. - "NvimInternalError ctermfg=Red ctermbg=Red guifg=Red guibg=Red", - - // Highlight groups (links) used by parser: - - "default link NvimAssignment Operator", - "default link NvimPlainAssignment NvimAssignment", - "default link NvimAugmentedAssignment NvimAssignment", - "default link NvimAssignmentWithAddition NvimAugmentedAssignment", - "default link NvimAssignmentWithSubtraction NvimAugmentedAssignment", - "default link NvimAssignmentWithConcatenation NvimAugmentedAssignment", - - "default link NvimOperator Operator", - - "default link NvimUnaryOperator NvimOperator", - "default link NvimUnaryPlus NvimUnaryOperator", - "default link NvimUnaryMinus NvimUnaryOperator", - "default link NvimNot NvimUnaryOperator", - - "default link NvimBinaryOperator NvimOperator", - "default link NvimComparison NvimBinaryOperator", - "default link NvimComparisonModifier NvimComparison", - "default link NvimBinaryPlus NvimBinaryOperator", - "default link NvimBinaryMinus NvimBinaryOperator", - "default link NvimConcat NvimBinaryOperator", - "default link NvimConcatOrSubscript NvimConcat", - "default link NvimOr NvimBinaryOperator", - "default link NvimAnd NvimBinaryOperator", - "default link NvimMultiplication NvimBinaryOperator", - "default link NvimDivision NvimBinaryOperator", - "default link NvimMod NvimBinaryOperator", - - "default link NvimTernary NvimOperator", - "default link NvimTernaryColon NvimTernary", - - "default link NvimParenthesis Delimiter", - "default link NvimLambda NvimParenthesis", - "default link NvimNestingParenthesis NvimParenthesis", - "default link NvimCallingParenthesis NvimParenthesis", - - "default link NvimSubscript NvimParenthesis", - "default link NvimSubscriptBracket NvimSubscript", - "default link NvimSubscriptColon NvimSubscript", - "default link NvimCurly NvimSubscript", - - "default link NvimContainer NvimParenthesis", - "default link NvimDict NvimContainer", - "default link NvimList NvimContainer", - - "default link NvimIdentifier Identifier", - "default link NvimIdentifierScope NvimIdentifier", - "default link NvimIdentifierScopeDelimiter NvimIdentifier", - "default link NvimIdentifierName NvimIdentifier", - "default link NvimIdentifierKey NvimIdentifier", - - "default link NvimColon Delimiter", - "default link NvimComma Delimiter", - "default link NvimArrow Delimiter", - - "default link NvimRegister SpecialChar", - "default link NvimNumber Number", - "default link NvimFloat NvimNumber", - "default link NvimNumberPrefix Type", - - "default link NvimOptionSigil Type", - "default link NvimOptionName NvimIdentifier", - "default link NvimOptionScope NvimIdentifierScope", - "default link NvimOptionScopeDelimiter NvimIdentifierScopeDelimiter", - - "default link NvimEnvironmentSigil NvimOptionSigil", - "default link NvimEnvironmentName NvimIdentifier", - - "default link NvimString String", - "default link NvimStringBody NvimString", - "default link NvimStringQuote NvimString", - "default link NvimStringSpecial SpecialChar", - - "default link NvimSingleQuote NvimStringQuote", - "default link NvimSingleQuotedBody NvimStringBody", - "default link NvimSingleQuotedQuote NvimStringSpecial", - - "default link NvimDoubleQuote NvimStringQuote", - "default link NvimDoubleQuotedBody NvimStringBody", - "default link NvimDoubleQuotedEscape NvimStringSpecial", - - "default link NvimFigureBrace NvimInternalError", - "default link NvimSingleQuotedUnknownEscape NvimInternalError", - - "default link NvimSpacing Normal", - - // NvimInvalid groups: - - "default link NvimInvalidSingleQuotedUnknownEscape NvimInternalError", - - "default link NvimInvalid Error", - - "default link NvimInvalidAssignment NvimInvalid", - "default link NvimInvalidPlainAssignment NvimInvalidAssignment", - "default link NvimInvalidAugmentedAssignment NvimInvalidAssignment", - "default link NvimInvalidAssignmentWithAddition NvimInvalidAugmentedAssignment", - "default link NvimInvalidAssignmentWithSubtraction NvimInvalidAugmentedAssignment", - "default link NvimInvalidAssignmentWithConcatenation NvimInvalidAugmentedAssignment", - - "default link NvimInvalidOperator NvimInvalid", - - "default link NvimInvalidUnaryOperator NvimInvalidOperator", - "default link NvimInvalidUnaryPlus NvimInvalidUnaryOperator", - "default link NvimInvalidUnaryMinus NvimInvalidUnaryOperator", - "default link NvimInvalidNot NvimInvalidUnaryOperator", - - "default link NvimInvalidBinaryOperator NvimInvalidOperator", - "default link NvimInvalidComparison NvimInvalidBinaryOperator", - "default link NvimInvalidComparisonModifier NvimInvalidComparison", - "default link NvimInvalidBinaryPlus NvimInvalidBinaryOperator", - "default link NvimInvalidBinaryMinus NvimInvalidBinaryOperator", - "default link NvimInvalidConcat NvimInvalidBinaryOperator", - "default link NvimInvalidConcatOrSubscript NvimInvalidConcat", - "default link NvimInvalidOr NvimInvalidBinaryOperator", - "default link NvimInvalidAnd NvimInvalidBinaryOperator", - "default link NvimInvalidMultiplication NvimInvalidBinaryOperator", - "default link NvimInvalidDivision NvimInvalidBinaryOperator", - "default link NvimInvalidMod NvimInvalidBinaryOperator", - - "default link NvimInvalidTernary NvimInvalidOperator", - "default link NvimInvalidTernaryColon NvimInvalidTernary", - - "default link NvimInvalidDelimiter NvimInvalid", - - "default link NvimInvalidParenthesis NvimInvalidDelimiter", - "default link NvimInvalidLambda NvimInvalidParenthesis", - "default link NvimInvalidNestingParenthesis NvimInvalidParenthesis", - "default link NvimInvalidCallingParenthesis NvimInvalidParenthesis", - - "default link NvimInvalidSubscript NvimInvalidParenthesis", - "default link NvimInvalidSubscriptBracket NvimInvalidSubscript", - "default link NvimInvalidSubscriptColon NvimInvalidSubscript", - "default link NvimInvalidCurly NvimInvalidSubscript", - - "default link NvimInvalidContainer NvimInvalidParenthesis", - "default link NvimInvalidDict NvimInvalidContainer", - "default link NvimInvalidList NvimInvalidContainer", - - "default link NvimInvalidValue NvimInvalid", - - "default link NvimInvalidIdentifier NvimInvalidValue", - "default link NvimInvalidIdentifierScope NvimInvalidIdentifier", - "default link NvimInvalidIdentifierScopeDelimiter NvimInvalidIdentifier", - "default link NvimInvalidIdentifierName NvimInvalidIdentifier", - "default link NvimInvalidIdentifierKey NvimInvalidIdentifier", - - "default link NvimInvalidColon NvimInvalidDelimiter", - "default link NvimInvalidComma NvimInvalidDelimiter", - "default link NvimInvalidArrow NvimInvalidDelimiter", - - "default link NvimInvalidRegister NvimInvalidValue", - "default link NvimInvalidNumber NvimInvalidValue", - "default link NvimInvalidFloat NvimInvalidNumber", - "default link NvimInvalidNumberPrefix NvimInvalidNumber", - - "default link NvimInvalidOptionSigil NvimInvalidIdentifier", - "default link NvimInvalidOptionName NvimInvalidIdentifier", - "default link NvimInvalidOptionScope NvimInvalidIdentifierScope", - "default link NvimInvalidOptionScopeDelimiter " - "NvimInvalidIdentifierScopeDelimiter", - - "default link NvimInvalidEnvironmentSigil NvimInvalidOptionSigil", - "default link NvimInvalidEnvironmentName NvimInvalidIdentifier", - - // Invalid string bodies and specials are still highlighted as valid ones to - // minimize the red area. - "default link NvimInvalidString NvimInvalidValue", - "default link NvimInvalidStringBody NvimStringBody", - "default link NvimInvalidStringQuote NvimInvalidString", - "default link NvimInvalidStringSpecial NvimStringSpecial", - - "default link NvimInvalidSingleQuote NvimInvalidStringQuote", - "default link NvimInvalidSingleQuotedBody NvimInvalidStringBody", - "default link NvimInvalidSingleQuotedQuote NvimInvalidStringSpecial", - - "default link NvimInvalidDoubleQuote NvimInvalidStringQuote", - "default link NvimInvalidDoubleQuotedBody NvimInvalidStringBody", - "default link NvimInvalidDoubleQuotedEscape NvimInvalidStringSpecial", - "default link NvimInvalidDoubleQuotedUnknownEscape NvimInvalidValue", - - "default link NvimInvalidFigureBrace NvimInvalidDelimiter", - - "default link NvimInvalidSpacing ErrorMsg", - - // Not actually invalid, but we highlight user that he is doing something - // wrong. - "default link NvimDoubleQuotedUnknownEscape NvimInvalidValue", - NULL, -}; - -/// Create default links for Nvim* highlight groups used for cmdline coloring -void syn_init_cmdline_highlight(bool reset, bool init) -{ - for (size_t i = 0; highlight_init_cmdline[i] != NULL; i++) { - do_highlight(highlight_init_cmdline[i], reset, init); - } -} - -/// Load colors from a file if "g:colors_name" is set, otherwise load builtin -/// colors -/// -/// @param both include groups where 'bg' doesn't matter -/// @param reset clear groups first -void init_highlight(bool both, bool reset) -{ - static int had_both = false; - - // Try finding the color scheme file. Used when a color file was loaded - // and 'background' or 't_Co' is changed. - char_u *p = get_var_value("g:colors_name"); - if (p != NULL) { - // Value of g:colors_name could be freed in load_colors() and make - // p invalid, so copy it. - char_u *copy_p = vim_strsave(p); - bool okay = load_colors(copy_p); - xfree(copy_p); - if (okay) { - return; - } - } - - /* - * Didn't use a color file, use the compiled-in colors. - */ - if (both) { - had_both = true; - const char *const *const pp = highlight_init_both; - for (size_t i = 0; pp[i] != NULL; i++) { - do_highlight(pp[i], reset, true); - } - } else if (!had_both) { - // Don't do anything before the call with both == true from main(). - // Not everything has been setup then, and that call will overrule - // everything anyway. - return; - } - - const char *const *const pp = ((*p_bg == 'l') - ? highlight_init_light - : highlight_init_dark); - for (size_t i = 0; pp[i] != NULL; i++) { - do_highlight(pp[i], reset, true); - } - - /* Reverse looks ugly, but grey may not work for 8 colors. Thus let it - * depend on the number of colors available. - * With 8 colors brown is equal to yellow, need to use black for Search fg - * to avoid Statement highlighted text disappears. - * Clear the attributes, needed when changing the t_Co value. */ - if (t_colors > 8) { - do_highlight((*p_bg == 'l' - ? "Visual cterm=NONE ctermbg=LightGrey" - : "Visual cterm=NONE ctermbg=DarkGrey"), false, true); - } else { - do_highlight("Visual cterm=reverse ctermbg=NONE", false, true); - if (*p_bg == 'l') { - do_highlight("Search ctermfg=black", false, true); - } - } - - syn_init_cmdline_highlight(false, false); -} - -/* - * Load color file "name". - * Return OK for success, FAIL for failure. - */ -int load_colors(char_u *name) -{ - char_u *buf; - int retval = FAIL; - static bool recursive = false; - - // When being called recursively, this is probably because setting - // 'background' caused the highlighting to be reloaded. This means it is - // working, thus we should return OK. - if (recursive) { - return OK; - } - - recursive = true; - size_t buflen = STRLEN(name) + 12; - buf = xmalloc(buflen); - apply_autocmds(EVENT_COLORSCHEMEPRE, name, curbuf->b_fname, false, curbuf); - snprintf((char *)buf, buflen, "colors/%s.vim", name); - retval = source_runtime((char *)buf, DIP_START + DIP_OPT); - if (retval == FAIL) { - snprintf((char *)buf, buflen, "colors/%s.lua", name); - retval = source_runtime((char *)buf, DIP_START + DIP_OPT); - } - xfree(buf); - apply_autocmds(EVENT_COLORSCHEME, name, curbuf->b_fname, false, curbuf); - - recursive = false; - - return retval; -} - -static char *(color_names[28]) = { - "Black", "DarkBlue", "DarkGreen", "DarkCyan", - "DarkRed", "DarkMagenta", "Brown", "DarkYellow", - "Gray", "Grey", "LightGray", "LightGrey", - "DarkGray", "DarkGrey", - "Blue", "LightBlue", "Green", "LightGreen", - "Cyan", "LightCyan", "Red", "LightRed", "Magenta", - "LightMagenta", "Yellow", "LightYellow", "White", "NONE" -}; -// indices: -// 0, 1, 2, 3, -// 4, 5, 6, 7, -// 8, 9, 10, 11, -// 12, 13, -// 14, 15, 16, 17, -// 18, 19, 20, 21, 22, -// 23, 24, 25, 26, 27 -static int color_numbers_16[28] = { 0, 1, 2, 3, - 4, 5, 6, 6, - 7, 7, 7, 7, - 8, 8, - 9, 9, 10, 10, - 11, 11, 12, 12, 13, - 13, 14, 14, 15, -1 }; -// for xterm with 88 colors... -static int color_numbers_88[28] = { 0, 4, 2, 6, - 1, 5, 32, 72, - 84, 84, 7, 7, - 82, 82, - 12, 43, 10, 61, - 14, 63, 9, 74, 13, - 75, 11, 78, 15, -1 }; -// for xterm with 256 colors... -static int color_numbers_256[28] = { 0, 4, 2, 6, - 1, 5, 130, 3, - 248, 248, 7, 7, - 242, 242, - 12, 81, 10, 121, - 14, 159, 9, 224, 13, - 225, 11, 229, 15, -1 }; -// for terminals with less than 16 colors... -static int color_numbers_8[28] = { 0, 4, 2, 6, - 1, 5, 3, 3, - 7, 7, 7, 7, - 0+8, 0+8, - 4+8, 4+8, 2+8, 2+8, - 6+8, 6+8, 1+8, 1+8, 5+8, - 5+8, 3+8, 3+8, 7+8, -1 }; - -// Lookup the "cterm" value to be used for color with index "idx" in -// color_names[]. -// "boldp" will be set to TRUE or FALSE for a foreground color when using 8 -// colors, otherwise it will be unchanged. -int lookup_color(const int idx, const bool foreground, TriState *const boldp) -{ - int color = color_numbers_16[idx]; - - // Use the _16 table to check if it's a valid color name. - if (color < 0) { - return -1; - } - - if (t_colors == 8) { - // t_Co is 8: use the 8 colors table - color = color_numbers_8[idx]; - if (foreground) { - // set/reset bold attribute to get light foreground - // colors (on some terminals, e.g. "linux") - if (color & 8) { - *boldp = kTrue; - } else { - *boldp = kFalse; - } - } - color &= 7; // truncate to 8 colors - } else if (t_colors == 16) { - color = color_numbers_8[idx]; - } else if (t_colors == 88) { - color = color_numbers_88[idx]; - } else if (t_colors >= 256) { - color = color_numbers_256[idx]; - } - return color; -} - - -/// Handle ":highlight" command -/// -/// When using ":highlight clear" this is called recursively for each group with -/// forceit and init being both true. -/// -/// @param[in] line Command arguments. -/// @param[in] forceit True when bang is given, allows to link group even if -/// it has its own settings. -/// @param[in] init True when initializing. -void do_highlight(const char *line, const bool forceit, const bool init) - FUNC_ATTR_NONNULL_ALL -{ - const char *name_end; - const char *linep; - const char *key_start; - const char *arg_start; - long i; - int off; - int len; - int attr; - int id; - int idx; - struct hl_group item_before; - bool did_change = false; - bool dodefault = false; - bool doclear = false; - bool dolink = false; - bool error = false; - int color; - bool is_normal_group = false; // "Normal" group - bool did_highlight_changed = false; - - // If no argument, list current highlighting. - if (ends_excmd((uint8_t)(*line))) { - for (i = 1; i <= highlight_ga.ga_len && !got_int; i++) { - // TODO(brammool): only call when the group has attributes set - highlight_list_one(i); - } - return; - } - - // Isolate the name. - name_end = (const char *)skiptowhite((const char_u *)line); - linep = (const char *)skipwhite((const char_u *)name_end); - - // Check for "default" argument. - if (strncmp(line, "default", name_end - line) == 0) { - dodefault = true; - line = linep; - name_end = (const char *)skiptowhite((const char_u *)line); - linep = (const char *)skipwhite((const char_u *)name_end); - } - - // Check for "clear" or "link" argument. - if (strncmp(line, "clear", name_end - line) == 0) { - doclear = true; - } else if (strncmp(line, "link", name_end - line) == 0) { - dolink = true; - } - - // ":highlight {group-name}": list highlighting for one group. - if (!doclear && !dolink && ends_excmd((uint8_t)(*linep))) { - id = syn_name2id_len((const char_u *)line, (int)(name_end - line)); - if (id == 0) { - semsg(_("E411: highlight group not found: %s"), line); - } else { - highlight_list_one(id); - } - return; - } - - // Handle ":highlight link {from} {to}" command. - if (dolink) { - const char *from_start = linep; - const char *from_end; - const char *to_start; - const char *to_end; - int from_id; - int to_id; - struct hl_group *hlgroup = NULL; - - from_end = (const char *)skiptowhite((const char_u *)from_start); - to_start = (const char *)skipwhite((const char_u *)from_end); - to_end = (const char *)skiptowhite((const char_u *)to_start); - - if (ends_excmd((uint8_t)(*from_start)) - || ends_excmd((uint8_t)(*to_start))) { - semsg(_("E412: Not enough arguments: \":highlight link %s\""), - from_start); - return; - } - - if (!ends_excmd(*skipwhite((const char_u *)to_end))) { - semsg(_("E413: Too many arguments: \":highlight link %s\""), from_start); - return; - } - - from_id = syn_check_group(from_start, (int)(from_end - from_start)); - if (strncmp(to_start, "NONE", 4) == 0) { - to_id = 0; - } else { - to_id = syn_check_group(to_start, (int)(to_end - to_start)); - } - - if (from_id > 0) { - hlgroup = &HL_TABLE()[from_id - 1]; - if (dodefault && (forceit || hlgroup->sg_deflink == 0)) { - hlgroup->sg_deflink = to_id; - hlgroup->sg_deflink_sctx = current_sctx; - hlgroup->sg_deflink_sctx.sc_lnum += sourcing_lnum; - } - } - - if (from_id > 0 && (!init || hlgroup->sg_set == 0)) { - // Don't allow a link when there already is some highlighting - // for the group, unless '!' is used - if (to_id > 0 && !forceit && !init - && hl_has_settings(from_id - 1, dodefault)) { - if (sourcing_name == NULL && !dodefault) { - emsg(_("E414: group has settings, highlight link ignored")); - } - } else if (hlgroup->sg_link != to_id - || hlgroup->sg_script_ctx.sc_sid != current_sctx.sc_sid - || hlgroup->sg_cleared) { - if (!init) { - hlgroup->sg_set |= SG_LINK; - } - hlgroup->sg_link = to_id; - hlgroup->sg_script_ctx = current_sctx; - hlgroup->sg_script_ctx.sc_lnum += sourcing_lnum; - hlgroup->sg_cleared = false; - redraw_all_later(SOME_VALID); - - // Only call highlight changed() once after multiple changes - need_highlight_changed = true; - } - } - - return; - } - - if (doclear) { - // ":highlight clear [group]" command. - line = linep; - if (ends_excmd((uint8_t)(*line))) { - do_unlet(S_LEN("colors_name"), true); - restore_cterm_colors(); - - // Clear all default highlight groups and load the defaults. - for (int j = 0; j < highlight_ga.ga_len; j++) { - highlight_clear(j); - } - init_highlight(true, true); - highlight_changed(); - redraw_all_later(NOT_VALID); - return; - } - name_end = (const char *)skiptowhite((const char_u *)line); - linep = (const char *)skipwhite((const char_u *)name_end); - } - - // Find the group name in the table. If it does not exist yet, add it. - id = syn_check_group(line, (int)(name_end - line)); - if (id == 0) { // Failed (out of memory). - return; - } - idx = id - 1; // Index is ID minus one. - - // Return if "default" was used and the group already has settings - if (dodefault && hl_has_settings(idx, true)) { - return; - } - - // Make a copy so we can check if any attribute actually changed - item_before = HL_TABLE()[idx]; - is_normal_group = (STRCMP(HL_TABLE()[idx].sg_name_u, "NORMAL") == 0); - - // Clear the highlighting for ":hi clear {group}" and ":hi clear". - if (doclear || (forceit && init)) { - highlight_clear(idx); - if (!doclear) { - HL_TABLE()[idx].sg_set = 0; - } - } - - char *key = NULL; - char *arg = NULL; - if (!doclear) { - while (!ends_excmd((uint8_t)(*linep))) { - key_start = linep; - if (*linep == '=') { - semsg(_("E415: unexpected equal sign: %s"), key_start); - error = true; - break; - } - - // Isolate the key ("term", "ctermfg", "ctermbg", "font", "guifg", - // "guibg" or "guisp"). - while (*linep && !ascii_iswhite(*linep) && *linep != '=') { - linep++; - } - xfree(key); - key = (char *)vim_strnsave_up((const char_u *)key_start, - linep - key_start); - linep = (const char *)skipwhite((const char_u *)linep); - - if (strcmp(key, "NONE") == 0) { - if (!init || HL_TABLE()[idx].sg_set == 0) { - if (!init) { - HL_TABLE()[idx].sg_set |= SG_CTERM+SG_GUI; - } - highlight_clear(idx); - } - continue; - } - - // Check for the equal sign. - if (*linep != '=') { - semsg(_("E416: missing equal sign: %s"), key_start); - error = true; - break; - } - linep++; - - // Isolate the argument. - linep = (const char *)skipwhite((const char_u *)linep); - if (*linep == '\'') { // guifg='color name' - arg_start = ++linep; - linep = strchr(linep, '\''); - if (linep == NULL) { - semsg(_(e_invarg2), key_start); - error = true; - break; - } - } else { - arg_start = linep; - linep = (const char *)skiptowhite((const char_u *)linep); - } - if (linep == arg_start) { - semsg(_("E417: missing argument: %s"), key_start); - error = true; - break; - } - xfree(arg); - arg = xstrndup(arg_start, (size_t)(linep - arg_start)); - - if (*linep == '\'') { - linep++; - } - - // Store the argument. - if (strcmp(key, "TERM") == 0 - || strcmp(key, "CTERM") == 0 - || strcmp(key, "GUI") == 0) { - attr = 0; - off = 0; - while (arg[off] != NUL) { - for (i = ARRAY_SIZE(hl_attr_table); --i >= 0;) { - len = (int)STRLEN(hl_name_table[i]); - if (STRNICMP(arg + off, hl_name_table[i], len) == 0) { - attr |= hl_attr_table[i]; - off += len; - break; - } - } - if (i < 0) { - semsg(_("E418: Illegal value: %s"), arg); - error = true; - break; - } - if (arg[off] == ',') { // Another one follows. - off++; - } - } - if (error) { - break; - } - if (*key == 'C') { - if (!init || !(HL_TABLE()[idx].sg_set & SG_CTERM)) { - if (!init) { - HL_TABLE()[idx].sg_set |= SG_CTERM; - } - HL_TABLE()[idx].sg_cterm = attr; - HL_TABLE()[idx].sg_cterm_bold = false; - } - } else if (*key == 'G') { - if (!init || !(HL_TABLE()[idx].sg_set & SG_GUI)) { - if (!init) { - HL_TABLE()[idx].sg_set |= SG_GUI; - } - HL_TABLE()[idx].sg_gui = attr; - } - } - } else if (STRCMP(key, "FONT") == 0) { - // in non-GUI fonts are simply ignored - } else if (STRCMP(key, "CTERMFG") == 0 || STRCMP(key, "CTERMBG") == 0) { - if (!init || !(HL_TABLE()[idx].sg_set & SG_CTERM)) { - if (!init) { - HL_TABLE()[idx].sg_set |= SG_CTERM; - } - - /* When setting the foreground color, and previously the "bold" - * flag was set for a light color, reset it now */ - if (key[5] == 'F' && HL_TABLE()[idx].sg_cterm_bold) { - HL_TABLE()[idx].sg_cterm &= ~HL_BOLD; - HL_TABLE()[idx].sg_cterm_bold = false; - } - - if (ascii_isdigit(*arg)) { - color = atoi(arg); - } else if (STRICMP(arg, "fg") == 0) { - if (cterm_normal_fg_color) { - color = cterm_normal_fg_color - 1; - } else { - emsg(_("E419: FG color unknown")); - error = true; - break; - } - } else if (STRICMP(arg, "bg") == 0) { - if (cterm_normal_bg_color > 0) { - color = cterm_normal_bg_color - 1; - } else { - emsg(_("E420: BG color unknown")); - error = true; - break; - } - } else { - // Reduce calls to STRICMP a bit, it can be slow. - off = TOUPPER_ASC(*arg); - for (i = ARRAY_SIZE(color_names); --i >= 0;) { - if (off == color_names[i][0] - && STRICMP(arg + 1, color_names[i] + 1) == 0) { - break; - } - } - if (i < 0) { - semsg(_("E421: Color name or number not recognized: %s"), - key_start); - error = true; - break; - } - - TriState bold = kNone; - color = lookup_color(i, key[5] == 'F', &bold); - - // set/reset bold attribute to get light foreground - // colors (on some terminals, e.g. "linux") - if (bold == kTrue) { - HL_TABLE()[idx].sg_cterm |= HL_BOLD; - HL_TABLE()[idx].sg_cterm_bold = true; - } else if (bold == kFalse) { - HL_TABLE()[idx].sg_cterm &= ~HL_BOLD; - } - } - // Add one to the argument, to avoid zero. Zero is used for - // "NONE", then "color" is -1. - if (key[5] == 'F') { - HL_TABLE()[idx].sg_cterm_fg = color + 1; - if (is_normal_group) { - cterm_normal_fg_color = color + 1; - } - } else { - HL_TABLE()[idx].sg_cterm_bg = color + 1; - if (is_normal_group) { - cterm_normal_bg_color = color + 1; - if (!ui_rgb_attached()) { - if (color >= 0) { - int dark = -1; - - if (t_colors < 16) { - dark = (color == 0 || color == 4); - } else if (color < 16) { - // Limit the heuristic to the standard 16 colors - dark = (color < 7 || color == 8); - } - // Set the 'background' option if the value is - // wrong. - if (dark != -1 - && dark != (*p_bg == 'd') - && !option_was_set("bg")) { - set_option_value("bg", 0L, (dark ? "dark" : "light"), 0); - reset_option_was_set("bg"); - } - } - } - } - } - } - } else if (strcmp(key, "GUIFG") == 0) { - char **namep = &HL_TABLE()[idx].sg_rgb_fg_name; - - if (!init || !(HL_TABLE()[idx].sg_set & SG_GUI)) { - if (!init) { - HL_TABLE()[idx].sg_set |= SG_GUI; - } - - if (*namep == NULL || STRCMP(*namep, arg) != 0) { - xfree(*namep); - if (strcmp(arg, "NONE") != 0) { - *namep = xstrdup(arg); - HL_TABLE()[idx].sg_rgb_fg = name_to_color(arg); - } else { - *namep = NULL; - HL_TABLE()[idx].sg_rgb_fg = -1; - } - did_change = true; - } - } - - if (is_normal_group) { - normal_fg = HL_TABLE()[idx].sg_rgb_fg; - } - } else if (STRCMP(key, "GUIBG") == 0) { - char **const namep = &HL_TABLE()[idx].sg_rgb_bg_name; - - if (!init || !(HL_TABLE()[idx].sg_set & SG_GUI)) { - if (!init) { - HL_TABLE()[idx].sg_set |= SG_GUI; - } - - if (*namep == NULL || STRCMP(*namep, arg) != 0) { - xfree(*namep); - if (STRCMP(arg, "NONE") != 0) { - *namep = xstrdup(arg); - HL_TABLE()[idx].sg_rgb_bg = name_to_color(arg); - } else { - *namep = NULL; - HL_TABLE()[idx].sg_rgb_bg = -1; - } - did_change = true; - } - } - - if (is_normal_group) { - normal_bg = HL_TABLE()[idx].sg_rgb_bg; - } - } else if (strcmp(key, "GUISP") == 0) { - char **const namep = &HL_TABLE()[idx].sg_rgb_sp_name; - - if (!init || !(HL_TABLE()[idx].sg_set & SG_GUI)) { - if (!init) { - HL_TABLE()[idx].sg_set |= SG_GUI; - } - - if (*namep == NULL || STRCMP(*namep, arg) != 0) { - xfree(*namep); - if (strcmp(arg, "NONE") != 0) { - *namep = xstrdup(arg); - HL_TABLE()[idx].sg_rgb_sp = name_to_color(arg); - } else { - *namep = NULL; - HL_TABLE()[idx].sg_rgb_sp = -1; - } - did_change = true; - } - } - - if (is_normal_group) { - normal_sp = HL_TABLE()[idx].sg_rgb_sp; - } - } else if (strcmp(key, "START") == 0 || strcmp(key, "STOP") == 0) { - // Ignored for now - } else if (strcmp(key, "BLEND") == 0) { - if (strcmp(arg, "NONE") != 0) { - HL_TABLE()[idx].sg_blend = strtol(arg, NULL, 10); - } else { - HL_TABLE()[idx].sg_blend = -1; - } - } else { - semsg(_("E423: Illegal argument: %s"), key_start); - error = true; - break; - } - HL_TABLE()[idx].sg_cleared = false; - - // When highlighting has been given for a group, don't link it. - if (!init || !(HL_TABLE()[idx].sg_set & SG_LINK)) { - HL_TABLE()[idx].sg_link = 0; - } - - // Continue with next argument. - linep = (const char *)skipwhite((const char_u *)linep); - } - } - - // If there is an error, and it's a new entry, remove it from the table. - if (error && idx == highlight_ga.ga_len) { - syn_unadd_group(); - } else { - if (!error && is_normal_group) { - // Need to update all groups, because they might be using "bg" and/or - // "fg", which have been changed now. - highlight_attr_set_all(); - - if (!ui_has(kUILinegrid) && starting == 0) { - // Older UIs assume that we clear the screen after normal group is - // changed - ui_refresh(); - } else { - // TUI and newer UIs will repaint the screen themselves. NOT_VALID - // redraw below will still handle usages of guibg=fg etc. - ui_default_colors_set(); - } - did_highlight_changed = true; - redraw_all_later(NOT_VALID); - } else { - set_hl_attr(idx); - } - HL_TABLE()[idx].sg_script_ctx = current_sctx; - HL_TABLE()[idx].sg_script_ctx.sc_lnum += sourcing_lnum; - } - xfree(key); - xfree(arg); - - // Only call highlight_changed() once, after a sequence of highlight - // commands, and only if an attribute actually changed - if ((did_change - || memcmp(&HL_TABLE()[idx], &item_before, sizeof(item_before)) != 0) - && !did_highlight_changed) { - // Do not trigger a redraw when highlighting is changed while - // redrawing. This may happen when evaluating 'statusline' changes the - // StatusLine group. - if (!updating_screen) { - redraw_all_later(NOT_VALID); - } - need_highlight_changed = true; - } -} - -#if defined(EXITFREE) -void free_highlight(void) -{ - for (int i = 0; i < highlight_ga.ga_len; ++i) { - highlight_clear(i); - xfree(HL_TABLE()[i].sg_name); - xfree(HL_TABLE()[i].sg_name_u); - } - ga_clear(&highlight_ga); - map_destroy(cstr_t, int)(&highlight_unames); -} - -#endif - -/* - * Reset the cterm colors to what they were before Vim was started, if - * possible. Otherwise reset them to zero. - */ -void restore_cterm_colors(void) -{ - normal_fg = -1; - normal_bg = -1; - normal_sp = -1; - cterm_normal_fg_color = 0; - cterm_normal_bg_color = 0; -} - -/// @param check_link if true also check for an existing link. -/// -/// @return TRUE if highlight group "idx" has any settings. -static int hl_has_settings(int idx, bool check_link) -{ - return HL_TABLE()[idx].sg_cleared == 0 - && (HL_TABLE()[idx].sg_attr != 0 - || HL_TABLE()[idx].sg_cterm_fg != 0 - || HL_TABLE()[idx].sg_cterm_bg != 0 - || HL_TABLE()[idx].sg_rgb_fg_name != NULL - || HL_TABLE()[idx].sg_rgb_bg_name != NULL - || HL_TABLE()[idx].sg_rgb_sp_name != NULL - || (check_link && (HL_TABLE()[idx].sg_set & SG_LINK))); -} - -/* - * Clear highlighting for one group. - */ -static void highlight_clear(int idx) -{ - HL_TABLE()[idx].sg_cleared = true; - - HL_TABLE()[idx].sg_attr = 0; - HL_TABLE()[idx].sg_cterm = 0; - HL_TABLE()[idx].sg_cterm_bold = false; - HL_TABLE()[idx].sg_cterm_fg = 0; - HL_TABLE()[idx].sg_cterm_bg = 0; - HL_TABLE()[idx].sg_gui = 0; - HL_TABLE()[idx].sg_rgb_fg = -1; - HL_TABLE()[idx].sg_rgb_bg = -1; - HL_TABLE()[idx].sg_rgb_sp = -1; - XFREE_CLEAR(HL_TABLE()[idx].sg_rgb_fg_name); - XFREE_CLEAR(HL_TABLE()[idx].sg_rgb_bg_name); - XFREE_CLEAR(HL_TABLE()[idx].sg_rgb_sp_name); - HL_TABLE()[idx].sg_blend = -1; - // Restore default link and context if they exist. Otherwise clears. - HL_TABLE()[idx].sg_link = HL_TABLE()[idx].sg_deflink; - // Since we set the default link, set the location to where the default - // link was set. - HL_TABLE()[idx].sg_script_ctx = HL_TABLE()[idx].sg_deflink_sctx; -} - - -/// \addtogroup LIST_XXX -/// @{ -#define LIST_ATTR 1 -#define LIST_STRING 2 -#define LIST_INT 3 -/// @} - -static void highlight_list_one(const int id) -{ - struct hl_group *const sgp = &HL_TABLE()[id - 1]; // index is ID minus one - bool didh = false; - - if (message_filtered(sgp->sg_name)) { - return; - } - - didh = highlight_list_arg(id, didh, LIST_ATTR, - sgp->sg_cterm, NULL, "cterm"); - didh = highlight_list_arg(id, didh, LIST_INT, - sgp->sg_cterm_fg, NULL, "ctermfg"); - didh = highlight_list_arg(id, didh, LIST_INT, - sgp->sg_cterm_bg, NULL, "ctermbg"); - - didh = highlight_list_arg(id, didh, LIST_ATTR, - sgp->sg_gui, NULL, "gui"); - didh = highlight_list_arg(id, didh, LIST_STRING, - 0, sgp->sg_rgb_fg_name, "guifg"); - didh = highlight_list_arg(id, didh, LIST_STRING, - 0, sgp->sg_rgb_bg_name, "guibg"); - didh = highlight_list_arg(id, didh, LIST_STRING, - 0, sgp->sg_rgb_sp_name, "guisp"); - - didh = highlight_list_arg(id, didh, LIST_INT, - sgp->sg_blend+1, NULL, "blend"); - - if (sgp->sg_link && !got_int) { - (void)syn_list_header(didh, 0, id, true); - didh = true; - msg_puts_attr("links to", HL_ATTR(HLF_D)); - msg_putchar(' '); - msg_outtrans(HL_TABLE()[HL_TABLE()[id - 1].sg_link - 1].sg_name); - } - - if (!didh) { - highlight_list_arg(id, didh, LIST_STRING, 0, "cleared", ""); - } - if (p_verbose > 0) { - last_set_msg(sgp->sg_script_ctx); - } -} - -Dictionary get_global_hl_defs(void) -{ - Dictionary rv = ARRAY_DICT_INIT; - for (int i = 1; i <= highlight_ga.ga_len && !got_int; i++) { - Dictionary attrs = ARRAY_DICT_INIT; - struct hl_group *h = &HL_TABLE()[i - 1]; - if (h->sg_attr > 0) { - attrs = hlattrs2dict(syn_attr2entry(h->sg_attr), true); - } else if (h->sg_link > 0) { - const char *link = (const char *)HL_TABLE()[h->sg_link - 1].sg_name; - PUT(attrs, "link", STRING_OBJ(cstr_to_string(link))); - } - PUT(rv, (const char *)h->sg_name, DICTIONARY_OBJ(attrs)); - } - - return rv; -} - -/// Outputs a highlight when doing ":hi MyHighlight" -/// -/// @param type one of \ref LIST_XXX -/// @param iarg integer argument used if \p type == LIST_INT -/// @param sarg string used if \p type == LIST_STRING -static bool highlight_list_arg(const int id, bool didh, const int type, int iarg, char *const sarg, - const char *const name) -{ - char buf[100]; - - if (got_int) { - return false; - } - if (type == LIST_STRING ? (sarg != NULL) : (iarg != 0)) { - char *ts = buf; - if (type == LIST_INT) { - snprintf((char *)buf, sizeof(buf), "%d", iarg - 1); - } else if (type == LIST_STRING) { - ts = sarg; - } else { // type == LIST_ATTR - buf[0] = NUL; - for (int i = 0; hl_attr_table[i] != 0; i++) { - if (iarg & hl_attr_table[i]) { - if (buf[0] != NUL) { - xstrlcat(buf, ",", 100); - } - xstrlcat(buf, hl_name_table[i], 100); - iarg &= ~hl_attr_table[i]; // don't want "inverse" - } - } - } - - (void)syn_list_header(didh, (int)(vim_strsize((char_u *)ts) + STRLEN(name) - + 1), id, false); - didh = true; - if (!got_int) { - if (*name != NUL) { - msg_puts_attr(name, HL_ATTR(HLF_D)); - msg_puts_attr("=", HL_ATTR(HLF_D)); - } - msg_outtrans((char_u *)ts); - } - } - return didh; -} - -/// Check whether highlight group has attribute -/// -/// @param[in] id Highlight group to check. -/// @param[in] flag Attribute to check. -/// @param[in] modec 'g' for GUI, 'c' for term. -/// -/// @return "1" if highlight group has attribute, NULL otherwise. -const char *highlight_has_attr(const int id, const int flag, const int modec) - FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE -{ - int attr; - - if (id <= 0 || id > highlight_ga.ga_len) { - return NULL; - } - - if (modec == 'g') { - attr = HL_TABLE()[id - 1].sg_gui; - } else { - attr = HL_TABLE()[id - 1].sg_cterm; - } - - return (attr & flag) ? "1" : NULL; -} - -/// Return color name of the given highlight group -/// -/// @param[in] id Highlight group to work with. -/// @param[in] what What to return: one of "font", "fg", "bg", "sp", "fg#", -/// "bg#" or "sp#". -/// @param[in] modec 'g' for GUI, 'c' for cterm and 't' for term. -/// -/// @return color name, possibly in a static buffer. Buffer will be overwritten -/// on next highlight_color() call. May return NULL. -const char *highlight_color(const int id, const char *const what, const int modec) - FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL -{ - static char name[20]; - int n; - bool fg = false; - bool sp = false; - bool font = false; - - if (id <= 0 || id > highlight_ga.ga_len) { - return NULL; - } - - if (TOLOWER_ASC(what[0]) == 'f' && TOLOWER_ASC(what[1]) == 'g') { - fg = true; - } else if (TOLOWER_ASC(what[0]) == 'f' && TOLOWER_ASC(what[1]) == 'o' - && TOLOWER_ASC(what[2]) == 'n' && TOLOWER_ASC(what[3]) == 't') { - font = true; - } else if (TOLOWER_ASC(what[0]) == 's' && TOLOWER_ASC(what[1]) == 'p') { - sp = true; - } else if (!(TOLOWER_ASC(what[0]) == 'b' && TOLOWER_ASC(what[1]) == 'g')) { - return NULL; - } - if (modec == 'g') { - if (what[2] == '#' && ui_rgb_attached()) { - if (fg) { - n = HL_TABLE()[id - 1].sg_rgb_fg; - } else if (sp) { - n = HL_TABLE()[id - 1].sg_rgb_sp; - } else { - n = HL_TABLE()[id - 1].sg_rgb_bg; - } - if (n < 0 || n > 0xffffff) { - return NULL; - } - snprintf(name, sizeof(name), "#%06x", n); - return name; - } - if (fg) { - return (const char *)HL_TABLE()[id - 1].sg_rgb_fg_name; - } - if (sp) { - return (const char *)HL_TABLE()[id - 1].sg_rgb_sp_name; - } - return (const char *)HL_TABLE()[id - 1].sg_rgb_bg_name; - } - if (font || sp) { - return NULL; - } - if (modec == 'c') { - if (fg) { - n = HL_TABLE()[id - 1].sg_cterm_fg - 1; - } else { - n = HL_TABLE()[id - 1].sg_cterm_bg - 1; - } - if (n < 0) { - return NULL; - } - snprintf(name, sizeof(name), "%d", n); - return name; - } - // term doesn't have color. - return NULL; -} - -/// Output the syntax list header. -/// -/// @param did_header did header already -/// @param outlen length of string that comes -/// @param id highlight group id -/// @param force_newline always start a new line -/// @return true when started a new line. -static bool syn_list_header(const bool did_header, const int outlen, const int id, - bool force_newline) -{ - int endcol = 19; - bool newline = true; - bool adjust = true; - - if (!did_header) { - msg_putchar('\n'); - if (got_int) { - return true; - } - msg_outtrans(HL_TABLE()[id - 1].sg_name); - endcol = 15; - } else if ((ui_has(kUIMessages) || msg_silent) && !force_newline) { - msg_putchar(' '); - adjust = false; - } else if (msg_col + outlen + 1 >= Columns || force_newline) { - msg_putchar('\n'); - if (got_int) { - return true; - } - } else { - if (msg_col >= endcol) { // wrap around is like starting a new line - newline = false; - } - } - - if (adjust) { - if (msg_col >= endcol) { - // output at least one space - endcol = msg_col + 1; - } - - msg_advance(endcol); - } - - // Show "xxx" with the attributes. - if (!did_header) { - msg_puts_attr("xxx", syn_id2attr(id)); - msg_putchar(' '); - } - - return newline; -} - -/// Set the attribute numbers for a highlight group. -/// Called after one of the attributes has changed. -/// @param idx corrected highlight index -static void set_hl_attr(int idx) -{ - HlAttrs at_en = HLATTRS_INIT; - struct hl_group *sgp = HL_TABLE() + idx; - - at_en.cterm_ae_attr = sgp->sg_cterm; - at_en.cterm_fg_color = sgp->sg_cterm_fg; - at_en.cterm_bg_color = sgp->sg_cterm_bg; - at_en.rgb_ae_attr = sgp->sg_gui; - // FIXME(tarruda): The "unset value" for rgb is -1, but since hlgroup is - // initialized with 0(by garray functions), check for sg_rgb_{f,b}g_name - // before setting attr_entry->{f,g}g_color to a other than -1 - at_en.rgb_fg_color = sgp->sg_rgb_fg_name ? sgp->sg_rgb_fg : -1; - at_en.rgb_bg_color = sgp->sg_rgb_bg_name ? sgp->sg_rgb_bg : -1; - at_en.rgb_sp_color = sgp->sg_rgb_sp_name ? sgp->sg_rgb_sp : -1; - at_en.hl_blend = sgp->sg_blend; - - sgp->sg_attr = hl_get_syn_attr(0, idx+1, at_en); - - // a cursor style uses this syn_id, make sure its attribute is updated. - if (cursor_mode_uses_syn_id(idx+1)) { - ui_mode_info_set(); - } -} - -int syn_name2id(const char *name) - FUNC_ATTR_NONNULL_ALL -{ - return syn_name2id_len((char_u *)name, STRLEN(name)); -} - -/// Lookup a highlight group name and return its ID. -/// -/// @param highlight name e.g. 'Cursor', 'Normal' -/// @return the highlight id, else 0 if \p name does not exist -int syn_name2id_len(const char_u *name, size_t len) - FUNC_ATTR_NONNULL_ALL -{ - char name_u[201]; - - if (len == 0 || len > 200) { - // ID names over 200 chars don't deserve to be found! - return 0; - } - - // Avoid using stricmp() too much, it's slow on some systems */ - // Avoid alloc()/free(), these are slow too. - memcpy(name_u, name, len); - name_u[len] = '\0'; - vim_strup((char_u *)name_u); - - // map_get(..., int) returns 0 when no key is present, which is - // the expected value for missing highlight group. - return map_get(cstr_t, int)(&highlight_unames, name_u); -} - -/// Lookup a highlight group name and return its attributes. -/// Return zero if not found. -int syn_name2attr(const char_u *name) - FUNC_ATTR_NONNULL_ALL -{ - int id = syn_name2id((char *)name); - - if (id != 0) { - return syn_id2attr(id); - } - return 0; -} - -/* - * Return TRUE if highlight group "name" exists. - */ -int highlight_exists(const char *name) -{ - return syn_name2id(name) > 0; -} - -/* - * Return the name of highlight group "id". - * When not a valid ID return an empty string. - */ -char_u *syn_id2name(int id) -{ - if (id <= 0 || id > highlight_ga.ga_len) { - return (char_u *)""; - } - return HL_TABLE()[id - 1].sg_name; -} - - -/// Find highlight group name in the table and return its ID. -/// If it doesn't exist yet, a new entry is created. -/// -/// @param pp Highlight group name -/// @param len length of \p pp -/// -/// @return 0 for failure else the id of the group -int syn_check_group(const char *name, int len) -{ - int id = syn_name2id_len((char_u *)name, len); - if (id == 0) { // doesn't exist yet - return syn_add_group(vim_strnsave((char_u *)name, len)); - } - return id; -} - -/// Add new highlight group and return its ID. -/// -/// @param name must be an allocated string, it will be consumed. -/// @return 0 for failure, else the allocated group id -/// @see syn_check_group syn_unadd_group -static int syn_add_group(char_u *name) -{ - char_u *p; - - // Check that the name is ASCII letters, digits and underscore. - for (p = name; *p != NUL; ++p) { - if (!vim_isprintc(*p)) { - emsg(_("E669: Unprintable character in group name")); - xfree(name); - return 0; - } else if (!ASCII_ISALNUM(*p) && *p != '_') { - /* This is an error, but since there previously was no check only - * give a warning. */ - msg_source(HL_ATTR(HLF_W)); - msg(_("W18: Invalid character in group name")); - break; - } - } - - /* - * First call for this growarray: init growing array. - */ - if (highlight_ga.ga_data == NULL) { - highlight_ga.ga_itemsize = sizeof(struct hl_group); - ga_set_growsize(&highlight_ga, 10); - } - - if (highlight_ga.ga_len >= MAX_HL_ID) { - emsg(_("E849: Too many highlight and syntax groups")); - xfree(name); - return 0; - } - - char *const name_up = (char *)vim_strsave_up(name); - - // Append another syntax_highlight entry. - struct hl_group *hlgp = GA_APPEND_VIA_PTR(struct hl_group, &highlight_ga); - memset(hlgp, 0, sizeof(*hlgp)); - hlgp->sg_name = name; - hlgp->sg_rgb_bg = -1; - hlgp->sg_rgb_fg = -1; - hlgp->sg_rgb_sp = -1; - hlgp->sg_blend = -1; - hlgp->sg_name_u = name_up; - - int id = highlight_ga.ga_len; // ID is index plus one - - map_put(cstr_t, int)(&highlight_unames, name_up, id); - - return id; -} - -/// When, just after calling syn_add_group(), an error is discovered, this -/// function deletes the new name. -static void syn_unadd_group(void) -{ - highlight_ga.ga_len--; - HlGroup *item = &HL_TABLE()[highlight_ga.ga_len]; - map_del(cstr_t, int)(&highlight_unames, item->sg_name_u); - xfree(item->sg_name); - xfree(item->sg_name_u); -} - - -/// Translate a group ID to highlight attributes. -/// @see syn_attr2entry -int syn_id2attr(int hl_id) -{ - hl_id = syn_get_final_id(hl_id); - struct hl_group *sgp = &HL_TABLE()[hl_id - 1]; // index is ID minus one - - int attr = ns_get_hl(-1, hl_id, false, sgp->sg_set); - if (attr >= 0) { - return attr; - } - return sgp->sg_attr; -} - - -/* - * Translate a group ID to the final group ID (following links). - */ -int syn_get_final_id(int hl_id) -{ - int count; - - if (hl_id > highlight_ga.ga_len || hl_id < 1) { - return 0; // Can be called from eval!! - } - /* - * Follow links until there is no more. - * Look out for loops! Break after 100 links. - */ - for (count = 100; --count >= 0;) { - struct hl_group *sgp = &HL_TABLE()[hl_id - 1]; // index is ID minus one - - // ACHTUNG: when using "tmp" attribute (no link) the function might be - // called twice. it needs be smart enough to remember attr only to - // syn_id2attr time - int check = ns_get_hl(-1, hl_id, true, sgp->sg_set); - if (check == 0) { - return hl_id; // how dare! it broke the link! - } else if (check > 0) { - hl_id = check; - continue; - } - - - if (sgp->sg_link == 0 || sgp->sg_link > highlight_ga.ga_len) { - break; - } - hl_id = sgp->sg_link; - } - - return hl_id; -} - -/// Refresh the color attributes of all highlight groups. -void highlight_attr_set_all(void) -{ - for (int idx = 0; idx < highlight_ga.ga_len; idx++) { - struct hl_group *sgp = &HL_TABLE()[idx]; - if (sgp->sg_rgb_bg_name != NULL) { - sgp->sg_rgb_bg = name_to_color(sgp->sg_rgb_bg_name); - } - if (sgp->sg_rgb_fg_name != NULL) { - sgp->sg_rgb_fg = name_to_color(sgp->sg_rgb_fg_name); - } - if (sgp->sg_rgb_sp_name != NULL) { - sgp->sg_rgb_sp = name_to_color(sgp->sg_rgb_sp_name); - } - set_hl_attr(idx); - } -} - -// Apply difference between User[1-9] and HLF_S to HLF_SNC. -static void combine_stl_hlt(int id, int id_S, int id_alt, int hlcnt, int i, int hlf, int *table) - FUNC_ATTR_NONNULL_ALL -{ - struct hl_group *const hlt = HL_TABLE(); - - if (id_alt == 0) { - memset(&hlt[hlcnt + i], 0, sizeof(struct hl_group)); - hlt[hlcnt + i].sg_cterm = highlight_attr[hlf]; - hlt[hlcnt + i].sg_gui = highlight_attr[hlf]; - } else { - memmove(&hlt[hlcnt + i], &hlt[id_alt - 1], sizeof(struct hl_group)); - } - hlt[hlcnt + i].sg_link = 0; - - hlt[hlcnt + i].sg_cterm ^= hlt[id - 1].sg_cterm ^ hlt[id_S - 1].sg_cterm; - if (hlt[id - 1].sg_cterm_fg != hlt[id_S - 1].sg_cterm_fg) { - hlt[hlcnt + i].sg_cterm_fg = hlt[id - 1].sg_cterm_fg; - } - if (hlt[id - 1].sg_cterm_bg != hlt[id_S - 1].sg_cterm_bg) { - hlt[hlcnt + i].sg_cterm_bg = hlt[id - 1].sg_cterm_bg; - } - hlt[hlcnt + i].sg_gui ^= hlt[id - 1].sg_gui ^ hlt[id_S - 1].sg_gui; - if (hlt[id - 1].sg_rgb_fg != hlt[id_S - 1].sg_rgb_fg) { - hlt[hlcnt + i].sg_rgb_fg = hlt[id - 1].sg_rgb_fg; - } - if (hlt[id - 1].sg_rgb_bg != hlt[id_S - 1].sg_rgb_bg) { - hlt[hlcnt + i].sg_rgb_bg = hlt[id - 1].sg_rgb_bg; - } - if (hlt[id - 1].sg_rgb_sp != hlt[id_S - 1].sg_rgb_sp) { - hlt[hlcnt + i].sg_rgb_sp = hlt[id - 1].sg_rgb_sp; - } - highlight_ga.ga_len = hlcnt + i + 1; - set_hl_attr(hlcnt + i); // At long last we can apply - table[i] = syn_id2attr(hlcnt + i + 1); -} - -/// Translate highlight groups into attributes in highlight_attr[] and set up -/// the user highlights User1..9. A set of corresponding highlights to use on -/// top of HLF_SNC is computed. Called only when nvim starts and upon first -/// screen redraw after any :highlight command. -void highlight_changed(void) -{ - int id; - char userhl[30]; // use 30 to avoid compiler warning - int id_S = -1; - int id_SNC = 0; - int hlcnt; - - need_highlight_changed = false; - - /// Translate builtin highlight groups into attributes for quick lookup. - for (int hlf = 0; hlf < HLF_COUNT; hlf++) { - id = syn_check_group(hlf_names[hlf], STRLEN(hlf_names[hlf])); - if (id == 0) { - abort(); - } - int final_id = syn_get_final_id(id); - if (hlf == HLF_SNC) { - id_SNC = final_id; - } else if (hlf == HLF_S) { - id_S = final_id; - } - - highlight_attr[hlf] = hl_get_ui_attr(hlf, final_id, - hlf == HLF_INACTIVE); - - if (highlight_attr[hlf] != highlight_attr_last[hlf]) { - if (hlf == HLF_MSG) { - clear_cmdline = true; - } - ui_call_hl_group_set(cstr_as_string((char *)hlf_names[hlf]), - highlight_attr[hlf]); - highlight_attr_last[hlf] = highlight_attr[hlf]; - } - } - - // - // Setup the user highlights - // - // Temporarily utilize 10 more hl entries: - // 9 for User1-User9 combined with StatusLineNC - // 1 for StatusLine default - // Must to be in there simultaneously in case of table overflows in - // get_attr_entry() - ga_grow(&highlight_ga, 10); - hlcnt = highlight_ga.ga_len; - if (id_S == -1) { - // Make sure id_S is always valid to simplify code below. Use the last entry - memset(&HL_TABLE()[hlcnt + 9], 0, sizeof(struct hl_group)); - id_S = hlcnt + 10; - } - for (int i = 0; i < 9; i++) { - snprintf(userhl, sizeof(userhl), "User%d", i + 1); - id = syn_name2id(userhl); - if (id == 0) { - highlight_user[i] = 0; - highlight_stlnc[i] = 0; - } else { - highlight_user[i] = syn_id2attr(id); - combine_stl_hlt(id, id_S, id_SNC, hlcnt, i, HLF_SNC, highlight_stlnc); - } - } - highlight_ga.ga_len = hlcnt; -} - - -/* - * Handle command line completion for :highlight command. - */ -void set_context_in_highlight_cmd(expand_T *xp, const char *arg) -{ - // Default: expand group names. - xp->xp_context = EXPAND_HIGHLIGHT; - xp->xp_pattern = (char_u *)arg; - include_link = 2; - include_default = 1; - - // (part of) subcommand already typed - if (*arg != NUL) { - const char *p = (const char *)skiptowhite((const char_u *)arg); - if (*p != NUL) { // Past "default" or group name. - include_default = 0; - if (strncmp("default", arg, p - arg) == 0) { - arg = (const char *)skipwhite((const char_u *)p); - xp->xp_pattern = (char_u *)arg; - p = (const char *)skiptowhite((const char_u *)arg); - } - if (*p != NUL) { // past group name - include_link = 0; - if (arg[1] == 'i' && arg[0] == 'N') { - highlight_list(); - } - if (strncmp("link", arg, p - arg) == 0 - || strncmp("clear", arg, p - arg) == 0) { - xp->xp_pattern = skipwhite((const char_u *)p); - p = (const char *)skiptowhite(xp->xp_pattern); - if (*p != NUL) { // Past first group name. - xp->xp_pattern = skipwhite((const char_u *)p); - p = (const char *)skiptowhite(xp->xp_pattern); - } - } - if (*p != NUL) { // Past group name(s). - xp->xp_context = EXPAND_NOTHING; - } - } - } - } -} - -/* - * List highlighting matches in a nice way. - */ -static void highlight_list(void) -{ - int i; - - for (i = 10; --i >= 0;) { - highlight_list_two(i, HL_ATTR(HLF_D)); - } - for (i = 40; --i >= 0;) { - highlight_list_two(99, 0); - } -} - -static void highlight_list_two(int cnt, int attr) -{ - msg_puts_attr(&("N \bI \b! \b"[cnt / 11]), attr); - msg_clr_eos(); - ui_flush(); - os_delay(cnt == 99 ? 40L : (long)cnt * 50L, false); -} - - -/// Function given to ExpandGeneric() to obtain the list of group names. -const char *get_highlight_name(expand_T *const xp, int idx) - FUNC_ATTR_WARN_UNUSED_RESULT -{ - return get_highlight_name_ext(xp, idx, true); -} - - -/// Obtain a highlight group name. -/// -/// @param skip_cleared if true don't return a cleared entry. -const char *get_highlight_name_ext(expand_T *xp, int idx, bool skip_cleared) - FUNC_ATTR_WARN_UNUSED_RESULT -{ - if (idx < 0) { - return NULL; - } - - // Items are never removed from the table, skip the ones that were cleared. - if (skip_cleared && idx < highlight_ga.ga_len && HL_TABLE()[idx].sg_cleared) { - return ""; - } - - if (idx == highlight_ga.ga_len && include_none != 0) { - return "none"; - } else if (idx == highlight_ga.ga_len + include_none - && include_default != 0) { - return "default"; - } else if (idx == highlight_ga.ga_len + include_none + include_default - && include_link != 0) { - return "link"; - } else if (idx == highlight_ga.ga_len + include_none + include_default + 1 - && include_link != 0) { - return "clear"; - } else if (idx >= highlight_ga.ga_len) { - return NULL; - } - return (const char *)HL_TABLE()[idx].sg_name; -} - -color_name_table_T color_name_table[] = { - // Colors from rgb.txt - { "AliceBlue", RGB_(0xf0, 0xf8, 0xff) }, - { "AntiqueWhite", RGB_(0xfa, 0xeb, 0xd7) }, - { "AntiqueWhite1", RGB_(0xff, 0xef, 0xdb) }, - { "AntiqueWhite2", RGB_(0xee, 0xdf, 0xcc) }, - { "AntiqueWhite3", RGB_(0xcd, 0xc0, 0xb0) }, - { "AntiqueWhite4", RGB_(0x8b, 0x83, 0x78) }, - { "Aqua", RGB_(0x00, 0xff, 0xff) }, - { "Aquamarine", RGB_(0x7f, 0xff, 0xd4) }, - { "Aquamarine1", RGB_(0x7f, 0xff, 0xd4) }, - { "Aquamarine2", RGB_(0x76, 0xee, 0xc6) }, - { "Aquamarine3", RGB_(0x66, 0xcd, 0xaa) }, - { "Aquamarine4", RGB_(0x45, 0x8b, 0x74) }, - { "Azure", RGB_(0xf0, 0xff, 0xff) }, - { "Azure1", RGB_(0xf0, 0xff, 0xff) }, - { "Azure2", RGB_(0xe0, 0xee, 0xee) }, - { "Azure3", RGB_(0xc1, 0xcd, 0xcd) }, - { "Azure4", RGB_(0x83, 0x8b, 0x8b) }, - { "Beige", RGB_(0xf5, 0xf5, 0xdc) }, - { "Bisque", RGB_(0xff, 0xe4, 0xc4) }, - { "Bisque1", RGB_(0xff, 0xe4, 0xc4) }, - { "Bisque2", RGB_(0xee, 0xd5, 0xb7) }, - { "Bisque3", RGB_(0xcd, 0xb7, 0x9e) }, - { "Bisque4", RGB_(0x8b, 0x7d, 0x6b) }, - { "Black", RGB_(0x00, 0x00, 0x00) }, - { "BlanchedAlmond", RGB_(0xff, 0xeb, 0xcd) }, - { "Blue", RGB_(0x00, 0x00, 0xff) }, - { "Blue1", RGB_(0x0, 0x0, 0xff) }, - { "Blue2", RGB_(0x0, 0x0, 0xee) }, - { "Blue3", RGB_(0x0, 0x0, 0xcd) }, - { "Blue4", RGB_(0x0, 0x0, 0x8b) }, - { "BlueViolet", RGB_(0x8a, 0x2b, 0xe2) }, - { "Brown", RGB_(0xa5, 0x2a, 0x2a) }, - { "Brown1", RGB_(0xff, 0x40, 0x40) }, - { "Brown2", RGB_(0xee, 0x3b, 0x3b) }, - { "Brown3", RGB_(0xcd, 0x33, 0x33) }, - { "Brown4", RGB_(0x8b, 0x23, 0x23) }, - { "BurlyWood", RGB_(0xde, 0xb8, 0x87) }, - { "Burlywood1", RGB_(0xff, 0xd3, 0x9b) }, - { "Burlywood2", RGB_(0xee, 0xc5, 0x91) }, - { "Burlywood3", RGB_(0xcd, 0xaa, 0x7d) }, - { "Burlywood4", RGB_(0x8b, 0x73, 0x55) }, - { "CadetBlue", RGB_(0x5f, 0x9e, 0xa0) }, - { "CadetBlue1", RGB_(0x98, 0xf5, 0xff) }, - { "CadetBlue2", RGB_(0x8e, 0xe5, 0xee) }, - { "CadetBlue3", RGB_(0x7a, 0xc5, 0xcd) }, - { "CadetBlue4", RGB_(0x53, 0x86, 0x8b) }, - { "ChartReuse", RGB_(0x7f, 0xff, 0x00) }, - { "Chartreuse1", RGB_(0x7f, 0xff, 0x0) }, - { "Chartreuse2", RGB_(0x76, 0xee, 0x0) }, - { "Chartreuse3", RGB_(0x66, 0xcd, 0x0) }, - { "Chartreuse4", RGB_(0x45, 0x8b, 0x0) }, - { "Chocolate", RGB_(0xd2, 0x69, 0x1e) }, - { "Chocolate1", RGB_(0xff, 0x7f, 0x24) }, - { "Chocolate2", RGB_(0xee, 0x76, 0x21) }, - { "Chocolate3", RGB_(0xcd, 0x66, 0x1d) }, - { "Chocolate4", RGB_(0x8b, 0x45, 0x13) }, - { "Coral", RGB_(0xff, 0x7f, 0x50) }, - { "Coral1", RGB_(0xff, 0x72, 0x56) }, - { "Coral2", RGB_(0xee, 0x6a, 0x50) }, - { "Coral3", RGB_(0xcd, 0x5b, 0x45) }, - { "Coral4", RGB_(0x8b, 0x3e, 0x2f) }, - { "CornFlowerBlue", RGB_(0x64, 0x95, 0xed) }, - { "Cornsilk", RGB_(0xff, 0xf8, 0xdc) }, - { "Cornsilk1", RGB_(0xff, 0xf8, 0xdc) }, - { "Cornsilk2", RGB_(0xee, 0xe8, 0xcd) }, - { "Cornsilk3", RGB_(0xcd, 0xc8, 0xb1) }, - { "Cornsilk4", RGB_(0x8b, 0x88, 0x78) }, - { "Crimson", RGB_(0xdc, 0x14, 0x3c) }, - { "Cyan", RGB_(0x00, 0xff, 0xff) }, - { "Cyan1", RGB_(0x0, 0xff, 0xff) }, - { "Cyan2", RGB_(0x0, 0xee, 0xee) }, - { "Cyan3", RGB_(0x0, 0xcd, 0xcd) }, - { "Cyan4", RGB_(0x0, 0x8b, 0x8b) }, - { "DarkBlue", RGB_(0x00, 0x00, 0x8b) }, - { "DarkCyan", RGB_(0x00, 0x8b, 0x8b) }, - { "DarkGoldenRod", RGB_(0xb8, 0x86, 0x0b) }, - { "DarkGoldenrod1", RGB_(0xff, 0xb9, 0xf) }, - { "DarkGoldenrod2", RGB_(0xee, 0xad, 0xe) }, - { "DarkGoldenrod3", RGB_(0xcd, 0x95, 0xc) }, - { "DarkGoldenrod4", RGB_(0x8b, 0x65, 0x8) }, - { "DarkGray", RGB_(0xa9, 0xa9, 0xa9) }, - { "DarkGreen", RGB_(0x00, 0x64, 0x00) }, - { "DarkGrey", RGB_(0xa9, 0xa9, 0xa9) }, - { "DarkKhaki", RGB_(0xbd, 0xb7, 0x6b) }, - { "DarkMagenta", RGB_(0x8b, 0x00, 0x8b) }, - { "DarkOliveGreen", RGB_(0x55, 0x6b, 0x2f) }, - { "DarkOliveGreen1", RGB_(0xca, 0xff, 0x70) }, - { "DarkOliveGreen2", RGB_(0xbc, 0xee, 0x68) }, - { "DarkOliveGreen3", RGB_(0xa2, 0xcd, 0x5a) }, - { "DarkOliveGreen4", RGB_(0x6e, 0x8b, 0x3d) }, - { "DarkOrange", RGB_(0xff, 0x8c, 0x00) }, - { "DarkOrange1", RGB_(0xff, 0x7f, 0x0) }, - { "DarkOrange2", RGB_(0xee, 0x76, 0x0) }, - { "DarkOrange3", RGB_(0xcd, 0x66, 0x0) }, - { "DarkOrange4", RGB_(0x8b, 0x45, 0x0) }, - { "DarkOrchid", RGB_(0x99, 0x32, 0xcc) }, - { "DarkOrchid1", RGB_(0xbf, 0x3e, 0xff) }, - { "DarkOrchid2", RGB_(0xb2, 0x3a, 0xee) }, - { "DarkOrchid3", RGB_(0x9a, 0x32, 0xcd) }, - { "DarkOrchid4", RGB_(0x68, 0x22, 0x8b) }, - { "DarkRed", RGB_(0x8b, 0x00, 0x00) }, - { "DarkSalmon", RGB_(0xe9, 0x96, 0x7a) }, - { "DarkSeaGreen", RGB_(0x8f, 0xbc, 0x8f) }, - { "DarkSeaGreen1", RGB_(0xc1, 0xff, 0xc1) }, - { "DarkSeaGreen2", RGB_(0xb4, 0xee, 0xb4) }, - { "DarkSeaGreen3", RGB_(0x9b, 0xcd, 0x9b) }, - { "DarkSeaGreen4", RGB_(0x69, 0x8b, 0x69) }, - { "DarkSlateBlue", RGB_(0x48, 0x3d, 0x8b) }, - { "DarkSlateGray", RGB_(0x2f, 0x4f, 0x4f) }, - { "DarkSlateGray1", RGB_(0x97, 0xff, 0xff) }, - { "DarkSlateGray2", RGB_(0x8d, 0xee, 0xee) }, - { "DarkSlateGray3", RGB_(0x79, 0xcd, 0xcd) }, - { "DarkSlateGray4", RGB_(0x52, 0x8b, 0x8b) }, - { "DarkSlateGrey", RGB_(0x2f, 0x4f, 0x4f) }, - { "DarkTurquoise", RGB_(0x00, 0xce, 0xd1) }, - { "DarkViolet", RGB_(0x94, 0x00, 0xd3) }, - { "DarkYellow", RGB_(0xbb, 0xbb, 0x00) }, - { "DeepPink", RGB_(0xff, 0x14, 0x93) }, - { "DeepPink1", RGB_(0xff, 0x14, 0x93) }, - { "DeepPink2", RGB_(0xee, 0x12, 0x89) }, - { "DeepPink3", RGB_(0xcd, 0x10, 0x76) }, - { "DeepPink4", RGB_(0x8b, 0xa, 0x50) }, - { "DeepSkyBlue", RGB_(0x00, 0xbf, 0xff) }, - { "DeepSkyBlue1", RGB_(0x0, 0xbf, 0xff) }, - { "DeepSkyBlue2", RGB_(0x0, 0xb2, 0xee) }, - { "DeepSkyBlue3", RGB_(0x0, 0x9a, 0xcd) }, - { "DeepSkyBlue4", RGB_(0x0, 0x68, 0x8b) }, - { "DimGray", RGB_(0x69, 0x69, 0x69) }, - { "DimGrey", RGB_(0x69, 0x69, 0x69) }, - { "DodgerBlue", RGB_(0x1e, 0x90, 0xff) }, - { "DodgerBlue1", RGB_(0x1e, 0x90, 0xff) }, - { "DodgerBlue2", RGB_(0x1c, 0x86, 0xee) }, - { "DodgerBlue3", RGB_(0x18, 0x74, 0xcd) }, - { "DodgerBlue4", RGB_(0x10, 0x4e, 0x8b) }, - { "Firebrick", RGB_(0xb2, 0x22, 0x22) }, - { "Firebrick1", RGB_(0xff, 0x30, 0x30) }, - { "Firebrick2", RGB_(0xee, 0x2c, 0x2c) }, - { "Firebrick3", RGB_(0xcd, 0x26, 0x26) }, - { "Firebrick4", RGB_(0x8b, 0x1a, 0x1a) }, - { "FloralWhite", RGB_(0xff, 0xfa, 0xf0) }, - { "ForestGreen", RGB_(0x22, 0x8b, 0x22) }, - { "Fuchsia", RGB_(0xff, 0x00, 0xff) }, - { "Gainsboro", RGB_(0xdc, 0xdc, 0xdc) }, - { "GhostWhite", RGB_(0xf8, 0xf8, 0xff) }, - { "Gold", RGB_(0xff, 0xd7, 0x00) }, - { "Gold1", RGB_(0xff, 0xd7, 0x0) }, - { "Gold2", RGB_(0xee, 0xc9, 0x0) }, - { "Gold3", RGB_(0xcd, 0xad, 0x0) }, - { "Gold4", RGB_(0x8b, 0x75, 0x0) }, - { "GoldenRod", RGB_(0xda, 0xa5, 0x20) }, - { "Goldenrod1", RGB_(0xff, 0xc1, 0x25) }, - { "Goldenrod2", RGB_(0xee, 0xb4, 0x22) }, - { "Goldenrod3", RGB_(0xcd, 0x9b, 0x1d) }, - { "Goldenrod4", RGB_(0x8b, 0x69, 0x14) }, - { "Gray", RGB_(0x80, 0x80, 0x80) }, - { "Gray0", RGB_(0x0, 0x0, 0x0) }, - { "Gray1", RGB_(0x3, 0x3, 0x3) }, - { "Gray10", RGB_(0x1a, 0x1a, 0x1a) }, - { "Gray100", RGB_(0xff, 0xff, 0xff) }, - { "Gray11", RGB_(0x1c, 0x1c, 0x1c) }, - { "Gray12", RGB_(0x1f, 0x1f, 0x1f) }, - { "Gray13", RGB_(0x21, 0x21, 0x21) }, - { "Gray14", RGB_(0x24, 0x24, 0x24) }, - { "Gray15", RGB_(0x26, 0x26, 0x26) }, - { "Gray16", RGB_(0x29, 0x29, 0x29) }, - { "Gray17", RGB_(0x2b, 0x2b, 0x2b) }, - { "Gray18", RGB_(0x2e, 0x2e, 0x2e) }, - { "Gray19", RGB_(0x30, 0x30, 0x30) }, - { "Gray2", RGB_(0x5, 0x5, 0x5) }, - { "Gray20", RGB_(0x33, 0x33, 0x33) }, - { "Gray21", RGB_(0x36, 0x36, 0x36) }, - { "Gray22", RGB_(0x38, 0x38, 0x38) }, - { "Gray23", RGB_(0x3b, 0x3b, 0x3b) }, - { "Gray24", RGB_(0x3d, 0x3d, 0x3d) }, - { "Gray25", RGB_(0x40, 0x40, 0x40) }, - { "Gray26", RGB_(0x42, 0x42, 0x42) }, - { "Gray27", RGB_(0x45, 0x45, 0x45) }, - { "Gray28", RGB_(0x47, 0x47, 0x47) }, - { "Gray29", RGB_(0x4a, 0x4a, 0x4a) }, - { "Gray3", RGB_(0x8, 0x8, 0x8) }, - { "Gray30", RGB_(0x4d, 0x4d, 0x4d) }, - { "Gray31", RGB_(0x4f, 0x4f, 0x4f) }, - { "Gray32", RGB_(0x52, 0x52, 0x52) }, - { "Gray33", RGB_(0x54, 0x54, 0x54) }, - { "Gray34", RGB_(0x57, 0x57, 0x57) }, - { "Gray35", RGB_(0x59, 0x59, 0x59) }, - { "Gray36", RGB_(0x5c, 0x5c, 0x5c) }, - { "Gray37", RGB_(0x5e, 0x5e, 0x5e) }, - { "Gray38", RGB_(0x61, 0x61, 0x61) }, - { "Gray39", RGB_(0x63, 0x63, 0x63) }, - { "Gray4", RGB_(0xa, 0xa, 0xa) }, - { "Gray40", RGB_(0x66, 0x66, 0x66) }, - { "Gray41", RGB_(0x69, 0x69, 0x69) }, - { "Gray42", RGB_(0x6b, 0x6b, 0x6b) }, - { "Gray43", RGB_(0x6e, 0x6e, 0x6e) }, - { "Gray44", RGB_(0x70, 0x70, 0x70) }, - { "Gray45", RGB_(0x73, 0x73, 0x73) }, - { "Gray46", RGB_(0x75, 0x75, 0x75) }, - { "Gray47", RGB_(0x78, 0x78, 0x78) }, - { "Gray48", RGB_(0x7a, 0x7a, 0x7a) }, - { "Gray49", RGB_(0x7d, 0x7d, 0x7d) }, - { "Gray5", RGB_(0xd, 0xd, 0xd) }, - { "Gray50", RGB_(0x7f, 0x7f, 0x7f) }, - { "Gray51", RGB_(0x82, 0x82, 0x82) }, - { "Gray52", RGB_(0x85, 0x85, 0x85) }, - { "Gray53", RGB_(0x87, 0x87, 0x87) }, - { "Gray54", RGB_(0x8a, 0x8a, 0x8a) }, - { "Gray55", RGB_(0x8c, 0x8c, 0x8c) }, - { "Gray56", RGB_(0x8f, 0x8f, 0x8f) }, - { "Gray57", RGB_(0x91, 0x91, 0x91) }, - { "Gray58", RGB_(0x94, 0x94, 0x94) }, - { "Gray59", RGB_(0x96, 0x96, 0x96) }, - { "Gray6", RGB_(0xf, 0xf, 0xf) }, - { "Gray60", RGB_(0x99, 0x99, 0x99) }, - { "Gray61", RGB_(0x9c, 0x9c, 0x9c) }, - { "Gray62", RGB_(0x9e, 0x9e, 0x9e) }, - { "Gray63", RGB_(0xa1, 0xa1, 0xa1) }, - { "Gray64", RGB_(0xa3, 0xa3, 0xa3) }, - { "Gray65", RGB_(0xa6, 0xa6, 0xa6) }, - { "Gray66", RGB_(0xa8, 0xa8, 0xa8) }, - { "Gray67", RGB_(0xab, 0xab, 0xab) }, - { "Gray68", RGB_(0xad, 0xad, 0xad) }, - { "Gray69", RGB_(0xb0, 0xb0, 0xb0) }, - { "Gray7", RGB_(0x12, 0x12, 0x12) }, - { "Gray70", RGB_(0xb3, 0xb3, 0xb3) }, - { "Gray71", RGB_(0xb5, 0xb5, 0xb5) }, - { "Gray72", RGB_(0xb8, 0xb8, 0xb8) }, - { "Gray73", RGB_(0xba, 0xba, 0xba) }, - { "Gray74", RGB_(0xbd, 0xbd, 0xbd) }, - { "Gray75", RGB_(0xbf, 0xbf, 0xbf) }, - { "Gray76", RGB_(0xc2, 0xc2, 0xc2) }, - { "Gray77", RGB_(0xc4, 0xc4, 0xc4) }, - { "Gray78", RGB_(0xc7, 0xc7, 0xc7) }, - { "Gray79", RGB_(0xc9, 0xc9, 0xc9) }, - { "Gray8", RGB_(0x14, 0x14, 0x14) }, - { "Gray80", RGB_(0xcc, 0xcc, 0xcc) }, - { "Gray81", RGB_(0xcf, 0xcf, 0xcf) }, - { "Gray82", RGB_(0xd1, 0xd1, 0xd1) }, - { "Gray83", RGB_(0xd4, 0xd4, 0xd4) }, - { "Gray84", RGB_(0xd6, 0xd6, 0xd6) }, - { "Gray85", RGB_(0xd9, 0xd9, 0xd9) }, - { "Gray86", RGB_(0xdb, 0xdb, 0xdb) }, - { "Gray87", RGB_(0xde, 0xde, 0xde) }, - { "Gray88", RGB_(0xe0, 0xe0, 0xe0) }, - { "Gray89", RGB_(0xe3, 0xe3, 0xe3) }, - { "Gray9", RGB_(0x17, 0x17, 0x17) }, - { "Gray90", RGB_(0xe5, 0xe5, 0xe5) }, - { "Gray91", RGB_(0xe8, 0xe8, 0xe8) }, - { "Gray92", RGB_(0xeb, 0xeb, 0xeb) }, - { "Gray93", RGB_(0xed, 0xed, 0xed) }, - { "Gray94", RGB_(0xf0, 0xf0, 0xf0) }, - { "Gray95", RGB_(0xf2, 0xf2, 0xf2) }, - { "Gray96", RGB_(0xf5, 0xf5, 0xf5) }, - { "Gray97", RGB_(0xf7, 0xf7, 0xf7) }, - { "Gray98", RGB_(0xfa, 0xfa, 0xfa) }, - { "Gray99", RGB_(0xfc, 0xfc, 0xfc) }, - { "Green", RGB_(0x00, 0x80, 0x00) }, - { "Green1", RGB_(0x0, 0xff, 0x0) }, - { "Green2", RGB_(0x0, 0xee, 0x0) }, - { "Green3", RGB_(0x0, 0xcd, 0x0) }, - { "Green4", RGB_(0x0, 0x8b, 0x0) }, - { "GreenYellow", RGB_(0xad, 0xff, 0x2f) }, - { "Grey", RGB_(0x80, 0x80, 0x80) }, - { "Grey0", RGB_(0x0, 0x0, 0x0) }, - { "Grey1", RGB_(0x3, 0x3, 0x3) }, - { "Grey10", RGB_(0x1a, 0x1a, 0x1a) }, - { "Grey100", RGB_(0xff, 0xff, 0xff) }, - { "Grey11", RGB_(0x1c, 0x1c, 0x1c) }, - { "Grey12", RGB_(0x1f, 0x1f, 0x1f) }, - { "Grey13", RGB_(0x21, 0x21, 0x21) }, - { "Grey14", RGB_(0x24, 0x24, 0x24) }, - { "Grey15", RGB_(0x26, 0x26, 0x26) }, - { "Grey16", RGB_(0x29, 0x29, 0x29) }, - { "Grey17", RGB_(0x2b, 0x2b, 0x2b) }, - { "Grey18", RGB_(0x2e, 0x2e, 0x2e) }, - { "Grey19", RGB_(0x30, 0x30, 0x30) }, - { "Grey2", RGB_(0x5, 0x5, 0x5) }, - { "Grey20", RGB_(0x33, 0x33, 0x33) }, - { "Grey21", RGB_(0x36, 0x36, 0x36) }, - { "Grey22", RGB_(0x38, 0x38, 0x38) }, - { "Grey23", RGB_(0x3b, 0x3b, 0x3b) }, - { "Grey24", RGB_(0x3d, 0x3d, 0x3d) }, - { "Grey25", RGB_(0x40, 0x40, 0x40) }, - { "Grey26", RGB_(0x42, 0x42, 0x42) }, - { "Grey27", RGB_(0x45, 0x45, 0x45) }, - { "Grey28", RGB_(0x47, 0x47, 0x47) }, - { "Grey29", RGB_(0x4a, 0x4a, 0x4a) }, - { "Grey3", RGB_(0x8, 0x8, 0x8) }, - { "Grey30", RGB_(0x4d, 0x4d, 0x4d) }, - { "Grey31", RGB_(0x4f, 0x4f, 0x4f) }, - { "Grey32", RGB_(0x52, 0x52, 0x52) }, - { "Grey33", RGB_(0x54, 0x54, 0x54) }, - { "Grey34", RGB_(0x57, 0x57, 0x57) }, - { "Grey35", RGB_(0x59, 0x59, 0x59) }, - { "Grey36", RGB_(0x5c, 0x5c, 0x5c) }, - { "Grey37", RGB_(0x5e, 0x5e, 0x5e) }, - { "Grey38", RGB_(0x61, 0x61, 0x61) }, - { "Grey39", RGB_(0x63, 0x63, 0x63) }, - { "Grey4", RGB_(0xa, 0xa, 0xa) }, - { "Grey40", RGB_(0x66, 0x66, 0x66) }, - { "Grey41", RGB_(0x69, 0x69, 0x69) }, - { "Grey42", RGB_(0x6b, 0x6b, 0x6b) }, - { "Grey43", RGB_(0x6e, 0x6e, 0x6e) }, - { "Grey44", RGB_(0x70, 0x70, 0x70) }, - { "Grey45", RGB_(0x73, 0x73, 0x73) }, - { "Grey46", RGB_(0x75, 0x75, 0x75) }, - { "Grey47", RGB_(0x78, 0x78, 0x78) }, - { "Grey48", RGB_(0x7a, 0x7a, 0x7a) }, - { "Grey49", RGB_(0x7d, 0x7d, 0x7d) }, - { "Grey5", RGB_(0xd, 0xd, 0xd) }, - { "Grey50", RGB_(0x7f, 0x7f, 0x7f) }, - { "Grey51", RGB_(0x82, 0x82, 0x82) }, - { "Grey52", RGB_(0x85, 0x85, 0x85) }, - { "Grey53", RGB_(0x87, 0x87, 0x87) }, - { "Grey54", RGB_(0x8a, 0x8a, 0x8a) }, - { "Grey55", RGB_(0x8c, 0x8c, 0x8c) }, - { "Grey56", RGB_(0x8f, 0x8f, 0x8f) }, - { "Grey57", RGB_(0x91, 0x91, 0x91) }, - { "Grey58", RGB_(0x94, 0x94, 0x94) }, - { "Grey59", RGB_(0x96, 0x96, 0x96) }, - { "Grey6", RGB_(0xf, 0xf, 0xf) }, - { "Grey60", RGB_(0x99, 0x99, 0x99) }, - { "Grey61", RGB_(0x9c, 0x9c, 0x9c) }, - { "Grey62", RGB_(0x9e, 0x9e, 0x9e) }, - { "Grey63", RGB_(0xa1, 0xa1, 0xa1) }, - { "Grey64", RGB_(0xa3, 0xa3, 0xa3) }, - { "Grey65", RGB_(0xa6, 0xa6, 0xa6) }, - { "Grey66", RGB_(0xa8, 0xa8, 0xa8) }, - { "Grey67", RGB_(0xab, 0xab, 0xab) }, - { "Grey68", RGB_(0xad, 0xad, 0xad) }, - { "Grey69", RGB_(0xb0, 0xb0, 0xb0) }, - { "Grey7", RGB_(0x12, 0x12, 0x12) }, - { "Grey70", RGB_(0xb3, 0xb3, 0xb3) }, - { "Grey71", RGB_(0xb5, 0xb5, 0xb5) }, - { "Grey72", RGB_(0xb8, 0xb8, 0xb8) }, - { "Grey73", RGB_(0xba, 0xba, 0xba) }, - { "Grey74", RGB_(0xbd, 0xbd, 0xbd) }, - { "Grey75", RGB_(0xbf, 0xbf, 0xbf) }, - { "Grey76", RGB_(0xc2, 0xc2, 0xc2) }, - { "Grey77", RGB_(0xc4, 0xc4, 0xc4) }, - { "Grey78", RGB_(0xc7, 0xc7, 0xc7) }, - { "Grey79", RGB_(0xc9, 0xc9, 0xc9) }, - { "Grey8", RGB_(0x14, 0x14, 0x14) }, - { "Grey80", RGB_(0xcc, 0xcc, 0xcc) }, - { "Grey81", RGB_(0xcf, 0xcf, 0xcf) }, - { "Grey82", RGB_(0xd1, 0xd1, 0xd1) }, - { "Grey83", RGB_(0xd4, 0xd4, 0xd4) }, - { "Grey84", RGB_(0xd6, 0xd6, 0xd6) }, - { "Grey85", RGB_(0xd9, 0xd9, 0xd9) }, - { "Grey86", RGB_(0xdb, 0xdb, 0xdb) }, - { "Grey87", RGB_(0xde, 0xde, 0xde) }, - { "Grey88", RGB_(0xe0, 0xe0, 0xe0) }, - { "Grey89", RGB_(0xe3, 0xe3, 0xe3) }, - { "Grey9", RGB_(0x17, 0x17, 0x17) }, - { "Grey90", RGB_(0xe5, 0xe5, 0xe5) }, - { "Grey91", RGB_(0xe8, 0xe8, 0xe8) }, - { "Grey92", RGB_(0xeb, 0xeb, 0xeb) }, - { "Grey93", RGB_(0xed, 0xed, 0xed) }, - { "Grey94", RGB_(0xf0, 0xf0, 0xf0) }, - { "Grey95", RGB_(0xf2, 0xf2, 0xf2) }, - { "Grey96", RGB_(0xf5, 0xf5, 0xf5) }, - { "Grey97", RGB_(0xf7, 0xf7, 0xf7) }, - { "Grey98", RGB_(0xfa, 0xfa, 0xfa) }, - { "Grey99", RGB_(0xfc, 0xfc, 0xfc) }, - { "Honeydew", RGB_(0xf0, 0xff, 0xf0) }, - { "Honeydew1", RGB_(0xf0, 0xff, 0xf0) }, - { "Honeydew2", RGB_(0xe0, 0xee, 0xe0) }, - { "Honeydew3", RGB_(0xc1, 0xcd, 0xc1) }, - { "Honeydew4", RGB_(0x83, 0x8b, 0x83) }, - { "HotPink", RGB_(0xff, 0x69, 0xb4) }, - { "HotPink1", RGB_(0xff, 0x6e, 0xb4) }, - { "HotPink2", RGB_(0xee, 0x6a, 0xa7) }, - { "HotPink3", RGB_(0xcd, 0x60, 0x90) }, - { "HotPink4", RGB_(0x8b, 0x3a, 0x62) }, - { "IndianRed", RGB_(0xcd, 0x5c, 0x5c) }, - { "IndianRed1", RGB_(0xff, 0x6a, 0x6a) }, - { "IndianRed2", RGB_(0xee, 0x63, 0x63) }, - { "IndianRed3", RGB_(0xcd, 0x55, 0x55) }, - { "IndianRed4", RGB_(0x8b, 0x3a, 0x3a) }, - { "Indigo", RGB_(0x4b, 0x00, 0x82) }, - { "Ivory", RGB_(0xff, 0xff, 0xf0) }, - { "Ivory1", RGB_(0xff, 0xff, 0xf0) }, - { "Ivory2", RGB_(0xee, 0xee, 0xe0) }, - { "Ivory3", RGB_(0xcd, 0xcd, 0xc1) }, - { "Ivory4", RGB_(0x8b, 0x8b, 0x83) }, - { "Khaki", RGB_(0xf0, 0xe6, 0x8c) }, - { "Khaki1", RGB_(0xff, 0xf6, 0x8f) }, - { "Khaki2", RGB_(0xee, 0xe6, 0x85) }, - { "Khaki3", RGB_(0xcd, 0xc6, 0x73) }, - { "Khaki4", RGB_(0x8b, 0x86, 0x4e) }, - { "Lavender", RGB_(0xe6, 0xe6, 0xfa) }, - { "LavenderBlush", RGB_(0xff, 0xf0, 0xf5) }, - { "LavenderBlush1", RGB_(0xff, 0xf0, 0xf5) }, - { "LavenderBlush2", RGB_(0xee, 0xe0, 0xe5) }, - { "LavenderBlush3", RGB_(0xcd, 0xc1, 0xc5) }, - { "LavenderBlush4", RGB_(0x8b, 0x83, 0x86) }, - { "LawnGreen", RGB_(0x7c, 0xfc, 0x00) }, - { "LemonChiffon", RGB_(0xff, 0xfa, 0xcd) }, - { "LemonChiffon1", RGB_(0xff, 0xfa, 0xcd) }, - { "LemonChiffon2", RGB_(0xee, 0xe9, 0xbf) }, - { "LemonChiffon3", RGB_(0xcd, 0xc9, 0xa5) }, - { "LemonChiffon4", RGB_(0x8b, 0x89, 0x70) }, - { "LightBlue", RGB_(0xad, 0xd8, 0xe6) }, - { "LightBlue1", RGB_(0xbf, 0xef, 0xff) }, - { "LightBlue2", RGB_(0xb2, 0xdf, 0xee) }, - { "LightBlue3", RGB_(0x9a, 0xc0, 0xcd) }, - { "LightBlue4", RGB_(0x68, 0x83, 0x8b) }, - { "LightCoral", RGB_(0xf0, 0x80, 0x80) }, - { "LightCyan", RGB_(0xe0, 0xff, 0xff) }, - { "LightCyan1", RGB_(0xe0, 0xff, 0xff) }, - { "LightCyan2", RGB_(0xd1, 0xee, 0xee) }, - { "LightCyan3", RGB_(0xb4, 0xcd, 0xcd) }, - { "LightCyan4", RGB_(0x7a, 0x8b, 0x8b) }, - { "LightGoldenrod", RGB_(0xee, 0xdd, 0x82) }, - { "LightGoldenrod1", RGB_(0xff, 0xec, 0x8b) }, - { "LightGoldenrod2", RGB_(0xee, 0xdc, 0x82) }, - { "LightGoldenrod3", RGB_(0xcd, 0xbe, 0x70) }, - { "LightGoldenrod4", RGB_(0x8b, 0x81, 0x4c) }, - { "LightGoldenRodYellow", RGB_(0xfa, 0xfa, 0xd2) }, - { "LightGray", RGB_(0xd3, 0xd3, 0xd3) }, - { "LightGreen", RGB_(0x90, 0xee, 0x90) }, - { "LightGrey", RGB_(0xd3, 0xd3, 0xd3) }, - { "LightMagenta", RGB_(0xff, 0xbb, 0xff) }, - { "LightPink", RGB_(0xff, 0xb6, 0xc1) }, - { "LightPink1", RGB_(0xff, 0xae, 0xb9) }, - { "LightPink2", RGB_(0xee, 0xa2, 0xad) }, - { "LightPink3", RGB_(0xcd, 0x8c, 0x95) }, - { "LightPink4", RGB_(0x8b, 0x5f, 0x65) }, - { "LightRed", RGB_(0xff, 0xbb, 0xbb) }, - { "LightSalmon", RGB_(0xff, 0xa0, 0x7a) }, - { "LightSalmon1", RGB_(0xff, 0xa0, 0x7a) }, - { "LightSalmon2", RGB_(0xee, 0x95, 0x72) }, - { "LightSalmon3", RGB_(0xcd, 0x81, 0x62) }, - { "LightSalmon4", RGB_(0x8b, 0x57, 0x42) }, - { "LightSeaGreen", RGB_(0x20, 0xb2, 0xaa) }, - { "LightSkyBlue", RGB_(0x87, 0xce, 0xfa) }, - { "LightSkyBlue1", RGB_(0xb0, 0xe2, 0xff) }, - { "LightSkyBlue2", RGB_(0xa4, 0xd3, 0xee) }, - { "LightSkyBlue3", RGB_(0x8d, 0xb6, 0xcd) }, - { "LightSkyBlue4", RGB_(0x60, 0x7b, 0x8b) }, - { "LightSlateBlue", RGB_(0x84, 0x70, 0xff) }, - { "LightSlateGray", RGB_(0x77, 0x88, 0x99) }, - { "LightSlateGrey", RGB_(0x77, 0x88, 0x99) }, - { "LightSteelBlue", RGB_(0xb0, 0xc4, 0xde) }, - { "LightSteelBlue1", RGB_(0xca, 0xe1, 0xff) }, - { "LightSteelBlue2", RGB_(0xbc, 0xd2, 0xee) }, - { "LightSteelBlue3", RGB_(0xa2, 0xb5, 0xcd) }, - { "LightSteelBlue4", RGB_(0x6e, 0x7b, 0x8b) }, - { "LightYellow", RGB_(0xff, 0xff, 0xe0) }, - { "LightYellow1", RGB_(0xff, 0xff, 0xe0) }, - { "LightYellow2", RGB_(0xee, 0xee, 0xd1) }, - { "LightYellow3", RGB_(0xcd, 0xcd, 0xb4) }, - { "LightYellow4", RGB_(0x8b, 0x8b, 0x7a) }, - { "Lime", RGB_(0x00, 0xff, 0x00) }, - { "LimeGreen", RGB_(0x32, 0xcd, 0x32) }, - { "Linen", RGB_(0xfa, 0xf0, 0xe6) }, - { "Magenta", RGB_(0xff, 0x00, 0xff) }, - { "Magenta1", RGB_(0xff, 0x0, 0xff) }, - { "Magenta2", RGB_(0xee, 0x0, 0xee) }, - { "Magenta3", RGB_(0xcd, 0x0, 0xcd) }, - { "Magenta4", RGB_(0x8b, 0x0, 0x8b) }, - { "Maroon", RGB_(0x80, 0x00, 0x00) }, - { "Maroon1", RGB_(0xff, 0x34, 0xb3) }, - { "Maroon2", RGB_(0xee, 0x30, 0xa7) }, - { "Maroon3", RGB_(0xcd, 0x29, 0x90) }, - { "Maroon4", RGB_(0x8b, 0x1c, 0x62) }, - { "MediumAquamarine", RGB_(0x66, 0xcd, 0xaa) }, - { "MediumBlue", RGB_(0x00, 0x00, 0xcd) }, - { "MediumOrchid", RGB_(0xba, 0x55, 0xd3) }, - { "MediumOrchid1", RGB_(0xe0, 0x66, 0xff) }, - { "MediumOrchid2", RGB_(0xd1, 0x5f, 0xee) }, - { "MediumOrchid3", RGB_(0xb4, 0x52, 0xcd) }, - { "MediumOrchid4", RGB_(0x7a, 0x37, 0x8b) }, - { "MediumPurple", RGB_(0x93, 0x70, 0xdb) }, - { "MediumPurple1", RGB_(0xab, 0x82, 0xff) }, - { "MediumPurple2", RGB_(0x9f, 0x79, 0xee) }, - { "MediumPurple3", RGB_(0x89, 0x68, 0xcd) }, - { "MediumPurple4", RGB_(0x5d, 0x47, 0x8b) }, - { "MediumSeaGreen", RGB_(0x3c, 0xb3, 0x71) }, - { "MediumSlateBlue", RGB_(0x7b, 0x68, 0xee) }, - { "MediumSpringGreen", RGB_(0x00, 0xfa, 0x9a) }, - { "MediumTurquoise", RGB_(0x48, 0xd1, 0xcc) }, - { "MediumVioletRed", RGB_(0xc7, 0x15, 0x85) }, - { "MidnightBlue", RGB_(0x19, 0x19, 0x70) }, - { "MintCream", RGB_(0xf5, 0xff, 0xfa) }, - { "MistyRose", RGB_(0xff, 0xe4, 0xe1) }, - { "MistyRose1", RGB_(0xff, 0xe4, 0xe1) }, - { "MistyRose2", RGB_(0xee, 0xd5, 0xd2) }, - { "MistyRose3", RGB_(0xcd, 0xb7, 0xb5) }, - { "MistyRose4", RGB_(0x8b, 0x7d, 0x7b) }, - { "Moccasin", RGB_(0xff, 0xe4, 0xb5) }, - { "NavajoWhite", RGB_(0xff, 0xde, 0xad) }, - { "NavajoWhite1", RGB_(0xff, 0xde, 0xad) }, - { "NavajoWhite2", RGB_(0xee, 0xcf, 0xa1) }, - { "NavajoWhite3", RGB_(0xcd, 0xb3, 0x8b) }, - { "NavajoWhite4", RGB_(0x8b, 0x79, 0x5e) }, - { "Navy", RGB_(0x00, 0x00, 0x80) }, - { "NavyBlue", RGB_(0x0, 0x0, 0x80) }, - { "OldLace", RGB_(0xfd, 0xf5, 0xe6) }, - { "Olive", RGB_(0x80, 0x80, 0x00) }, - { "OliveDrab", RGB_(0x6b, 0x8e, 0x23) }, - { "OliveDrab1", RGB_(0xc0, 0xff, 0x3e) }, - { "OliveDrab2", RGB_(0xb3, 0xee, 0x3a) }, - { "OliveDrab3", RGB_(0x9a, 0xcd, 0x32) }, - { "OliveDrab4", RGB_(0x69, 0x8b, 0x22) }, - { "Orange", RGB_(0xff, 0xa5, 0x00) }, - { "Orange1", RGB_(0xff, 0xa5, 0x0) }, - { "Orange2", RGB_(0xee, 0x9a, 0x0) }, - { "Orange3", RGB_(0xcd, 0x85, 0x0) }, - { "Orange4", RGB_(0x8b, 0x5a, 0x0) }, - { "OrangeRed", RGB_(0xff, 0x45, 0x00) }, - { "OrangeRed1", RGB_(0xff, 0x45, 0x0) }, - { "OrangeRed2", RGB_(0xee, 0x40, 0x0) }, - { "OrangeRed3", RGB_(0xcd, 0x37, 0x0) }, - { "OrangeRed4", RGB_(0x8b, 0x25, 0x0) }, - { "Orchid", RGB_(0xda, 0x70, 0xd6) }, - { "Orchid1", RGB_(0xff, 0x83, 0xfa) }, - { "Orchid2", RGB_(0xee, 0x7a, 0xe9) }, - { "Orchid3", RGB_(0xcd, 0x69, 0xc9) }, - { "Orchid4", RGB_(0x8b, 0x47, 0x89) }, - { "PaleGoldenRod", RGB_(0xee, 0xe8, 0xaa) }, - { "PaleGreen", RGB_(0x98, 0xfb, 0x98) }, - { "PaleGreen1", RGB_(0x9a, 0xff, 0x9a) }, - { "PaleGreen2", RGB_(0x90, 0xee, 0x90) }, - { "PaleGreen3", RGB_(0x7c, 0xcd, 0x7c) }, - { "PaleGreen4", RGB_(0x54, 0x8b, 0x54) }, - { "PaleTurquoise", RGB_(0xaf, 0xee, 0xee) }, - { "PaleTurquoise1", RGB_(0xbb, 0xff, 0xff) }, - { "PaleTurquoise2", RGB_(0xae, 0xee, 0xee) }, - { "PaleTurquoise3", RGB_(0x96, 0xcd, 0xcd) }, - { "PaleTurquoise4", RGB_(0x66, 0x8b, 0x8b) }, - { "PaleVioletRed", RGB_(0xdb, 0x70, 0x93) }, - { "PaleVioletRed1", RGB_(0xff, 0x82, 0xab) }, - { "PaleVioletRed2", RGB_(0xee, 0x79, 0x9f) }, - { "PaleVioletRed3", RGB_(0xcd, 0x68, 0x89) }, - { "PaleVioletRed4", RGB_(0x8b, 0x47, 0x5d) }, - { "PapayaWhip", RGB_(0xff, 0xef, 0xd5) }, - { "PeachPuff", RGB_(0xff, 0xda, 0xb9) }, - { "PeachPuff1", RGB_(0xff, 0xda, 0xb9) }, - { "PeachPuff2", RGB_(0xee, 0xcb, 0xad) }, - { "PeachPuff3", RGB_(0xcd, 0xaf, 0x95) }, - { "PeachPuff4", RGB_(0x8b, 0x77, 0x65) }, - { "Peru", RGB_(0xcd, 0x85, 0x3f) }, - { "Pink", RGB_(0xff, 0xc0, 0xcb) }, - { "Pink1", RGB_(0xff, 0xb5, 0xc5) }, - { "Pink2", RGB_(0xee, 0xa9, 0xb8) }, - { "Pink3", RGB_(0xcd, 0x91, 0x9e) }, - { "Pink4", RGB_(0x8b, 0x63, 0x6c) }, - { "Plum", RGB_(0xdd, 0xa0, 0xdd) }, - { "Plum1", RGB_(0xff, 0xbb, 0xff) }, - { "Plum2", RGB_(0xee, 0xae, 0xee) }, - { "Plum3", RGB_(0xcd, 0x96, 0xcd) }, - { "Plum4", RGB_(0x8b, 0x66, 0x8b) }, - { "PowderBlue", RGB_(0xb0, 0xe0, 0xe6) }, - { "Purple", RGB_(0x80, 0x00, 0x80) }, - { "Purple1", RGB_(0x9b, 0x30, 0xff) }, - { "Purple2", RGB_(0x91, 0x2c, 0xee) }, - { "Purple3", RGB_(0x7d, 0x26, 0xcd) }, - { "Purple4", RGB_(0x55, 0x1a, 0x8b) }, - { "RebeccaPurple", RGB_(0x66, 0x33, 0x99) }, - { "Red", RGB_(0xff, 0x00, 0x00) }, - { "Red1", RGB_(0xff, 0x0, 0x0) }, - { "Red2", RGB_(0xee, 0x0, 0x0) }, - { "Red3", RGB_(0xcd, 0x0, 0x0) }, - { "Red4", RGB_(0x8b, 0x0, 0x0) }, - { "RosyBrown", RGB_(0xbc, 0x8f, 0x8f) }, - { "RosyBrown1", RGB_(0xff, 0xc1, 0xc1) }, - { "RosyBrown2", RGB_(0xee, 0xb4, 0xb4) }, - { "RosyBrown3", RGB_(0xcd, 0x9b, 0x9b) }, - { "RosyBrown4", RGB_(0x8b, 0x69, 0x69) }, - { "RoyalBlue", RGB_(0x41, 0x69, 0xe1) }, - { "RoyalBlue1", RGB_(0x48, 0x76, 0xff) }, - { "RoyalBlue2", RGB_(0x43, 0x6e, 0xee) }, - { "RoyalBlue3", RGB_(0x3a, 0x5f, 0xcd) }, - { "RoyalBlue4", RGB_(0x27, 0x40, 0x8b) }, - { "SaddleBrown", RGB_(0x8b, 0x45, 0x13) }, - { "Salmon", RGB_(0xfa, 0x80, 0x72) }, - { "Salmon1", RGB_(0xff, 0x8c, 0x69) }, - { "Salmon2", RGB_(0xee, 0x82, 0x62) }, - { "Salmon3", RGB_(0xcd, 0x70, 0x54) }, - { "Salmon4", RGB_(0x8b, 0x4c, 0x39) }, - { "SandyBrown", RGB_(0xf4, 0xa4, 0x60) }, - { "SeaGreen", RGB_(0x2e, 0x8b, 0x57) }, - { "SeaGreen1", RGB_(0x54, 0xff, 0x9f) }, - { "SeaGreen2", RGB_(0x4e, 0xee, 0x94) }, - { "SeaGreen3", RGB_(0x43, 0xcd, 0x80) }, - { "SeaGreen4", RGB_(0x2e, 0x8b, 0x57) }, - { "SeaShell", RGB_(0xff, 0xf5, 0xee) }, - { "Seashell1", RGB_(0xff, 0xf5, 0xee) }, - { "Seashell2", RGB_(0xee, 0xe5, 0xde) }, - { "Seashell3", RGB_(0xcd, 0xc5, 0xbf) }, - { "Seashell4", RGB_(0x8b, 0x86, 0x82) }, - { "Sienna", RGB_(0xa0, 0x52, 0x2d) }, - { "Sienna1", RGB_(0xff, 0x82, 0x47) }, - { "Sienna2", RGB_(0xee, 0x79, 0x42) }, - { "Sienna3", RGB_(0xcd, 0x68, 0x39) }, - { "Sienna4", RGB_(0x8b, 0x47, 0x26) }, - { "Silver", RGB_(0xc0, 0xc0, 0xc0) }, - { "SkyBlue", RGB_(0x87, 0xce, 0xeb) }, - { "SkyBlue1", RGB_(0x87, 0xce, 0xff) }, - { "SkyBlue2", RGB_(0x7e, 0xc0, 0xee) }, - { "SkyBlue3", RGB_(0x6c, 0xa6, 0xcd) }, - { "SkyBlue4", RGB_(0x4a, 0x70, 0x8b) }, - { "SlateBlue", RGB_(0x6a, 0x5a, 0xcd) }, - { "SlateBlue1", RGB_(0x83, 0x6f, 0xff) }, - { "SlateBlue2", RGB_(0x7a, 0x67, 0xee) }, - { "SlateBlue3", RGB_(0x69, 0x59, 0xcd) }, - { "SlateBlue4", RGB_(0x47, 0x3c, 0x8b) }, - { "SlateGray", RGB_(0x70, 0x80, 0x90) }, - { "SlateGray1", RGB_(0xc6, 0xe2, 0xff) }, - { "SlateGray2", RGB_(0xb9, 0xd3, 0xee) }, - { "SlateGray3", RGB_(0x9f, 0xb6, 0xcd) }, - { "SlateGray4", RGB_(0x6c, 0x7b, 0x8b) }, - { "SlateGrey", RGB_(0x70, 0x80, 0x90) }, - { "Snow", RGB_(0xff, 0xfa, 0xfa) }, - { "Snow1", RGB_(0xff, 0xfa, 0xfa) }, - { "Snow2", RGB_(0xee, 0xe9, 0xe9) }, - { "Snow3", RGB_(0xcd, 0xc9, 0xc9) }, - { "Snow4", RGB_(0x8b, 0x89, 0x89) }, - { "SpringGreen", RGB_(0x00, 0xff, 0x7f) }, - { "SpringGreen1", RGB_(0x0, 0xff, 0x7f) }, - { "SpringGreen2", RGB_(0x0, 0xee, 0x76) }, - { "SpringGreen3", RGB_(0x0, 0xcd, 0x66) }, - { "SpringGreen4", RGB_(0x0, 0x8b, 0x45) }, - { "SteelBlue", RGB_(0x46, 0x82, 0xb4) }, - { "SteelBlue1", RGB_(0x63, 0xb8, 0xff) }, - { "SteelBlue2", RGB_(0x5c, 0xac, 0xee) }, - { "SteelBlue3", RGB_(0x4f, 0x94, 0xcd) }, - { "SteelBlue4", RGB_(0x36, 0x64, 0x8b) }, - { "Tan", RGB_(0xd2, 0xb4, 0x8c) }, - { "Tan1", RGB_(0xff, 0xa5, 0x4f) }, - { "Tan2", RGB_(0xee, 0x9a, 0x49) }, - { "Tan3", RGB_(0xcd, 0x85, 0x3f) }, - { "Tan4", RGB_(0x8b, 0x5a, 0x2b) }, - { "Teal", RGB_(0x00, 0x80, 0x80) }, - { "Thistle", RGB_(0xd8, 0xbf, 0xd8) }, - { "Thistle1", RGB_(0xff, 0xe1, 0xff) }, - { "Thistle2", RGB_(0xee, 0xd2, 0xee) }, - { "Thistle3", RGB_(0xcd, 0xb5, 0xcd) }, - { "Thistle4", RGB_(0x8b, 0x7b, 0x8b) }, - { "Tomato", RGB_(0xff, 0x63, 0x47) }, - { "Tomato1", RGB_(0xff, 0x63, 0x47) }, - { "Tomato2", RGB_(0xee, 0x5c, 0x42) }, - { "Tomato3", RGB_(0xcd, 0x4f, 0x39) }, - { "Tomato4", RGB_(0x8b, 0x36, 0x26) }, - { "Turquoise", RGB_(0x40, 0xe0, 0xd0) }, - { "Turquoise1", RGB_(0x0, 0xf5, 0xff) }, - { "Turquoise2", RGB_(0x0, 0xe5, 0xee) }, - { "Turquoise3", RGB_(0x0, 0xc5, 0xcd) }, - { "Turquoise4", RGB_(0x0, 0x86, 0x8b) }, - { "Violet", RGB_(0xee, 0x82, 0xee) }, - { "VioletRed", RGB_(0xd0, 0x20, 0x90) }, - { "VioletRed1", RGB_(0xff, 0x3e, 0x96) }, - { "VioletRed2", RGB_(0xee, 0x3a, 0x8c) }, - { "VioletRed3", RGB_(0xcd, 0x32, 0x78) }, - { "VioletRed4", RGB_(0x8b, 0x22, 0x52) }, - { "WebGray", RGB_(0x80, 0x80, 0x80) }, - { "WebGreen", RGB_(0x0, 0x80, 0x0) }, - { "WebGrey", RGB_(0x80, 0x80, 0x80) }, - { "WebMaroon", RGB_(0x80, 0x0, 0x0) }, - { "WebPurple", RGB_(0x80, 0x0, 0x80) }, - { "Wheat", RGB_(0xf5, 0xde, 0xb3) }, - { "Wheat1", RGB_(0xff, 0xe7, 0xba) }, - { "Wheat2", RGB_(0xee, 0xd8, 0xae) }, - { "Wheat3", RGB_(0xcd, 0xba, 0x96) }, - { "Wheat4", RGB_(0x8b, 0x7e, 0x66) }, - { "White", RGB_(0xff, 0xff, 0xff) }, - { "WhiteSmoke", RGB_(0xf5, 0xf5, 0xf5) }, - { "X11Gray", RGB_(0xbe, 0xbe, 0xbe) }, - { "X11Green", RGB_(0x0, 0xff, 0x0) }, - { "X11Grey", RGB_(0xbe, 0xbe, 0xbe) }, - { "X11Maroon", RGB_(0xb0, 0x30, 0x60) }, - { "X11Purple", RGB_(0xa0, 0x20, 0xf0) }, - { "Yellow", RGB_(0xff, 0xff, 0x00) }, - { "Yellow1", RGB_(0xff, 0xff, 0x0) }, - { "Yellow2", RGB_(0xee, 0xee, 0x0) }, - { "Yellow3", RGB_(0xcd, 0xcd, 0x0) }, - { "Yellow4", RGB_(0x8b, 0x8b, 0x0) }, - { "YellowGreen", RGB_(0x9a, 0xcd, 0x32) }, - { NULL, 0 }, -}; - - -/// Translate to RgbValue if \p name is an hex value (e.g. #XXXXXX), -/// else look into color_name_table to translate a color name to its -/// hex value -/// -/// @param[in] name string value to convert to RGB -/// return the hex value or -1 if could not find a correct value -RgbValue name_to_color(const char *name) -{ - if (name[0] == '#' && isxdigit(name[1]) && isxdigit(name[2]) - && isxdigit(name[3]) && isxdigit(name[4]) && isxdigit(name[5]) - && isxdigit(name[6]) && name[7] == NUL) { - // rgb hex string - return strtol((char *)(name + 1), NULL, 16); - } else if (!STRICMP(name, "bg") || !STRICMP(name, "background")) { - return normal_bg; - } else if (!STRICMP(name, "fg") || !STRICMP(name, "foreground")) { - return normal_fg; - } - - for (int i = 0; color_name_table[i].name != NULL; i++) { - if (!STRICMP(name, color_name_table[i].name)) { - return color_name_table[i].color; - } - } - - return -1; -} - - -/************************************** -* End of Highlighting stuff * -**************************************/ diff --git a/src/nvim/syntax.h b/src/nvim/syntax.h index 15fc084a0a..0d890314c5 100644 --- a/src/nvim/syntax.h +++ b/src/nvim/syntax.h @@ -29,12 +29,6 @@ #define SYN_GROUP_STATIC(s) syn_check_group(S_LEN(s)) -typedef struct { - char *name; - RgbValue color; -} color_name_table_T; -extern color_name_table_T color_name_table[]; - /// Array of highlight definitions, used for unit testing extern const char *const highlight_init_cmdline[]; diff --git a/src/nvim/syntax_defs.h b/src/nvim/syntax_defs.h index 526be905e9..c656f21181 100644 --- a/src/nvim/syntax_defs.h +++ b/src/nvim/syntax_defs.h @@ -7,7 +7,7 @@ #define SST_MAX_ENTRIES 1000 // maximal size for state stack array #define SST_FIX_STATES 7 // size of sst_stack[]. #define SST_DIST 16 // normal distance between entries -#define SST_INVALID (synstate_T *)-1 // invalid syn_state pointer +#define SST_INVALID ((synstate_T *)-1) // invalid syn_state pointer typedef struct syn_state synstate_T; @@ -21,9 +21,7 @@ struct sp_syn { int16_t *cont_in_list; // cont.in group IDs, if non-zero }; -/* - * Each keyword has one keyentry, which is linked in a hash list. - */ +// Each keyword has one keyentry, which is linked in a hash list. typedef struct keyentry keyentry_T; struct keyentry { @@ -35,9 +33,7 @@ struct keyentry { char_u keyword[1]; // actually longer }; -/* - * Struct used to store one state of the state stack. - */ +// Struct used to store one state of the state stack. typedef struct buf_state { int bs_idx; // index of pattern int bs_flags; // flags for pattern @@ -46,10 +42,8 @@ typedef struct buf_state { reg_extmatch_T *bs_extmatch; // external matches from start pattern } bufstate_T; -/* - * syn_state contains the syntax state stack for the start of one line. - * Used by b_sst_array[]. - */ +// syn_state contains the syntax state stack for the start of one line. +// Used by b_sst_array[]. struct syn_state { synstate_T *sst_next; // next entry in used or free list linenr_T sst_lnum; // line number for this state @@ -66,4 +60,4 @@ struct syn_state { // may have made the state invalid }; -#endif // NVIM_SYNTAX_DEFS_H +#endif // NVIM_SYNTAX_DEFS_H diff --git a/src/nvim/tag.c b/src/nvim/tag.c index 1f4d3adc92..32d72218c8 100644 --- a/src/nvim/tag.c +++ b/src/nvim/tag.c @@ -25,11 +25,11 @@ #include "nvim/fold.h" #include "nvim/garray.h" #include "nvim/if_cscope.h" +#include "nvim/input.h" #include "nvim/mark.h" #include "nvim/mbyte.h" #include "nvim/memory.h" #include "nvim/message.h" -#include "nvim/misc1.h" #include "nvim/move.h" #include "nvim/option.h" #include "nvim/os/input.h" @@ -141,12 +141,12 @@ static int tfu_in_use = false; // disallow recursive call of tagfunc /// type == DT_LTAG: use location list for displaying tag matches /// type == DT_FREE: free cached matches /// -/// for cscope, returns TRUE if we jumped to tag or aborted, FALSE otherwise +/// for cscope, returns true if we jumped to tag or aborted, false otherwise /// /// @param tag tag (pattern) to jump to /// @param forceit :ta with ! /// @param verbose print "tag not found" message -int do_tag(char_u *tag, int type, int count, int forceit, int verbose) +bool do_tag(char_u *tag, int type, int count, int forceit, int verbose) { taggy_T *tagstack = curwin->w_tagstack; int tagstackidx = curwin->w_tagstackidx; @@ -163,7 +163,7 @@ int do_tag(char_u *tag, int type, int count, int forceit, int verbose) int error_cur_match = 0; int save_pos = false; fmark_T saved_fmark; - int jumped_to_tag = false; + bool jumped_to_tag = false; int new_num_matches; char_u **new_matches; int use_tagstack; @@ -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/tag.h b/src/nvim/tag.h index 64bacceb1b..902fe0c7ba 100644 --- a/src/nvim/tag.h +++ b/src/nvim/tag.h @@ -4,9 +4,7 @@ #include "nvim/ex_cmds_defs.h" #include "nvim/types.h" -/* - * Values for do_tag(). - */ +// Values for do_tag(). #define DT_TAG 1 // jump to newer position or same tag again #define DT_POP 2 // jump to older position #define DT_NEXT 3 // jump to next match of same tag @@ -20,9 +18,7 @@ #define DT_LTAG 11 // tag using location list #define DT_FREE 99 // free cached matches -// // flags for find_tags(). -// #define TAG_HELP 1 // only search for help tags #define TAG_NAMES 2 // only return name of tag #define TAG_REGEXP 4 // use tag pattern as regexp @@ -36,9 +32,7 @@ #define TAG_MANY 300 // When finding many tags (for completion), // find up to this many tags -/* - * Structure used for get_tagfname(). - */ +// Structure used for get_tagfname(). typedef struct { char_u *tn_tags; // value of 'tags' when starting char_u *tn_np; // current position in tn_tags diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c index 35c68fa1f6..fd870361c7 100644 --- a/src/nvim/terminal.c +++ b/src/nvim/terminal.c @@ -46,6 +46,7 @@ #include "nvim/ascii.h" #include "nvim/buffer.h" #include "nvim/change.h" +#include "nvim/cursor.h" #include "nvim/edit.h" #include "nvim/event/loop.h" #include "nvim/event/time.h" @@ -54,6 +55,7 @@ #include "nvim/fileio.h" #include "nvim/getchar.h" #include "nvim/highlight.h" +#include "nvim/highlight_group.h" #include "nvim/keymap.h" #include "nvim/log.h" #include "nvim/macros.h" @@ -63,14 +65,12 @@ #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/message.h" -#include "nvim/misc1.h" #include "nvim/mouse.h" #include "nvim/move.h" #include "nvim/option.h" #include "nvim/os/input.h" #include "nvim/screen.h" #include "nvim/state.h" -#include "nvim/syntax.h" #include "nvim/terminal.h" #include "nvim/ui.h" #include "nvim/vim.h" @@ -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; @@ -312,10 +317,14 @@ void terminal_close(Terminal *term, int status) term->opts.close_cb(term->opts.data); } } else if (!only_destroy) { - // This was called by channel_process_exit_cb() not in process_teardown(). + // Associated channel has been closed and the editor is not exiting. // Do not call the close callback now. Wait for the user to press a key. char msg[sizeof("\r\n[Process exited ]") + NUMBUFLEN]; - snprintf(msg, sizeof msg, "\r\n[Process exited %d]", status); + if (((Channel *)term->opts.data)->streamtype == kChannelStreamInternal) { + snprintf(msg, sizeof msg, "\r\n[Terminal closed]"); + } else { + snprintf(msg, sizeof msg, "\r\n[Process exited %d]", status); + } terminal_receive(term, msg, strlen(msg)); } @@ -324,10 +333,12 @@ void terminal_close(Terminal *term, int status) } if (buf && !is_autocmd_blocked()) { - dict_T *dict = get_vim_var_dict(VV_EVENT); + save_v_event_T save_v_event; + dict_T *dict = get_v_event(&save_v_event); tv_dict_add_nr(dict, S_LEN("status"), status); + tv_dict_set_keys_readonly(dict); apply_autocmds(EVENT_TERMCLOSE, NULL, NULL, false, buf); - tv_dict_clear(dict); + restore_v_event(dict, &save_v_event); } } @@ -412,6 +423,7 @@ void terminal_enter(void) curwin->w_redr_status = true; // For mode() in statusline. #8323 ui_busy_start(); apply_autocmds(EVENT_TERMENTER, NULL, NULL, false, curbuf); + may_trigger_modechanged(); s->state.execute = terminal_execute; s->state.check = terminal_check; @@ -462,9 +474,7 @@ static void terminal_check_cursor(void) row_to_linenr(term, term->cursor.row)); // Nudge cursor when returning to normal-mode. int off = is_focused(term) ? 0 : (curwin->w_p_rl ? 1 : -1); - curwin->w_cursor.col = MAX(0, term->cursor.col + win_col_off(curwin) + off); - curwin->w_cursor.coladd = 0; - mb_check_adjust_col(curwin); + coladvance(MAX(0, term->cursor.col + off)); } // Function executed before each iteration of terminal mode. @@ -529,6 +539,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 +647,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 +677,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 +695,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) @@ -1203,27 +1206,70 @@ static VTermKey convert_key(int key, VTermModifier *statep) return VTERM_KEY_FUNCTION(36); case K_F37: return VTERM_KEY_FUNCTION(37); + case K_F38: + return VTERM_KEY_FUNCTION(38); + case K_F39: + return VTERM_KEY_FUNCTION(39); + case K_F40: + return VTERM_KEY_FUNCTION(40); + case K_F41: + return VTERM_KEY_FUNCTION(41); + case K_F42: + return VTERM_KEY_FUNCTION(42); + case K_F43: + return VTERM_KEY_FUNCTION(43); + case K_F44: + return VTERM_KEY_FUNCTION(44); + case K_F45: + return VTERM_KEY_FUNCTION(45); + case K_F46: + return VTERM_KEY_FUNCTION(46); + case K_F47: + return VTERM_KEY_FUNCTION(47); + case K_F48: + return VTERM_KEY_FUNCTION(48); + case K_F49: + return VTERM_KEY_FUNCTION(49); + case K_F50: + return VTERM_KEY_FUNCTION(50); + case K_F51: + return VTERM_KEY_FUNCTION(51); + case K_F52: + return VTERM_KEY_FUNCTION(52); + case K_F53: + return VTERM_KEY_FUNCTION(53); + case K_F54: + return VTERM_KEY_FUNCTION(54); + case K_F55: + return VTERM_KEY_FUNCTION(55); + case K_F56: + return VTERM_KEY_FUNCTION(56); + case K_F57: + return VTERM_KEY_FUNCTION(57); + case K_F58: + return VTERM_KEY_FUNCTION(58); + case K_F59: + return VTERM_KEY_FUNCTION(59); + case K_F60: + return VTERM_KEY_FUNCTION(60); + case K_F61: + return VTERM_KEY_FUNCTION(61); + case K_F62: + return VTERM_KEY_FUNCTION(62); + case K_F63: + return VTERM_KEY_FUNCTION(63); default: return VTERM_KEY_NONE; } } -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 +1288,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; } @@ -1295,8 +1341,14 @@ static bool send_mouse_event(Terminal *term, int c) return mouse_win == curwin; } + // ignore left release action if it was not processed above + // to prevent leaving Terminal mode after entering to it using a mouse + if (c == K_LEFTRELEASE && mouse_win->w_buffer->terminal == term) { + return false; + } + end: - ins_char_typebuf(c); + ins_char_typebuf(c, mod_mask); return true; } @@ -1466,6 +1518,17 @@ static void refresh_scrollback(Terminal *term, buf_T *buf) int width, height; vterm_get_size(term->vt, &height, &width); + // May still have pending scrollback after increase in terminal height if the + // scrollback wasn't refreshed in time; append these to the top of the buffer. + int row_offset = term->sb_pending; + while (term->sb_pending > 0 && buf->b_ml.ml_line_count < height) { + fetch_row(term, term->sb_pending - row_offset - 1, width); + ml_append(0, (uint8_t *)term->textbuf, 0, false); + appended_lines(0, 1); + term->sb_pending--; + } + + row_offset -= term->sb_pending; while (term->sb_pending > 0) { // This means that either the window height has decreased or the screen // became full and libvterm had to push all rows up. Convert the first @@ -1476,7 +1539,7 @@ static void refresh_scrollback(Terminal *term, buf_T *buf) ml_delete(1, false); deleted_lines(1, 1); } - fetch_row(term, -term->sb_pending, width); + fetch_row(term, -term->sb_pending - row_offset, width); int buf_index = (int)buf->b_ml.ml_line_count - height; ml_append(buf_index, (uint8_t *)term->textbuf, 0, false); appended_lines(buf_index, 1); @@ -1506,6 +1569,13 @@ static void refresh_screen(Terminal *term, buf_T *buf) // Terminal height may have decreased before `invalid_end` reflects it. term->invalid_end = MIN(term->invalid_end, height); + // There are no invalid rows. + if (term->invalid_start >= term->invalid_end) { + term->invalid_start = INT_MAX; + term->invalid_end = -1; + return; + } + for (int r = term->invalid_start, linenr = row_to_linenr(term, r); r < term->invalid_end; r++, linenr++) { fetch_row(term, r, width); diff --git a/src/nvim/testdir/check.vim b/src/nvim/testdir/check.vim index 14bab33a2f..8f97d959ce 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() @@ -120,6 +137,14 @@ func CheckNotMSWindows() endif endfunc +" Command to check for not running under ASAN +command CheckNotAsan call CheckNotAsan() +func CheckNotAsan() + if execute('version') =~# '-fsanitize=[a-z,]*\<address\>' + throw 'Skipped: does not work with ASAN' + endif +endfunc + " Command to check for satisfying any of the conditions. " e.g. CheckAnyOf Feature:bsd Feature:sun Linux command -nargs=+ CheckAnyOf call CheckAnyOf(<f-args>) diff --git a/src/nvim/testdir/runnvim.sh b/src/nvim/testdir/runnvim.sh index 25cb8437b4..322265737a 100755 --- a/src/nvim/testdir/runnvim.sh +++ b/src/nvim/testdir/runnvim.sh @@ -38,7 +38,7 @@ main() {( -S runnvim.vim \ "$tlog" > "out-$tlog" 2> "err-$tlog" then - fail "$test_name" F "Nvim exited with non-zero code" + fail "$test_name" "Nvim exited with non-zero code" fi { echo "Stdout of :terminal runner" @@ -53,7 +53,7 @@ main() {( if test "$oldesttest" = 1 ; then if ! diff -q test.out "$test_name.ok" > /dev/null 2>&1 ; then if test -f test.out ; then - fail "$test_name" F "Oldest test .out file differs from .ok file" + fail "$test_name" "Oldest test .out file differs from .ok file" { echo "Diff between test.out and $test_name.ok" echo "$separator" @@ -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/runtest.vim b/src/nvim/testdir/runtest.vim index 49993c03aa..b0d872e392 100644 --- a/src/nvim/testdir/runtest.vim +++ b/src/nvim/testdir/runtest.vim @@ -64,6 +64,9 @@ if has('reltime') let s:start_time = reltime() endif +" Always use forward slashes. +set shellslash + " Common with all tests on all systems. source setup.vim @@ -104,9 +107,6 @@ lang mess C " Nvim: append runtime from build dir, which contains the generated doc/tags. let &runtimepath .= ','.expand($BUILD_DIR).'/runtime/' -" Always use forward slashes. -set shellslash - let s:t_bold = &t_md let s:t_normal = &t_me if has('win32') @@ -197,7 +197,12 @@ func RunTheTest(test) " Close any extra tab pages and windows and make the current one not modified. while tabpagenr('$') > 1 + let winid = win_getid() quit! + if winid == win_getid() + echoerr 'Could not quit window' + break + endif endwhile while 1 diff --git a/src/nvim/testdir/setup.vim b/src/nvim/testdir/setup.vim index b3df8c63e6..15e3b31498 100644 --- a/src/nvim/testdir/setup.vim +++ b/src/nvim/testdir/setup.vim @@ -10,10 +10,11 @@ let s:did_load = 1 set backspace= set directory^=. set fillchars=vert:\|,fold:- +set fsync set laststatus=1 set listchars=eol:$ set joinspaces -set nohidden smarttab noautoindent noautoread complete-=i noruler noshowcmd +set nohidden nosmarttab noautoindent noautoread complete-=i noruler noshowcmd set nrformats+=octal set shortmess-=F set sidescroll=0 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..43a519bc84 100644 --- a/src/nvim/testdir/test_alot.vim +++ b/src/nvim/testdir/test_alot.vim @@ -3,54 +3,32 @@ source test_backup.vim source test_behave.vim -source test_cd.vim -source test_changedtick.vim source test_compiler.vim -source test_cursor_func.vim -source test_cursorline.vim source test_ex_equal.vim source test_ex_undo.vim source test_ex_z.vim source test_ex_mode.vim -source test_execute_func.vim +source test_expand.vim source test_expand_func.vim source test_feedkeys.vim -source test_filter_cmd.vim -source test_filter_map.vim -source test_findfile.vim -source test_float_func.vim -source test_functions.vim +source test_file_perm.vim +source test_fnamemodify.vim source test_ga.vim +source test_glob2regpat.vim source test_global.vim -source test_goto.vim -source test_join.vim -source test_jumps.vim -source test_fileformat.vim -source test_filetype.vim -source test_lambda.vim +source test_lispwords.vim source test_menu.vim -source test_messages.vim -source test_modeline.vim source test_move.vim -source test_partial.vim -source test_popup.vim source test_put.vim -source test_rename.vim +source test_reltime.vim source test_scroll_opt.vim +source test_searchpos.vim +source test_set.vim source test_shift.vim -source test_sort.vim source test_sha256.vim -source test_suspend.vim -source test_syn_attr.vim source test_tabline.vim -source test_tabpage.vim source test_tagcase.vim source test_tagfunc.vim -source test_tagjump.vim -source test_taglist.vim -source test_true_false.vim source test_unlet.vim source test_version.vim -source test_virtualedit.vim -source test_window_cmd.vim source test_wnext.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 0b76828dd7..4229095f9f 100644 --- a/src/nvim/testdir/test_autochdir.vim +++ b/src/nvim/testdir/test_autochdir.vim @@ -26,4 +26,107 @@ func Test_set_filename() call delete('samples/Xtest') endfunc +func Test_set_filename_other_window() + CheckFunction test_autochdir + let cwd = getcwd() + call test_autochdir() + call mkdir('Xa') + call mkdir('Xb') + call mkdir('Xc') + try + args Xa/aaa.txt Xb/bbb.txt + set acd + let winid = win_getid() + snext + call assert_equal('Xb', substitute(getcwd(), '.*/\([^/]*\)$', '\1', '')) + call win_execute(winid, 'file ' .. cwd .. '/Xc/ccc.txt') + call assert_equal('Xb', substitute(getcwd(), '.*/\([^/]*\)$', '\1', '')) + finally + set noacd + call chdir(cwd) + call delete('Xa', 'rf') + call delete('Xb', 'rf') + call delete('Xc', 'rf') + bwipe! aaa.txt + bwipe! bbb.txt + bwipe! ccc.txt + endtry +endfunc + +func Test_acd_win_execute() + CheckFunction test_autochdir + let cwd = getcwd() + set acd + call test_autochdir() + + call mkdir('Xfile') + let winid = win_getid() + new Xfile/file + call assert_match('testdir.Xfile$', getcwd()) + cd .. + call assert_match('testdir$', getcwd()) + call win_execute(winid, 'echo') + call assert_match('testdir$', getcwd()) + + bwipe! + set noacd + call chdir(cwd) + call delete('Xfile', 'rf') +endfunc + +func Test_verbose_pwd() + CheckFunction test_autochdir + let cwd = getcwd() + call test_autochdir() + + edit global.txt + call assert_match('\[global\].*testdir$', execute('verbose pwd')) + + call mkdir('Xautodir') + split Xautodir/local.txt + lcd Xautodir + call assert_match('\[window\].*testdir[/\\]Xautodir', execute('verbose pwd')) + + set acd + wincmd w + call assert_match('\[autochdir\].*testdir$', execute('verbose pwd')) + execute 'tcd' cwd + call assert_match('\[tabpage\].*testdir$', execute('verbose pwd')) + execute 'cd' cwd + call assert_match('\[global\].*testdir$', execute('verbose pwd')) + execute 'lcd' cwd + call assert_match('\[window\].*testdir$', execute('verbose pwd')) + edit + call assert_match('\[autochdir\].*testdir$', execute('verbose pwd')) + enew + wincmd w + call assert_match('\[autochdir\].*testdir[/\\]Xautodir', execute('verbose pwd')) + wincmd w + call assert_match('\[window\].*testdir$', execute('verbose pwd')) + wincmd w + call assert_match('\[autochdir\].*testdir[/\\]Xautodir', execute('verbose pwd')) + set noacd + call assert_match('\[autochdir\].*testdir[/\\]Xautodir', execute('verbose pwd')) + wincmd w + call assert_match('\[window\].*testdir$', execute('verbose pwd')) + execute 'cd' cwd + call assert_match('\[global\].*testdir$', execute('verbose pwd')) + wincmd w + call assert_match('\[window\].*testdir[/\\]Xautodir', execute('verbose pwd')) + + bwipe! + call chdir(cwd) + 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 0c8b8a45d9..228145ec4d 100644 --- a/src/nvim/testdir/test_autocmd.vim +++ b/src/nvim/testdir/test_autocmd.vim @@ -3,8 +3,9 @@ source shared.vim source check.vim source term_util.vim +source screendump.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 @@ -33,7 +34,7 @@ if has('timers') let g:triggered = 0 au CursorHoldI * let g:triggered += 1 set updatetime=20 - call timer_start(LoadAdjust(100), 'ExitInsertMode') + call timer_start(LoadAdjust(200), 'ExitInsertMode') call feedkeys('a', 'x!') call assert_equal(1, g:triggered) unlet g:triggered @@ -260,6 +261,84 @@ func Test_win_tab_autocmd() unlet g:record endfunc +func Test_WinScrolled() + CheckRunVimInTerminal + + let lines =<< trim END + set nowrap scrolloff=0 + for ii in range(1, 18) + call setline(ii, repeat(nr2char(96 + ii), ii * 2)) + endfor + let win_id = win_getid() + let g:matched = v:false + execute 'au WinScrolled' win_id 'let g:matched = v:true' + let g:scrolled = 0 + au WinScrolled * let g:scrolled += 1 + au WinScrolled * let g:amatch = str2nr(expand('<amatch>')) + au WinScrolled * let g:afile = str2nr(expand('<afile>')) + END + call writefile(lines, 'Xtest_winscrolled') + let buf = RunVimInTerminal('-S Xtest_winscrolled', {'rows': 6}) + + call term_sendkeys(buf, ":echo g:scrolled\<CR>") + call WaitForAssert({-> assert_match('^0 ', term_getline(buf, 6))}, 1000) + + " Scroll left/right in Normal mode. + call term_sendkeys(buf, "zlzh:echo g:scrolled\<CR>") + call WaitForAssert({-> assert_match('^2 ', term_getline(buf, 6))}, 1000) + + " Scroll up/down in Normal mode. + call term_sendkeys(buf, "\<c-e>\<c-y>:echo g:scrolled\<CR>") + call WaitForAssert({-> assert_match('^4 ', term_getline(buf, 6))}, 1000) + + " Scroll up/down in Insert mode. + call term_sendkeys(buf, "Mi\<c-x>\<c-e>\<Esc>i\<c-x>\<c-y>\<Esc>") + call term_sendkeys(buf, ":echo g:scrolled\<CR>") + call WaitForAssert({-> assert_match('^6 ', term_getline(buf, 6))}, 1000) + + " Scroll the window horizontally to focus the last letter of the third line + " containing only six characters. Moving to the previous and shorter lines + " should trigger another autocommand as Vim has to make them visible. + call term_sendkeys(buf, "5zl2k") + call term_sendkeys(buf, ":echo g:scrolled\<CR>") + call WaitForAssert({-> assert_match('^8 ', term_getline(buf, 6))}, 1000) + + " Ensure the command was triggered for the specified window ID. + call term_sendkeys(buf, ":echo g:matched\<CR>") + call WaitForAssert({-> assert_match('^v:true ', term_getline(buf, 6))}, 1000) + + " Ensure the expansion of <amatch> and <afile> matches the window ID. + call term_sendkeys(buf, ":echo g:amatch == win_id && g:afile == win_id\<CR>") + call WaitForAssert({-> assert_match('^v:true ', term_getline(buf, 6))}, 1000) + + call StopVimInTerminal(buf) + call delete('Xtest_winscrolled') +endfunc + +func Test_WinScrolled_close_curwin() + CheckRunVimInTerminal + + let lines =<< trim END + set nowrap scrolloff=0 + call setline(1, ['aaa', 'bbb']) + vsplit + au WinScrolled * close + au VimLeave * call writefile(['123456'], 'Xtestout') + END + call writefile(lines, 'Xtest_winscrolled_close_curwin') + let buf = RunVimInTerminal('-S Xtest_winscrolled_close_curwin', {'rows': 6}) + + " This was using freed memory + call term_sendkeys(buf, "\<C-E>") + call TermWait(buf) + call StopVimInTerminal(buf) + + call assert_equal(['123456'], readfile('Xtestout')) + + call delete('Xtest_winscrolled_close_curwin') + call delete('Xtestout') +endfunc + func Test_WinClosed() " Test that the pattern is matched against the closed window's ID, and both " <amatch> and <afile> are set to it. @@ -299,6 +378,40 @@ func Test_WinClosed() unlet g:triggered endfunc +func Test_WinClosed_throws() + vnew + let bnr = bufnr() + call assert_equal(1, bufloaded(bnr)) + augroup test-WinClosed + autocmd WinClosed * throw 'foo' + augroup END + try + close + catch /.*/ + endtry + call assert_equal(0, bufloaded(bnr)) + + autocmd! test-WinClosed + augroup! test-WinClosed +endfunc + +func Test_WinClosed_throws_with_tabs() + tabnew + let bnr = bufnr() + call assert_equal(1, bufloaded(bnr)) + augroup test-WinClosed + autocmd WinClosed * throw 'foo' + augroup END + try + close + catch /.*/ + endtry + call assert_equal(0, bufloaded(bnr)) + + autocmd! test-WinClosed + augroup! test-WinClosed +endfunc + func s:AddAnAutocmd() augroup vimBarTest au BufReadCmd * echo 'hello' @@ -502,7 +615,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) @@ -526,8 +639,7 @@ func Test_autocmd_blast_badd() call writefile(content, 'XblastBall') call system(GetVimCommand() .. ' --clean -S XblastBall') - " call assert_match('OK', readfile('Xerrors')->join()) - call assert_match('OK', join(readfile('Xerrors'))) + call assert_match('OK', readfile('Xerrors')->join()) call delete('XblastBall') call delete('Xerrors') @@ -563,7 +675,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) @@ -580,9 +692,10 @@ func Test_empty_doau() endfunc func s:AutoCommandOptionSet(match) + let template = "Option: <%s>, OldVal: <%s>, OldValLocal: <%s>, OldValGlobal: <%s>, NewVal: <%s>, Scope: <%s>, Command: <%s>\n" let item = remove(g:options, 0) - let expected = printf("Option: <%s>, Oldval: <%s>, NewVal: <%s>, Scope: <%s>\n", item[0], item[1], item[2], item[3]) - let actual = printf("Option: <%s>, Oldval: <%s>, NewVal: <%s>, Scope: <%s>\n", a:match, v:option_old, v:option_new, v:option_type) + let expected = printf(template, item[0], item[1], item[2], item[3], item[4], item[5], item[6]) + let actual = printf(template, a:match, v:option_old, v:option_oldlocal, v:option_oldglobal, v:option_new, v:option_type, v:option_command) let g:opt = [expected, actual] "call assert_equal(expected, actual) endfunc @@ -596,130 +709,593 @@ func Test_OptionSet() au OptionSet * :call s:AutoCommandOptionSet(expand("<amatch>")) " 1: Setting number option" - let g:options = [['number', 0, 1, 'global']] + let g:options = [['number', 0, 0, 0, 1, 'global', 'set']] set nu call assert_equal([], g:options) call assert_equal(g:opt[0], g:opt[1]) " 2: Setting local number option" - let g:options = [['number', 1, 0, 'local']] + let g:options = [['number', 1, 1, '', 0, 'local', 'setlocal']] setlocal nonu call assert_equal([], g:options) call assert_equal(g:opt[0], g:opt[1]) " 3: Setting global number option" - let g:options = [['number', 1, 0, 'global']] + let g:options = [['number', 1, '', 1, 0, 'global', 'setglobal']] setglobal nonu call assert_equal([], g:options) call assert_equal(g:opt[0], g:opt[1]) " 4: Setting local autoindent option" - let g:options = [['autoindent', 0, 1, 'local']] + let g:options = [['autoindent', 0, 0, '', 1, 'local', 'setlocal']] setlocal ai call assert_equal([], g:options) call assert_equal(g:opt[0], g:opt[1]) " 5: Setting global autoindent option" - let g:options = [['autoindent', 0, 1, 'global']] + let g:options = [['autoindent', 0, '', 0, 1, 'global', 'setglobal']] setglobal ai call assert_equal([], g:options) call assert_equal(g:opt[0], g:opt[1]) " 6: Setting global autoindent option" - let g:options = [['autoindent', 1, 0, 'global']] + let g:options = [['autoindent', 1, 1, 1, 0, 'global', 'set']] + set ai! + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " 6a: Setting global autoindent option" + let g:options = [['autoindent', 1, 1, 0, 0, 'global', 'set']] + noa setlocal ai + noa setglobal noai set ai! call assert_equal([], g:options) call assert_equal(g:opt[0], g:opt[1]) " Should not print anything, use :noa " 7: don't trigger OptionSet" - let g:options = [['invalid', 1, 1, 'invalid']] + let g:options = [['invalid', 'invalid', 'invalid', 'invalid', 'invalid', 'invalid', 'invalid']] noa set nonu - call assert_equal([['invalid', 1, 1, 'invalid']], g:options) + call assert_equal([['invalid', 'invalid', 'invalid', 'invalid', 'invalid', 'invalid', 'invalid']], g:options) call assert_equal(g:opt[0], g:opt[1]) " 8: Setting several global list and number option" - let g:options = [['list', 0, 1, 'global'], ['number', 0, 1, 'global']] + let g:options = [['list', 0, 0, 0, 1, 'global', 'set'], ['number', 0, 0, 0, 1, 'global', 'set']] set list nu call assert_equal([], g:options) call assert_equal(g:opt[0], g:opt[1]) " 9: don't trigger OptionSet" - let g:options = [['invalid', 1, 1, 'invalid'], ['invalid', 1, 1, 'invalid']] + let g:options = [['invalid', 'invalid', 'invalid', 'invalid', 'invalid', 'invalid', 'invalid'], ['invalid', 'invalid', 'invalid', 'invalid', 'invalid', 'invalid', 'invalid']] noa set nolist nonu - call assert_equal([['invalid', 1, 1, 'invalid'], ['invalid', 1, 1, 'invalid']], g:options) + call assert_equal([['invalid', 'invalid', 'invalid', 'invalid', 'invalid', 'invalid', 'invalid'], ['invalid', 'invalid', 'invalid', 'invalid', 'invalid', 'invalid', 'invalid']], g:options) call assert_equal(g:opt[0], g:opt[1]) " 10: Setting global acd" - let g:options = [['autochdir', 0, 1, 'local']] + let g:options = [['autochdir', 0, 0, '', 1, 'local', 'setlocal']] setlocal acd call assert_equal([], g:options) call assert_equal(g:opt[0], g:opt[1]) " 11: Setting global autoread (also sets local value)" - let g:options = [['autoread', 0, 1, 'global']] + let g:options = [['autoread', 0, 0, 0, 1, 'global', 'set']] set ar call assert_equal([], g:options) call assert_equal(g:opt[0], g:opt[1]) " 12: Setting local autoread" - let g:options = [['autoread', 1, 1, 'local']] + let g:options = [['autoread', 1, 1, '', 1, 'local', 'setlocal']] setlocal ar call assert_equal([], g:options) call assert_equal(g:opt[0], g:opt[1]) " 13: Setting global autoread" - let g:options = [['autoread', 1, 0, 'global']] + let g:options = [['autoread', 1, '', 1, 0, 'global', 'setglobal']] setglobal invar call assert_equal([], g:options) call assert_equal(g:opt[0], g:opt[1]) " 14: Setting option backspace through :let" - let g:options = [['backspace', '', 'eol,indent,start', 'global']] + let g:options = [['backspace', '', '', '', 'eol,indent,start', 'global', 'set']] let &bs = "eol,indent,start" call assert_equal([], g:options) call assert_equal(g:opt[0], g:opt[1]) " 15: Setting option backspace through setbufvar()" - let g:options = [['backup', 0, 1, 'local']] + let g:options = [['backup', 0, 0, '', 1, 'local', 'setlocal']] " try twice, first time, shouldn't trigger because option name is invalid, " second time, it should trigger - call assert_fails("call setbufvar(1, '&l:bk', 1)", "E355") + let bnum = bufnr('%') + call assert_fails("call setbufvar(bnum, '&l:bk', 1)", 'E355:') " should trigger, use correct option name - call setbufvar(1, '&backup', 1) + call setbufvar(bnum, '&backup', 1) call assert_equal([], g:options) call assert_equal(g:opt[0], g:opt[1]) " 16: Setting number option using setwinvar" - let g:options = [['number', 0, 1, 'local']] + let g:options = [['number', 0, 0, '', 1, 'local', 'setlocal']] call setwinvar(0, '&number', 1) call assert_equal([], g:options) call assert_equal(g:opt[0], g:opt[1]) " 17: Setting key option, shouldn't trigger" - let g:options = [['key', 'invalid', 'invalid1', 'invalid']] + let g:options = [['key', 'invalid', 'invalid1', 'invalid2', 'invalid3', 'invalid4', 'invalid5']] setlocal key=blah setlocal key= - call assert_equal([['key', 'invalid', 'invalid1', 'invalid']], g:options) + call assert_equal([['key', 'invalid', 'invalid1', 'invalid2', 'invalid3', 'invalid4', 'invalid5']], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + + " 18a: Setting string global option" + let oldval = &backupext + let g:options = [['backupext', oldval, oldval, oldval, 'foo', 'global', 'set']] + set backupext=foo + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " 18b: Resetting string global option" + let g:options = [['backupext', 'foo', 'foo', 'foo', oldval, 'global', 'set']] + set backupext& + call assert_equal([], g:options) call assert_equal(g:opt[0], g:opt[1]) - " 18: Setting string option" + " 18c: Setting global string global option" + let g:options = [['backupext', oldval, '', oldval, 'bar', 'global', 'setglobal']] + setglobal backupext=bar + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " 18d: Setting local string global option" + " As this is a global option this sets the global value even though + " :setlocal is used! + noa set backupext& " Reset global and local value (without triggering autocmd) + let g:options = [['backupext', oldval, oldval, '', 'baz', 'local', 'setlocal']] + setlocal backupext=baz + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " 18e: Setting again string global option" + noa setglobal backupext=ext_global " Reset global and local value (without triggering autocmd) + noa setlocal backupext=ext_local " Sets the global(!) value! + let g:options = [['backupext', 'ext_local', 'ext_local', 'ext_local', 'fuu', 'global', 'set']] + set backupext=fuu + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + + " 19a: Setting string global-local (to buffer) option" let oldval = &tags - let g:options = [['tags', oldval, 'tagpath', 'global']] + let g:options = [['tags', oldval, oldval, oldval, 'tagpath', 'global', 'set']] set tags=tagpath call assert_equal([], g:options) call assert_equal(g:opt[0], g:opt[1]) - " 1l: Resetting string option" - let g:options = [['tags', 'tagpath', oldval, 'global']] + " 19b: Resetting string global-local (to buffer) option" + let g:options = [['tags', 'tagpath', 'tagpath', 'tagpath', oldval, 'global', 'set']] set tags& call assert_equal([], g:options) call assert_equal(g:opt[0], g:opt[1]) + " 19c: Setting global string global-local (to buffer) option " + let g:options = [['tags', oldval, '', oldval, 'tagpath1', 'global', 'setglobal']] + setglobal tags=tagpath1 + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " 19d: Setting local string global-local (to buffer) option" + let g:options = [['tags', 'tagpath1', 'tagpath1', '', 'tagpath2', 'local', 'setlocal']] + setlocal tags=tagpath2 + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " 19e: Setting again string global-local (to buffer) option" + " Note: v:option_old is the old global value for global-local string options + " but the old local value for all other kinds of options. + noa setglobal tags=tag_global " Reset global and local value (without triggering autocmd) + noa setlocal tags=tag_local + let g:options = [['tags', 'tag_global', 'tag_local', 'tag_global', 'tagpath', 'global', 'set']] + set tags=tagpath + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " 19f: Setting string global-local (to buffer) option to an empty string" + " Note: v:option_old is the old global value for global-local string options + " but the old local value for all other kinds of options. + noa set tags=tag_global " Reset global and local value (without triggering autocmd) + noa setlocal tags= " empty string + let g:options = [['tags', 'tag_global', '', 'tag_global', 'tagpath', 'global', 'set']] + set tags=tagpath + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + + " 20a: Setting string local (to buffer) option" + let oldval = &spelllang + let g:options = [['spelllang', oldval, oldval, oldval, 'elvish,klingon', 'global', 'set']] + set spelllang=elvish,klingon + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " 20b: Resetting string local (to buffer) option" + let g:options = [['spelllang', 'elvish,klingon', 'elvish,klingon', 'elvish,klingon', oldval, 'global', 'set']] + set spelllang& + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " 20c: Setting global string local (to buffer) option" + let g:options = [['spelllang', oldval, '', oldval, 'elvish', 'global', 'setglobal']] + setglobal spelllang=elvish + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " 20d: Setting local string local (to buffer) option" + noa set spelllang& " Reset global and local value (without triggering autocmd) + let g:options = [['spelllang', oldval, oldval, '', 'klingon', 'local', 'setlocal']] + setlocal spelllang=klingon + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " 20e: Setting again string local (to buffer) option" + " Note: v:option_old is the old global value for global-local string options + " but the old local value for all other kinds of options. + noa setglobal spelllang=spellglobal " Reset global and local value (without triggering autocmd) + noa setlocal spelllang=spelllocal + let g:options = [['spelllang', 'spelllocal', 'spelllocal', 'spellglobal', 'foo', 'global', 'set']] + set spelllang=foo + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + + " 21a: Setting string global-local (to window) option" + let oldval = &statusline + let g:options = [['statusline', oldval, oldval, oldval, 'foo', 'global', 'set']] + set statusline=foo + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " 21b: Resetting string global-local (to window) option" + " Note: v:option_old is the old global value for global-local string options + " but the old local value for all other kinds of options. + let g:options = [['statusline', 'foo', 'foo', 'foo', oldval, 'global', 'set']] + set statusline& + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " 21c: Setting global string global-local (to window) option" + let g:options = [['statusline', oldval, '', oldval, 'bar', 'global', 'setglobal']] + setglobal statusline=bar + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " 21d: Setting local string global-local (to window) option" + noa set statusline& " Reset global and local value (without triggering autocmd) + let g:options = [['statusline', oldval, oldval, '', 'baz', 'local', 'setlocal']] + setlocal statusline=baz + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " 21e: Setting again string global-local (to window) option" + " Note: v:option_old is the old global value for global-local string options + " but the old local value for all other kinds of options. + noa setglobal statusline=bar " Reset global and local value (without triggering autocmd) + noa setlocal statusline=baz + let g:options = [['statusline', 'bar', 'baz', 'bar', 'foo', 'global', 'set']] + set statusline=foo + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + + " 22a: Setting string local (to window) option" + let oldval = &foldignore + let g:options = [['foldignore', oldval, oldval, oldval, 'fo', 'global', 'set']] + set foldignore=fo + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " 22b: Resetting string local (to window) option" + let g:options = [['foldignore', 'fo', 'fo', 'fo', oldval, 'global', 'set']] + set foldignore& + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " 22c: Setting global string local (to window) option" + let g:options = [['foldignore', oldval, '', oldval, 'bar', 'global', 'setglobal']] + setglobal foldignore=bar + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " 22d: Setting local string local (to window) option" + noa set foldignore& " Reset global and local value (without triggering autocmd) + let g:options = [['foldignore', oldval, oldval, '', 'baz', 'local', 'setlocal']] + setlocal foldignore=baz + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " 22e: Setting again string local (to window) option" + noa setglobal foldignore=glob " Reset global and local value (without triggering autocmd) + noa setlocal foldignore=loc + let g:options = [['foldignore', 'loc', 'loc', 'glob', 'fo', 'global', 'set']] + set foldignore=fo + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + + " 23a: Setting global number global option" + noa setglobal cmdheight=8 " Reset global and local value (without triggering autocmd) + noa setlocal cmdheight=1 " Sets the global(!) value! + let g:options = [['cmdheight', '1', '', '1', '2', 'global', 'setglobal']] + setglobal cmdheight=2 + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " 23b: Setting local number global option" + noa setglobal cmdheight=8 " Reset global and local value (without triggering autocmd) + noa setlocal cmdheight=1 " Sets the global(!) value! + let g:options = [['cmdheight', '1', '1', '', '2', 'local', 'setlocal']] + setlocal cmdheight=2 + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " 23c: Setting again number global option" + noa setglobal cmdheight=8 " Reset global and local value (without triggering autocmd) + noa setlocal cmdheight=1 " Sets the global(!) value! + let g:options = [['cmdheight', '1', '1', '1', '2', 'global', 'set']] + set cmdheight=2 + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " 23d: Setting again number global option" + noa set cmdheight=8 " Reset global and local value (without triggering autocmd) + let g:options = [['cmdheight', '8', '8', '8', '2', 'global', 'set']] + set cmdheight=2 + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + + " 24a: Setting global number global-local (to buffer) option" + noa setglobal undolevels=8 " Reset global and local value (without triggering autocmd) + noa setlocal undolevels=1 + let g:options = [['undolevels', '8', '', '8', '2', 'global', 'setglobal']] + setglobal undolevels=2 + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " 24b: Setting local number global-local (to buffer) option" + noa setglobal undolevels=8 " Reset global and local value (without triggering autocmd) + noa setlocal undolevels=1 + let g:options = [['undolevels', '1', '1', '', '2', 'local', 'setlocal']] + setlocal undolevels=2 + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " 24c: Setting again number global-local (to buffer) option" + noa setglobal undolevels=8 " Reset global and local value (without triggering autocmd) + noa setlocal undolevels=1 + let g:options = [['undolevels', '1', '1', '8', '2', 'global', 'set']] + set undolevels=2 + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " 24d: Setting again global number global-local (to buffer) option" + noa set undolevels=8 " Reset global and local value (without triggering autocmd) + let g:options = [['undolevels', '8', '8', '8', '2', 'global', 'set']] + set undolevels=2 + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + + " 25a: Setting global number local (to buffer) option" + noa setglobal wrapmargin=8 " Reset global and local value (without triggering autocmd) + noa setlocal wrapmargin=1 + let g:options = [['wrapmargin', '8', '', '8', '2', 'global', 'setglobal']] + setglobal wrapmargin=2 + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " 25b: Setting local number local (to buffer) option" + noa setglobal wrapmargin=8 " Reset global and local value (without triggering autocmd) + noa setlocal wrapmargin=1 + let g:options = [['wrapmargin', '1', '1', '', '2', 'local', 'setlocal']] + setlocal wrapmargin=2 + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " 25c: Setting again number local (to buffer) option" + noa setglobal wrapmargin=8 " Reset global and local value (without triggering autocmd) + noa setlocal wrapmargin=1 + let g:options = [['wrapmargin', '1', '1', '8', '2', 'global', 'set']] + set wrapmargin=2 + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " 25d: Setting again global number local (to buffer) option" + noa set wrapmargin=8 " Reset global and local value (without triggering autocmd) + let g:options = [['wrapmargin', '8', '8', '8', '2', 'global', 'set']] + set wrapmargin=2 + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + + " 26: Setting number global-local (to window) option. + " Such option does currently not exist. + + + " 27a: Setting global number local (to window) option" + noa setglobal foldcolumn=8 " Reset global and local value (without triggering autocmd) + noa setlocal foldcolumn=1 + let g:options = [['foldcolumn', '8', '', '8', '2', 'global', 'setglobal']] + setglobal foldcolumn=2 + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " 27b: Setting local number local (to window) option" + noa setglobal foldcolumn=8 " Reset global and local value (without triggering autocmd) + noa setlocal foldcolumn=1 + let g:options = [['foldcolumn', '1', '1', '', '2', 'local', 'setlocal']] + setlocal foldcolumn=2 + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " 27c: Setting again number local (to window) option" + noa setglobal foldcolumn=8 " Reset global and local value (without triggering autocmd) + noa setlocal foldcolumn=1 + let g:options = [['foldcolumn', '1', '1', '8', '2', 'global', 'set']] + set foldcolumn=2 + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " 27d: Setting again global number local (to window) option" + noa set foldcolumn=8 " Reset global and local value (without triggering autocmd) + let g:options = [['foldcolumn', '8', '8', '8', '2', 'global', 'set']] + set foldcolumn=2 + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + + " 28a: Setting global boolean global option" + noa setglobal nowrapscan " Reset global and local value (without triggering autocmd) + noa setlocal wrapscan " Sets the global(!) value! + let g:options = [['wrapscan', '1', '', '1', '0', 'global', 'setglobal']] + setglobal nowrapscan + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " 28b: Setting local boolean global option" + noa setglobal nowrapscan " Reset global and local value (without triggering autocmd) + noa setlocal wrapscan " Sets the global(!) value! + let g:options = [['wrapscan', '1', '1', '', '0', 'local', 'setlocal']] + setlocal nowrapscan + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " 28c: Setting again boolean global option" + noa setglobal nowrapscan " Reset global and local value (without triggering autocmd) + noa setlocal wrapscan " Sets the global(!) value! + let g:options = [['wrapscan', '1', '1', '1', '0', 'global', 'set']] + set nowrapscan + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " 28d: Setting again global boolean global option" + noa set nowrapscan " Reset global and local value (without triggering autocmd) + let g:options = [['wrapscan', '0', '0', '0', '1', 'global', 'set']] + set wrapscan + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + + " 29a: Setting global boolean global-local (to buffer) option" + noa setglobal noautoread " Reset global and local value (without triggering autocmd) + noa setlocal autoread + let g:options = [['autoread', '0', '', '0', '1', 'global', 'setglobal']] + setglobal autoread + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " 29b: Setting local boolean global-local (to buffer) option" + noa setglobal noautoread " Reset global and local value (without triggering autocmd) + noa setlocal autoread + let g:options = [['autoread', '1', '1', '', '0', 'local', 'setlocal']] + setlocal noautoread + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " 29c: Setting again boolean global-local (to buffer) option" + noa setglobal noautoread " Reset global and local value (without triggering autocmd) + noa setlocal autoread + let g:options = [['autoread', '1', '1', '0', '1', 'global', 'set']] + set autoread + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " 29d: Setting again global boolean global-local (to buffer) option" + noa set noautoread " Reset global and local value (without triggering autocmd) + let g:options = [['autoread', '0', '0', '0', '1', 'global', 'set']] + set autoread + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + + " 30a: Setting global boolean local (to buffer) option" + noa setglobal nocindent " Reset global and local value (without triggering autocmd) + noa setlocal cindent + let g:options = [['cindent', '0', '', '0', '1', 'global', 'setglobal']] + setglobal cindent + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " 30b: Setting local boolean local (to buffer) option" + noa setglobal nocindent " Reset global and local value (without triggering autocmd) + noa setlocal cindent + let g:options = [['cindent', '1', '1', '', '0', 'local', 'setlocal']] + setlocal nocindent + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " 30c: Setting again boolean local (to buffer) option" + noa setglobal nocindent " Reset global and local value (without triggering autocmd) + noa setlocal cindent + let g:options = [['cindent', '1', '1', '0', '1', 'global', 'set']] + set cindent + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " 30d: Setting again global boolean local (to buffer) option" + noa set nocindent " Reset global and local value (without triggering autocmd) + let g:options = [['cindent', '0', '0', '0', '1', 'global', 'set']] + set cindent + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + + " 31: Setting boolean global-local (to window) option + " Currently no such option exists. + + + " 32a: Setting global boolean local (to window) option" + noa setglobal nocursorcolumn " Reset global and local value (without triggering autocmd) + noa setlocal cursorcolumn + let g:options = [['cursorcolumn', '0', '', '0', '1', 'global', 'setglobal']] + setglobal cursorcolumn + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " 32b: Setting local boolean local (to window) option" + noa setglobal nocursorcolumn " Reset global and local value (without triggering autocmd) + noa setlocal cursorcolumn + let g:options = [['cursorcolumn', '1', '1', '', '0', 'local', 'setlocal']] + setlocal nocursorcolumn + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " 32c: Setting again boolean local (to window) option" + noa setglobal nocursorcolumn " Reset global and local value (without triggering autocmd) + noa setlocal cursorcolumn + let g:options = [['cursorcolumn', '1', '1', '0', '1', 'global', 'set']] + set cursorcolumn + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " 32d: Setting again global boolean local (to window) option" + noa set nocursorcolumn " Reset global and local value (without triggering autocmd) + let g:options = [['cursorcolumn', '0', '0', '0', '1', 'global', 'set']] + set cursorcolumn + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + + " 33: Test autocommands when an option value is converted internally. + noa set backspace=1 " Reset global and local value (without triggering autocmd) + let g:options = [['backspace', 'indent,eol', 'indent,eol', 'indent,eol', '2', 'global', 'set']] + set backspace=2 + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " Cleanup au! OptionSet " set tags& - for opt in ['nu', 'ai', 'acd', 'ar', 'bs', 'backup', 'cul', 'cp', 'tags'] + for opt in ['nu', 'ai', 'acd', 'ar', 'bs', 'backup', 'cul', 'cp', 'backupext', 'tags', 'spelllang', 'statusline', 'foldignore', 'cmdheight', 'undolevels', 'wrapmargin', 'foldcolumn', 'wrapscan', 'autoread', 'cindent', 'cursorcolumn'] exe printf(":set %s&vim", opt) endfor call test_override('starting', 0) @@ -1043,7 +1619,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') @@ -1364,6 +1940,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! @@ -1387,14 +1971,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 @@ -1416,6 +2002,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 @@ -1423,7 +2010,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() @@ -1868,6 +2456,19 @@ func Test_throw_in_BufWritePre() au! throwing endfunc +func Test_autocmd_in_try_block() + call mkdir('Xdir') + au BufEnter * let g:fname = expand('%') + try + edit Xdir/ + endtry + call assert_match('Xdir', g:fname) + + unlet g:fname + au! BufEnter + call delete('Xdir', 'rf') +endfunc + func Test_autocmd_CmdWinEnter() CheckRunVimInTerminal " There is not cmdwin switch, so @@ -1897,96 +2498,62 @@ func Test_autocmd_CmdWinEnter() call delete(filename) endfunc -func Test_FileChangedShell_reload() - if !has('unix') - return - endif - augroup testreload - au FileChangedShell Xchanged let g:reason = v:fcs_reason | let v:fcs_choice = 'reload' +func Test_autocmd_was_using_freed_memory() + pedit xx + n x + augroup winenter + au WinEnter * if winnr('$') > 2 | quit | endif augroup END - new Xchanged - 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 - checktime - call assert_equal('changed', g:reason) - call assert_equal(2, line('$')) - call assert_equal('extra line', getline(2)) - - " Only triggers once - let g:reason = '' - checktime - call assert_equal('', g:reason) - - " When deleted buffer is not reloaded - silent !rm Xchanged - let g:reason = '' - checktime - call assert_equal('deleted', g:reason) - call assert_equal(2, line('$')) - call assert_equal('extra line', getline(2)) - - " When recreated buffer is reloaded - call setline(1, 'buffer is changed') - silent !echo 'new line' >>Xchanged - let g:reason = '' - checktime - call assert_equal('conflict', g:reason) - call assert_equal(1, line('$')) - call assert_equal('new line', getline(1)) + " Nvim needs large 'winwidth' and 'nowinfixwidth' to crash + set winwidth=99999 nowinfixwidth + split - " Only mode changed - silent !chmod +x Xchanged - let g:reason = '' - checktime - call assert_equal('mode', g:reason) - call assert_equal(1, line('$')) - call assert_equal('new line', getline(1)) - - " Only time changed - sleep 2 - silent !touch Xchanged - let g:reason = '' - checktime - call assert_equal('time', g:reason) - call assert_equal(1, line('$')) - call assert_equal('new line', getline(1)) - - if has('persistent_undo') - " With an undo file the reload can be undone and a change before the - " reload. - set undofile - call setline(2, 'before write') - write - call setline(2, 'after write') - sleep 2 - silent !echo 'different line' >>Xchanged - let g:reason = '' - checktime - call assert_equal('conflict', g:reason) - call assert_equal(3, line('$')) - call assert_equal('before write', getline(2)) - call assert_equal('different line', getline(3)) - " undo the reload - undo - call assert_equal(2, line('$')) - call assert_equal('after write', getline(2)) - " undo the change before reload - undo - call assert_equal(2, line('$')) - call assert_equal('before write', getline(2)) + augroup winenter + au! WinEnter + augroup END + + set winwidth& winfixwidth& + bwipe xx + bwipe x + 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("']")]) - set noundofile + 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! testreload - bwipe! - call delete('Xchanged') + au! lockmarks + augroup! lockmarks + call delete('Xtest') + call delete('Xtest2') endfunc +" FileChangedShell tested in test_filechanged.vim + func LogACmd() call add(g:logged, line('$')) endfunc @@ -2114,6 +2681,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 @@ -2125,6 +2702,19 @@ func Test_autocmd_closes_window() au! BufWinLeave endfunc +func Test_autocmd_quit_psearch() + sn aa bb + augroup aucmd_win_test + au! + au BufEnter,BufLeave,BufNew,WinEnter,WinLeave,WinNew * if winnr('$') > 1 | q | endif + augroup END + ps / + + augroup aucmd_win_test + au! + augroup END +endfunc + func Test_autocmd_closing_cmdwin() au BufWinLeave * nested q call assert_fails("norm 7q?\n", 'E855:') @@ -2134,4 +2724,39 @@ func Test_autocmd_closing_cmdwin() only endfunc +func Test_bufwipeout_changes_window() + " This should not crash, but we don't have any expectations about what + " happens, changing window in BufWipeout has unpredictable results. + tabedit + let g:window_id = win_getid() + topleft new + setlocal bufhidden=wipe + autocmd BufWipeout <buffer> call win_gotoid(g:window_id) + tabprevious + +tabclose + + unlet g:window_id + au! BufWipeout + %bwipe! +endfunc + +func Test_v_event_readonly() + autocmd CompleteChanged * let v:event.width = 0 + call assert_fails("normal! i\<C-X>\<C-V>", 'E46:') + au! CompleteChanged + + autocmd DirChangedPre * let v:event.directory = '' + call assert_fails('cd .', 'E46:') + au! DirChangedPre + + autocmd ModeChanged * let v:event.new_mode = '' + call assert_fails('normal! cc', 'E46:') + au! ModeChanged + + autocmd TextYankPost * let v:event.operator = '' + call assert_fails('normal! yy', 'E46:') + au! TextYankPost +endfunc + + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_blob.vim b/src/nvim/testdir/test_blob.vim index 20758b0c0a..af42b3857d 100644 --- a/src/nvim/testdir/test_blob.vim +++ b/src/nvim/testdir/test_blob.vim @@ -346,4 +346,12 @@ func Test_blob_sort() endif endfunc +" The following used to cause an out-of-bounds memory access +func Test_blob2string() + let v = '0z' .. repeat('01010101.', 444) + let v ..= '01' + exe 'let b = ' .. v + call assert_equal(v, string(b)) +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_blockedit.vim b/src/nvim/testdir/test_blockedit.vim index 180524cd73..7b56b1554f 100644 --- a/src/nvim/testdir/test_blockedit.vim +++ b/src/nvim/testdir/test_blockedit.vim @@ -15,6 +15,58 @@ func Test_blockinsert_indent() bwipe! endfunc +func Test_blockinsert_autoindent() + new + let lines =<< trim END + var d = { + a: () => 0, + b: () => 0, + c: () => 0, + } + END + call setline(1, lines) + filetype plugin indent on + setlocal sw=2 et ft=vim + setlocal indentkeys+=: + exe "norm! 2Gf)\<c-v>2jA: asdf\<esc>" + let expected =<< trim END + var d = { + a: (): asdf => 0, + b: (): asdf => 0, + c: (): asdf => 0, + } + END + call assert_equal(expected, getline(1, 5)) + + " insert on the next column should do exactly the same + :%dele + call setline(1, lines) + exe "norm! 2Gf)l\<c-v>2jI: asdf\<esc>" + call assert_equal(expected, getline(1, 5)) + + :%dele + call setline(1, lines) + setlocal sw=8 noet + exe "norm! 2Gf)\<c-v>2jA: asdf\<esc>" + let expected =<< trim END + var d = { + a: (): asdf => 0, + b: (): asdf => 0, + c: (): asdf => 0, + } + END + call assert_equal(expected, getline(1, 5)) + + " insert on the next column should do exactly the same + :%dele + call setline(1, lines) + exe "norm! 2Gf)l\<c-v>2jI: asdf\<esc>" + call assert_equal(expected, getline(1, 5)) + + filetype off + bwipe! +endfunc + func Test_blockinsert_delete() new let _bs = &bs @@ -29,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 277050876e..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 @@ -432,7 +432,7 @@ func Test_breakindent11_vartabs() call s:test_windows('setl cpo-=n sbr=>> nu nuw=4 nolist briopt= ts=4 vts=4') let text = getline(2) let width = strlen(text[1:]) + 2->indent() + strlen(&sbr) * 3 " text wraps 3 times - call assert_equal(width, strdisplaywidth(text)) + call assert_equal(width, text->strdisplaywidth()) call s:close_windows('set sbr= vts&') 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_bufwintabinfo.vim b/src/nvim/testdir/test_bufwintabinfo.vim index bb672cf0ec..a6eb93b4be 100644 --- a/src/nvim/testdir/test_bufwintabinfo.vim +++ b/src/nvim/testdir/test_bufwintabinfo.vim @@ -1,108 +1,113 @@ " Tests for the getbufinfo(), getwininfo() and gettabinfo() functions function Test_getbufwintabinfo() - edit Xtestfile1 - edit Xtestfile2 - let buflist = getbufinfo() - call assert_equal(2, len(buflist)) - call assert_match('Xtestfile1', buflist[0].name) - call assert_match('Xtestfile2', getbufinfo('Xtestfile2')[0].name) - call assert_equal([], getbufinfo(2016)) - edit Xtestfile1 - hide edit Xtestfile2 - hide enew - call assert_equal(3, len(getbufinfo({'bufloaded':1}))) - - set tabstop&vim - let b:editor = 'vim' + edit Xtestfile1 + edit Xtestfile2 + let buflist = getbufinfo() + call assert_equal(2, len(buflist)) + call assert_match('Xtestfile1', buflist[0].name) + call assert_match('Xtestfile2', getbufinfo('Xtestfile2')[0].name) + call assert_equal([], getbufinfo(2016)) + edit Xtestfile1 + hide edit Xtestfile2 + hide enew + call assert_equal(3, len(getbufinfo({'bufloaded':1}))) + + set tabstop&vim + let b:editor = 'vim' + let l = getbufinfo('%') + call assert_equal(bufnr('%'), l[0].bufnr) + call assert_equal('vim', l[0].variables.editor) + call assert_notequal(-1, index(l[0].windows, '%'->bufwinid())) + + let l = '%'->getbufinfo() + call assert_equal(bufnr('%'), l[0].bufnr) + + " Test for getbufinfo() with 'bufmodified' + call assert_equal(0, len(getbufinfo({'bufmodified' : 1}))) + call setbufline('Xtestfile1', 1, ["Line1"]) + let l = getbufinfo({'bufmodified' : 1}) + call assert_equal(1, len(l)) + call assert_equal(bufnr('Xtestfile1'), l[0].bufnr) + + if has('signs') + call append(0, ['Linux', 'Windows', 'Mac']) + sign define Mark text=>> texthl=Search + exe "sign place 2 line=3 name=Mark buffer=" . bufnr('%') let l = getbufinfo('%') - call assert_equal(bufnr('%'), l[0].bufnr) - call assert_equal('vim', l[0].variables.editor) - call assert_notequal(-1, index(l[0].windows, '%'->bufwinid())) - - " Test for getbufinfo() with 'bufmodified' - call assert_equal(0, len(getbufinfo({'bufmodified' : 1}))) - call setbufline('Xtestfile1', 1, ["Line1"]) - let l = getbufinfo({'bufmodified' : 1}) - call assert_equal(1, len(l)) - call assert_equal(bufnr('Xtestfile1'), l[0].bufnr) - - if has('signs') - call append(0, ['Linux', 'Windows', 'Mac']) - sign define Mark text=>> texthl=Search - exe "sign place 2 line=3 name=Mark buffer=" . bufnr('%') - let l = getbufinfo('%') - call assert_equal(2, l[0].signs[0].id) - call assert_equal(3, l[0].signs[0].lnum) - call assert_equal('Mark', l[0].signs[0].name) - sign unplace * - sign undefine Mark - enew! - endif - - only - let w1_id = win_getid() - new - let w2_id = win_getid() - tabnew | let w3_id = win_getid() - new | let w4_id = win_getid() - vert new | let w5_id = win_getid() - call setwinvar(0, 'signal', 'green') - tabfirst - let winlist = getwininfo() - call assert_equal(5, len(winlist)) - call assert_equal(winwidth(1), winlist[0].width) - call assert_equal(1, winlist[0].wincol) - " tabline adds one row in terminal, not in GUI - let tablineheight = winlist[0].winrow == 2 ? 1 : 0 - call assert_equal(tablineheight + 1, winlist[0].winrow) - - call assert_equal(winbufnr(2), winlist[1].bufnr) - call assert_equal(winheight(2), winlist[1].height) - call assert_equal(1, winlist[1].wincol) - call assert_equal(tablineheight + winheight(1) + 2, winlist[1].winrow) - - call assert_equal(1, winlist[2].winnr) - call assert_equal(tablineheight + 1, winlist[2].winrow) - call assert_equal(1, winlist[2].wincol) - - call assert_equal(winlist[2].width + 2, winlist[3].wincol) - call assert_equal(1, winlist[4].wincol) - - call assert_equal(1, winlist[0].tabnr) - call assert_equal(1, winlist[1].tabnr) - call assert_equal(2, winlist[2].tabnr) - call assert_equal(2, winlist[3].tabnr) - call assert_equal(2, winlist[4].tabnr) - - call assert_equal('green', winlist[2].variables.signal) - call assert_equal(w4_id, winlist[3].winid) - let winfo = w5_id->getwininfo()[0] - call assert_equal(2, winfo.tabnr) - call assert_equal([], getwininfo(3)) - - call settabvar(1, 'space', 'build') - let tablist = gettabinfo() - call assert_equal(2, len(tablist)) - call assert_equal(3, len(tablist[1].windows)) - call assert_equal(2, tablist[1].tabnr) - call assert_equal('build', tablist[0].variables.space) - call assert_equal(w2_id, tablist[0].windows[0]) - call assert_equal([], 3->gettabinfo()) - - tabonly | only - - lexpr '' - lopen - copen - let winlist = getwininfo() - call assert_false(winlist[0].quickfix) - call assert_false(winlist[0].loclist) - call assert_true(winlist[1].quickfix) - call assert_true(winlist[1].loclist) - call assert_true(winlist[2].quickfix) - call assert_false(winlist[2].loclist) - wincmd t | only + call assert_equal(2, l[0].signs[0].id) + call assert_equal(3, l[0].signs[0].lnum) + call assert_equal('Mark', l[0].signs[0].name) + sign unplace * + sign undefine Mark + enew! + endif + + only + let w1_id = win_getid() + setl foldcolumn=3 + new + let w2_id = win_getid() + tabnew | let w3_id = win_getid() + new | let w4_id = win_getid() + vert new | let w5_id = win_getid() + eval 'green'->setwinvar(0, 'signal') + tabfirst + let winlist = getwininfo() + call assert_equal(5, len(winlist)) + call assert_equal(winwidth(1), winlist[0].width) + call assert_equal(1, winlist[0].wincol) + " tabline adds one row in terminal, not in GUI + let tablineheight = winlist[0].winrow == 2 ? 1 : 0 + call assert_equal(tablineheight + 1, winlist[0].winrow) + + call assert_equal(winbufnr(2), winlist[1].bufnr) + call assert_equal(winheight(2), winlist[1].height) + call assert_equal(1, winlist[1].wincol) + call assert_equal(3, winlist[1].textoff) " foldcolumn + call assert_equal(tablineheight + winheight(1) + 2, winlist[1].winrow) + + call assert_equal(1, winlist[2].winnr) + call assert_equal(tablineheight + 1, winlist[2].winrow) + call assert_equal(1, winlist[2].wincol) + + call assert_equal(winlist[2].width + 2, winlist[3].wincol) + call assert_equal(1, winlist[4].wincol) + + call assert_equal(1, winlist[0].tabnr) + call assert_equal(1, winlist[1].tabnr) + call assert_equal(2, winlist[2].tabnr) + call assert_equal(2, winlist[3].tabnr) + call assert_equal(2, winlist[4].tabnr) + + call assert_equal('green', winlist[2].variables.signal) + call assert_equal(w4_id, winlist[3].winid) + let winfo = w5_id->getwininfo()[0] + call assert_equal(2, winfo.tabnr) + call assert_equal([], getwininfo(3)) + + call settabvar(1, 'space', 'build') + let tablist = gettabinfo() + call assert_equal(2, len(tablist)) + call assert_equal(3, len(tablist[1].windows)) + call assert_equal(2, tablist[1].tabnr) + call assert_equal('build', tablist[0].variables.space) + call assert_equal(w2_id, tablist[0].windows[0]) + call assert_equal([], 3->gettabinfo()) + + tabonly | only + + lexpr '' + lopen + copen + let winlist = getwininfo() + call assert_false(winlist[0].quickfix) + call assert_false(winlist[0].loclist) + call assert_true(winlist[1].quickfix) + call assert_true(winlist[1].loclist) + call assert_true(winlist[2].quickfix) + call assert_false(winlist[2].loclist) + wincmd t | only endfunction function Test_get_buf_options() diff --git a/src/nvim/testdir/test_cd.vim b/src/nvim/testdir/test_cd.vim index 0bba321ee2..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:') @@ -101,7 +110,7 @@ func Test_chdir_func() call assert_match('^\[global\] .*/Xdir$', trim(execute('verbose pwd'))) call chdir('..') call assert_equal('y', fnamemodify(getcwd(1, 2), ':t')) - call assert_equal('z', fnamemodify(getcwd(3, 2), ':t')) + call assert_equal('z', fnamemodify(3->getcwd(2), ':t')) tabnext | wincmd t call assert_match('^\[tabpage\] .*/y$', trim(execute('verbose pwd'))) call chdir('..') @@ -215,3 +224,42 @@ func Test_cd_from_non_existing_dir() cd - call assert_equal(saveddir, getcwd()) endfunc + +func Test_cd_unknown_dir() + call mkdir('Xa') + cd Xa + call writefile(['text'], 'Xb.txt') + edit Xa/Xb.txt + let first_buf = bufnr() + cd .. + edit + call assert_equal(first_buf, bufnr()) + edit Xa/Xb.txt + call assert_notequal(first_buf, bufnr()) + + bwipe! + exe "bwipe! " .. first_buf + call delete('Xa', 'rf') +endfunc + +func Test_getcwd_actual_dir() + CheckFunction test_autochdir + let startdir = getcwd() + call mkdir('Xactual') + call test_autochdir() + set autochdir + edit Xactual/file.txt + call assert_match('testdir.Xactual$', getcwd()) + lcd .. + call assert_match('testdir$', getcwd()) + edit + call assert_match('testdir.Xactual$', getcwd()) + call assert_match('testdir$', getcwd(win_getid())) + + set noautochdir + bwipe! + call chdir(startdir) + call delete('Xactual', 'rf') +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_cdo.vim b/src/nvim/testdir/test_cdo.vim new file mode 100644 index 0000000000..dbed7df4ac --- /dev/null +++ b/src/nvim/testdir/test_cdo.vim @@ -0,0 +1,216 @@ +" Tests for the :cdo, :cfdo, :ldo and :lfdo commands + +source check.vim +CheckFeature quickfix + +" Create the files used by the tests +func SetUp() + call writefile(["Line1", "Line2", "Line3"], 'Xtestfile1') + call writefile(["Line1", "Line2", "Line3"], 'Xtestfile2') + call writefile(["Line1", "Line2", "Line3"], 'Xtestfile3') +endfunc + +" Remove the files used by the tests +func TearDown() + call delete('Xtestfile1') + call delete('Xtestfile2') + call delete('Xtestfile3') +endfunc + +" Returns the current line in '<filename> <linenum>L <column>C' format +func GetRuler() + return expand('%') . ' ' . line('.') . 'L' . ' ' . col('.') . 'C' +endfunc + +" Tests for the :cdo and :ldo commands +func 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) + +endfunc + +" Tests for the :cfdo and :lfdo commands +func 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) + +endfunc + +" Tests for cdo and cfdo +func Test_cdo() + call XdoTests('c') + call XfdoTests('c') +endfunc + +" Tests for ldo and lfdo +func Test_ldo() + call XdoTests('l') + call XfdoTests('l') +endfunc + +" Test for making 'shm' doesn't interfere with the output. +func Test_cdo_print() + enew | only! + cgetexpr ["Xtestfile1:1:Line1", "Xtestfile2:1:Line1", "Xtestfile3:1:Line1"] + cdo print + call assert_equal('Line1', Screenline(&lines)) + call assert_equal('Line1', Screenline(&lines - 3)) + call assert_equal('Line1', Screenline(&lines - 6)) +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_charsearch.vim b/src/nvim/testdir/test_charsearch.vim index 17a49e02be..6f09e85a42 100644 --- a/src/nvim/testdir/test_charsearch.vim +++ b/src/nvim/testdir/test_charsearch.vim @@ -20,7 +20,7 @@ func Test_charsearch() " check that setcharsearch() changes the settings. 3 normal! ylfep - call setcharsearch({'char': 'k'}) + eval {'char': 'k'}->setcharsearch() normal! ;p call setcharsearch({'forward': 0}) normal! $;p 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..4d69aed96c 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,15 +127,5233 @@ 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 +func Test_cindent_scopedecls() + new + setl cindent ts=4 sw=4 + setl cino=g0 + setl cinsd+=public\ slots,signals + + let code =<< trim [CODE] + class Foo + { + public: + virtual void foo() = 0; + public slots: + void onBar(); + signals: + void baz(); + private: + int x; + }; + [CODE] + + call append(0, code) + normal gg + normal ]]=][ + + let expected =<< trim [CODE] + class Foo + { + public: + virtual void foo() = 0; + public slots: + void onBar(); + signals: + void baz(); + private: + int x; + }; + + [CODE] + + call assert_equal(expected, getline(1, '$')) + enew! | close +endfunc + func Test_cindent_pragma() new setl cindent ts=4 sw=4 @@ -173,4 +5390,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_clientserver.vim b/src/nvim/testdir/test_clientserver.vim index f3db472b03..922803438f 100644 --- a/src/nvim/testdir/test_clientserver.vim +++ b/src/nvim/testdir/test_clientserver.vim @@ -82,7 +82,7 @@ func Test_client_server() call remote_send(name, ":call server2client(expand('<client>'), 'got it')\<CR>", 'g:myserverid') call assert_equal('got it', g:myserverid->remote_read(2)) - call remote_send(name, ":call server2client(expand('<client>'), 'another')\<CR>", 'g:myserverid') + call remote_send(name, ":eval expand('<client>')->server2client('another')\<CR>", 'g:myserverid') let peek_result = 'nothing' let r = g:myserverid->remote_peek('peek_result') " unpredictable whether the result is already available. diff --git a/src/nvim/testdir/test_cmdline.vim b/src/nvim/testdir/test_cmdline.vim index 98340d0ac6..c589d941da 100644 --- a/src/nvim/testdir/test_cmdline.vim +++ b/src/nvim/testdir/test_cmdline.vim @@ -428,14 +428,17 @@ func Test_getcompletion() call assert_equal([], l) func T(a, c, p) + let g:cmdline_compl_params = [a:a, a:c, a:p] return "oneA\noneB\noneC" endfunc command -nargs=1 -complete=custom,T MyCmd let l = getcompletion('MyCmd ', 'cmdline') call assert_equal(['oneA', 'oneB', 'oneC'], l) + call assert_equal(['', 'MyCmd ', 6], g:cmdline_compl_params) delcommand MyCmd delfunc T + unlet g:cmdline_compl_params " For others test if the name is recognized. let names = ['buffer', 'environment', 'file_in_path', 'mapping', 'tag', 'tag_listfiles', 'user'] @@ -464,6 +467,51 @@ func Test_getcompletion() call assert_fails('call getcompletion("abc", [])', 'E475:') endfunc +func Test_fullcommand() + let tests = { + \ '': '', + \ ':': '', + \ ':::': '', + \ ':::5': '', + \ 'not_a_cmd': '', + \ 'Check': '', + \ 'syntax': 'syntax', + \ ':syntax': 'syntax', + \ '::::syntax': 'syntax', + \ 'sy': 'syntax', + \ 'syn': 'syntax', + \ 'synt': 'syntax', + \ ':sy': 'syntax', + \ '::::sy': 'syntax', + \ 'match': 'match', + \ '2match': 'match', + \ '3match': 'match', + \ 'aboveleft': 'aboveleft', + \ 'abo': 'aboveleft', + \ 's': 'substitute', + \ '5s': 'substitute', + \ ':5s': 'substitute', + \ "'<,'>s": 'substitute', + \ ":'<,'>s": 'substitute', + \ 'CheckUni': 'CheckUnix', + \ 'CheckUnix': 'CheckUnix', + \ } + + 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() let save_path = $PATH @@ -902,7 +950,7 @@ func Test_setcmdpos() call assert_equal('"12ab', @:) " setcmdpos() returns 1 when not editing the command line. - call assert_equal(1, setcmdpos(3)) + call assert_equal(1, 3->setcmdpos()) endfunc func Test_cmdline_overstrike() @@ -1075,6 +1123,18 @@ func Test_cmdlineclear_tabenter() call delete('XtestCmdlineClearTabenter') endfunc +func Test_cmdwin_tabpage() + tabedit + " v8.2.1919 isn't ported yet, so E492 is thrown after E11 here. + " v8.2.1183 also isn't ported yet, so we also can't assert E11 directly. + " For now, assert E11 and E492 seperately. When v8.2.1183 is ported, the + " assert for E492 will fail and this workaround should be removed. + " call assert_fails("silent norm q/g :I\<Esc>", 'E11:') + call assert_fails("silent norm q/g ", 'E11:') + call assert_fails("silent norm q/g :I\<Esc>", 'E492:') + tabclose! +endfunc + " test that ";" works to find a match at the start of the first line func Test_zero_line_search() new diff --git a/src/nvim/testdir/test_compiler.vim b/src/nvim/testdir/test_compiler.vim index c3de7d0050..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') @@ -38,10 +41,9 @@ func Test_compiler() endfunc func GetCompilerNames() - " return glob('$VIMRUNTIME/compiler/*.vim', 0, 1) - " \ ->map({i, v -> substitute(v, '.*[\\/]\([a-zA-Z0-9_\-]*\).vim', '\1', '')}) - " \ ->sort() - return sort(map(glob('$VIMRUNTIME/compiler/*.vim', 0, 1), {i, v -> substitute(v, '.*[\\/]\([a-zA-Z0-9_\-]*\).vim', '\1', '')})) + return glob('$VIMRUNTIME/compiler/*.vim', 0, 1) + \ ->map({i, v -> substitute(v, '.*[\\/]\([a-zA-Z0-9_\-]*\).vim', '\1', '')}) + \ ->sort() endfunc func Test_compiler_without_arg() @@ -54,8 +56,7 @@ func Test_compiler_without_arg() endfunc func Test_compiler_completion() - " let clist = GetCompilerNames()->join(' ') - let clist = join(GetCompilerNames(), ' ') + let clist = GetCompilerNames()->join(' ') call feedkeys(":compiler \<C-A>\<C-B>\"\<CR>", 'tx') call assert_match('^"compiler ' .. clist .. '$', @:) 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 46847e0663..9ba82e3b70 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) @@ -37,7 +40,7 @@ endfunc " Very short version of what matchparen does. function s:Highlight_Matching_Pair() let save_cursor = getcurpos() - call setpos('.', save_cursor) + eval save_cursor->setpos('.') endfunc func Test_curswant_with_autocommand() @@ -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"]) @@ -82,11 +84,11 @@ func Test_screenpos() call assert_equal({'row': winrow, \ 'col': wincol + 0, \ 'curscol': wincol + 7, - \ 'endcol': wincol + 7}, screenpos(winid, 1, 1)) + \ 'endcol': wincol + 7}, winid->screenpos(1, 1)) call assert_equal({'row': winrow, \ 'col': wincol + 13, \ 'curscol': wincol + 13, - \ 'endcol': wincol + 13}, screenpos(winid, 1, 7)) + \ 'endcol': wincol + 13}, winid->screenpos(1, 7)) call assert_equal({'row': winrow + 2, \ 'col': wincol + 1, \ 'curscol': wincol + 1, @@ -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,253 @@ 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', ' │ x']) + + " Test for '.' and '$' + normal 1G + call assert_equal([0, 1, 1, 0], getcharpos('.')) + call assert_equal([0, 5, 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")) + + " Check mark does not move + normal 5Gfxma + call assert_equal([0, 5, 5, 0], getcharpos("'a")) + call assert_equal([0, 5, 5, 0], getcharpos("'a")) + call assert_equal([0, 5, 5, 0], getcharpos("'a")) + + " 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_cursorline.vim b/src/nvim/testdir/test_cursorline.vim index 39d8b901ed..7e97df6027 100644 --- a/src/nvim/testdir/test_cursorline.vim +++ b/src/nvim/testdir/test_cursorline.vim @@ -268,4 +268,51 @@ END call delete('Xtextfile') endfunc +func Test_cursorline_callback() + CheckScreendump + CheckFeature timers + + let lines =<< trim END + call setline(1, ['aaaaa', 'bbbbb', 'ccccc', 'ddddd']) + set cursorline + call cursor(4, 1) + + func Func(timer) + call cursor(2, 1) + endfunc + + call timer_start(300, 'Func') + END + call writefile(lines, 'Xcul_timer') + + let buf = RunVimInTerminal('-S Xcul_timer', #{rows: 8}) + call TermWait(buf, 310) + call VerifyScreenDump(buf, 'Test_cursorline_callback_1', {}) + + call StopVimInTerminal(buf) + call delete('Xcul_timer') +endfunc + +func Test_cursorline_screenline_update() + CheckScreendump + + let lines =<< trim END + call setline(1, repeat('xyz ', 30)) + set cursorline cursorlineopt=screenline + inoremap <F2> <Cmd>call cursor(1, 1)<CR> + END + call writefile(lines, 'Xcul_screenline') + + let buf = RunVimInTerminal('-S Xcul_screenline', #{rows: 8}) + call term_sendkeys(buf, "A") + call VerifyScreenDump(buf, 'Test_cursorline_screenline_1', {}) + call term_sendkeys(buf, "\<F2>") + call VerifyScreenDump(buf, 'Test_cursorline_screenline_2', {}) + call term_sendkeys(buf, "\<Esc>") + + call StopVimInTerminal(buf) + call delete('Xcul_screenline') +endfunc + + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_delete.vim b/src/nvim/testdir/test_delete.vim new file mode 100644 index 0000000000..b23a3bd025 --- /dev/null +++ b/src/nvim/testdir/test_delete.vim @@ -0,0 +1,114 @@ +" Test for delete(). + +func Test_file_delete() + split Xfile + call setline(1, ['a', 'b']) + wq + call assert_equal(['a', 'b'], readfile('Xfile')) + call assert_equal(0, delete('Xfile')) + call assert_fails('call readfile("Xfile")', 'E484:') + call assert_equal(-1, delete('Xfile')) + bwipe Xfile +endfunc + +func Test_dir_delete() + call mkdir('Xdir1') + call assert_true(isdirectory('Xdir1')) + call assert_equal(0, delete('Xdir1', 'd')) + call assert_false(isdirectory('Xdir1')) + call assert_equal(-1, delete('Xdir1', 'd')) +endfunc + +func Test_recursive_delete() + call mkdir('Xdir1') + call mkdir('Xdir1/subdir') + call mkdir('Xdir1/empty') + split Xdir1/Xfile + call setline(1, ['a', 'b']) + w + w Xdir1/subdir/Xfile + close + call assert_true(isdirectory('Xdir1')) + call assert_equal(['a', 'b'], readfile('Xdir1/Xfile')) + call assert_true(isdirectory('Xdir1/subdir')) + call assert_equal(['a', 'b'], readfile('Xdir1/subdir/Xfile')) + call assert_true('Xdir1/empty'->isdirectory()) + call assert_equal(0, delete('Xdir1', 'rf')) + call assert_false(isdirectory('Xdir1')) + call assert_equal(-1, delete('Xdir1', 'd')) + bwipe Xdir1/Xfile + bwipe Xdir1/subdir/Xfile +endfunc + +func Test_symlink_delete() + if !has('unix') + return + endif + split Xfile + call setline(1, ['a', 'b']) + wq + silent !ln -s Xfile Xlink + " Delete the link, not the file + call assert_equal(0, delete('Xlink')) + call assert_equal(-1, delete('Xlink')) + call assert_equal(0, delete('Xfile')) + bwipe Xfile +endfunc + +func Test_symlink_dir_delete() + if !has('unix') + return + endif + call mkdir('Xdir1') + silent !ln -s Xdir1 Xlink + call assert_true(isdirectory('Xdir1')) + call assert_true(isdirectory('Xlink')) + " Delete the link, not the directory + call assert_equal(0, delete('Xlink')) + call assert_equal(-1, delete('Xlink')) + call assert_equal(0, delete('Xdir1', 'd')) +endfunc + +func Test_symlink_recursive_delete() + if !has('unix') + return + endif + call mkdir('Xdir3') + call mkdir('Xdir3/subdir') + call mkdir('Xdir4') + split Xdir3/Xfile + call setline(1, ['a', 'b']) + w + w Xdir3/subdir/Xfile + w Xdir4/Xfile + close + silent !ln -s ../Xdir4 Xdir3/Xlink + + call assert_true(isdirectory('Xdir3')) + call assert_equal(['a', 'b'], readfile('Xdir3/Xfile')) + call assert_true(isdirectory('Xdir3/subdir')) + call assert_equal(['a', 'b'], readfile('Xdir3/subdir/Xfile')) + call assert_true(isdirectory('Xdir4')) + call assert_true(isdirectory('Xdir3/Xlink')) + call assert_equal(['a', 'b'], readfile('Xdir4/Xfile')) + + call assert_equal(0, delete('Xdir3', 'rf')) + call assert_false(isdirectory('Xdir3')) + call assert_equal(-1, delete('Xdir3', 'd')) + " symlink is deleted, not the directory it points to + call assert_true(isdirectory('Xdir4')) + call assert_equal(['a', 'b'], readfile('Xdir4/Xfile')) + call assert_equal(0, delete('Xdir4/Xfile')) + call assert_equal(0, delete('Xdir4', 'd')) + + bwipe Xdir3/Xfile + bwipe Xdir3/subdir/Xfile + bwipe Xdir4/Xfile +endfunc + +func Test_delete_errors() + call assert_fails('call delete('''')', 'E474:') + call assert_fails('call delete(''foo'', 0)', 'E15:') +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_diffmode.vim b/src/nvim/testdir/test_diffmode.vim index 61da3cbcaa..be9a77ee75 100644 --- a/src/nvim/testdir/test_diffmode.vim +++ b/src/nvim/testdir/test_diffmode.vim @@ -387,7 +387,7 @@ func Test_diffoff() call setline(1, ['One', '', 'Two', 'Three']) diffthis redraw - call assert_notequal(normattr, screenattr(1, 1)) + call assert_notequal(normattr, 1->screenattr(1)) diffoff! redraw call assert_equal(normattr, screenattr(1, 1)) @@ -1017,6 +1017,32 @@ func Test_diff_with_cursorline() call delete('Xtest_diff_cursorline') endfunc +func Test_diff_with_cursorline_number() + CheckScreendump + + let lines =<< trim END + hi CursorLine ctermbg=red ctermfg=white + hi CursorLineNr ctermbg=white ctermfg=black cterm=underline + set cursorline number + call setline(1, ["baz", "foo", "foo", "bar"]) + 2 + vnew + call setline(1, ["foo", "foo", "bar"]) + windo diffthis + 1wincmd w + END + call writefile(lines, 'Xtest_diff_cursorline_number') + let buf = RunVimInTerminal('-S Xtest_diff_cursorline_number', {}) + + call VerifyScreenDump(buf, 'Test_diff_with_cursorline_number_01', {}) + call term_sendkeys(buf, ":set cursorlineopt=number\r") + call VerifyScreenDump(buf, 'Test_diff_with_cursorline_number_02', {}) + + " clean up + call StopVimInTerminal(buf) + call delete('Xtest_diff_cursorline_number') +endfunc + func Test_diff_with_cursorline_breakindent() CheckScreendump @@ -1146,6 +1172,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 @@ -1240,4 +1295,41 @@ func Test_diff_filler_cursorcolumn() endfunc +func Test_diff_binary() + CheckScreendump + + let content =<< trim END + call setline(1, ['a', 'b', "c\n", 'd', 'e', 'f', 'g']) + vnew + call setline(1, ['A', 'b', 'c', 'd', 'E', 'f', 'g']) + windo diffthis + wincmd p + norm! gg0 + redraw! + END + call writefile(content, 'Xtest_diff_bin') + let buf = RunVimInTerminal('-S Xtest_diff_bin', {}) + + " Test using internal diff + call VerifyScreenDump(buf, 'Test_diff_bin_01', {}) + + " Test using internal diff and case folding + call term_sendkeys(buf, ":set diffopt+=icase\<cr>") + call term_sendkeys(buf, "\<C-l>") + call VerifyScreenDump(buf, 'Test_diff_bin_02', {}) + " Test using external diff + call term_sendkeys(buf, ":set diffopt=filler\<cr>") + call term_sendkeys(buf, "\<C-l>") + call VerifyScreenDump(buf, 'Test_diff_bin_03', {}) + " Test using external diff and case folding + call term_sendkeys(buf, ":set diffopt=filler,icase\<cr>") + call term_sendkeys(buf, "\<C-l>") + call VerifyScreenDump(buf, 'Test_diff_bin_04', {}) + + " clean up + call StopVimInTerminal(buf) + call delete('Xtest_diff_bin') + set diffopt&vim +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_digraph.vim b/src/nvim/testdir/test_digraph.vim index d23748a3e3..7b6bc940d3 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 @@ -212,7 +212,7 @@ func Test_digraphs() call Put_Dig("el") call assert_equal(['␀', 'ü', '∞', 'l'], getline(line('.')-3,line('.'))) call assert_fails('digraph xy z', 'E39:') - call assert_fails('digraph x', 'E474:') + call assert_fails('digraph x', 'E1214:') bw! endfunc @@ -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 @@ -505,4 +505,85 @@ func Test_entering_digraph() call StopVimInTerminal(buf) endfunc +func Test_digraph_set_function() + new + call digraph_set('aa', 'あ') + call Put_Dig('aa') + call assert_equal('あ', getline('$')) + call digraph_set(' i', 'い') + call Put_Dig(' i') + call assert_equal('い', getline('$')) + call digraph_set(' ', 'う') + call Put_Dig(' ') + call assert_equal('う', getline('$')) + + eval 'aa'->digraph_set('え') + call Put_Dig('aa') + call assert_equal('え', getline('$')) + + call assert_fails('call digraph_set("aaa", "あ")', 'E1214: Digraph must be just two characters: aaa') + call assert_fails('call digraph_set("b", "あ")', 'E1214: Digraph must be just two characters: b') + call assert_fails('call digraph_set("あ", "あ")', 'E1214: Digraph must be just two characters: あ') + call assert_fails('call digraph_set("aa", "ああ")', 'E1215: Digraph must be one character: ああ') + call assert_fails('call digraph_set("aa", "か" .. nr2char(0x3099))', 'E1215: Digraph must be one character: か' .. nr2char(0x3099)) + bwipe! +endfunc + +func Test_digraph_get_function() + " Built-in digraphs + call assert_equal('∞', digraph_get('00')) + + " User-defined digraphs + call digraph_set('aa', 'あ') + call digraph_set(' i', 'い') + call digraph_set(' ', 'う') + call assert_equal('あ', digraph_get('aa')) + call assert_equal('あ', 'aa'->digraph_get()) + call assert_equal('い', digraph_get(' i')) + call assert_equal('う', digraph_get(' ')) + call assert_fails('call digraph_get("aaa")', 'E1214: Digraph must be just two characters: aaa') + call assert_fails('call digraph_get("b")', 'E1214: Digraph must be just two characters: b') +endfunc + +func Test_digraph_get_function_encode() + throw 'Skipped: Nvim does not support setting encoding=japan' + CheckFeature iconv + + let testcases = { + \'00': '∞', + \'aa': 'あ', + \} + for [key, ch] in items(testcases) + call digraph_set(key, ch) + set encoding=japan + call assert_equal(iconv(ch, 'utf-8', 'japan'), digraph_get(key)) + set encoding=utf-8 + endfor +endfunc + +func Test_digraph_setlist_function() + call digraph_setlist([['aa', 'き'], ['bb', 'く']]) + call assert_equal('き', digraph_get('aa')) + call assert_equal('く', digraph_get('bb')) + + call assert_fails('call digraph_setlist([[]])', 'E1216:') + call assert_fails('call digraph_setlist([["aa", "b", "cc"]])', '1216:') + call assert_fails('call digraph_setlist([["あ", "あ"]])', 'E1214: Digraph must be just two characters: あ') +endfunc + +func Test_digraph_getlist_function() + " Make sure user-defined digraphs are defined + call digraph_setlist([['aa', 'き'], ['bb', 'く']]) + + for pair in digraph_getlist(1) + call assert_equal(digraph_get(pair[0]), pair[1]) + endfor + + " We don't know how many digraphs are registered before, so check the number + " of digraphs returned. + call assert_equal(digraph_getlist()->len(), digraph_getlist(0)->len()) + call assert_notequal((digraph_getlist()->len()), digraph_getlist(1)->len()) +endfunc + + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_display.vim b/src/nvim/testdir/test_display.vim index 12327f34d6..6938abbc28 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) @@ -263,6 +263,52 @@ func Test_display_scroll_at_topline() call StopVimInTerminal(buf) endfunc +func Test_display_scroll_update_visual() + CheckScreendump + + let lines =<< trim END + set scrolloff=0 + call setline(1, repeat(['foo'], 10)) + call sign_define('foo', { 'text': '>' }) + call sign_place(1, 'bar', 'foo', bufnr(), { 'lnum': 2 }) + call sign_place(2, 'bar', 'foo', bufnr(), { 'lnum': 1 }) + autocmd CursorMoved * if getcurpos()[1] == 2 | call sign_unplace('bar', { 'id': 1 }) | endif + END + call writefile(lines, 'XupdateVisual.vim') + + let buf = RunVimInTerminal('-S XupdateVisual.vim', #{rows: 8, cols: 60}) + call term_sendkeys(buf, "VG7kk") + call VerifyScreenDump(buf, 'Test_display_scroll_update_visual', {}) + + call StopVimInTerminal(buf) + call delete('XupdateVisual.vim') +endfunc + +" Test for 'eob' (EndOfBuffer) item in 'fillchars' +func Test_eob_fillchars() + " default value (skipped) + " call assert_match('eob:\~', &fillchars) + " invalid values + call assert_fails(':set fillchars=eob:', 'E474:') + call assert_fails(':set fillchars=eob:xy', 'E474:') + call assert_fails(':set fillchars=eob:\255', 'E474:') + call assert_fails(':set fillchars=eob:<ff>', 'E474:') + call assert_fails(":set fillchars=eob:\x01", 'E474:') + call assert_fails(':set fillchars=eob:\\x01', 'E474:') + " default is ~ + new + redraw + call assert_equal('~', Screenline(2)) + set fillchars=eob:+ + redraw + call assert_equal('+', Screenline(2)) + set fillchars=eob:\ + redraw + call assert_equal(' ', nr2char(screenchar(2, 1))) + set fillchars& + close +endfunc + func Test_display_linebreak_breakat() new vert resize 25 @@ -279,4 +325,31 @@ func Test_display_linebreak_breakat() let &breakat=_breakat endfunc +func Test_display_lastline() + CheckScreendump + + let lines =<< trim END + call setline(1, ['aaa', 'b'->repeat(100)]) + set display=truncate + vsplit + 100wincmd < + END + call writefile(lines, 'XdispLastline') + let buf = RunVimInTerminal('-S XdispLastline', #{rows: 10}) + call VerifyScreenDump(buf, 'Test_display_lastline_1', {}) + + call term_sendkeys(buf, ":set display=lastline\<CR>") + call VerifyScreenDump(buf, 'Test_display_lastline_2', {}) + + call term_sendkeys(buf, ":100wincmd >\<CR>") + call VerifyScreenDump(buf, 'Test_display_lastline_3', {}) + + call term_sendkeys(buf, ":set display=truncate\<CR>") + call VerifyScreenDump(buf, 'Test_display_lastline_4', {}) + + call StopVimInTerminal(buf) + call delete('XdispLastline') +endfunc + + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_edit.vim b/src/nvim/testdir/test_edit.vim index 23ad8dbfc5..eea5d190b2 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 @@ -1294,6 +1303,7 @@ func Test_edit_forbidden() call assert_fails(':Sandbox', 'E48:') delcom Sandbox call assert_equal(['a'], getline(1,'$')) + " 2) edit with textlock set fu! DoIt() call feedkeys("i\<del>\<esc>", 'tnix') @@ -1313,6 +1323,7 @@ func Test_edit_forbidden() catch /^Vim\%((\a\+)\)\=:E117/ " catch E117: unknown function endtry au! InsertCharPre + " 3) edit when completion is shown fun! Complete(findstart, base) if a:findstart @@ -1330,6 +1341,7 @@ func Test_edit_forbidden() endtry delfu Complete set completefunc= + if has("rightleft") && exists("+fkmap") " 4) 'R' when 'fkmap' and 'revins' is set. set revins fkmap @@ -1547,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)) @@ -1619,6 +1627,29 @@ func Test_edit_is_a_directory() call delete(dirname, 'rf') endfunc +" Using :edit without leaving 'insertmode' should not cause Insert mode to be +" re-entered immediately after <C-L> +func Test_edit_insertmode_ex_edit() + CheckRunVimInTerminal + + let lines =<< trim END + set insertmode noruler + inoremap <C-B> <Cmd>edit Xfoo<CR> + END + call writefile(lines, 'Xtest_edit_insertmode_ex_edit') + + let buf = RunVimInTerminal('-S Xtest_edit_insertmode_ex_edit', #{rows: 6}) + call TermWait(buf, 50) + call assert_match('^-- INSERT --\s*$', term_getline(buf, 6)) + call term_sendkeys(buf, "\<C-B>\<C-L>") + call TermWait(buf, 50) + call assert_notmatch('^-- INSERT --\s*$', term_getline(buf, 6)) + + " clean up + call StopVimInTerminal(buf) + call delete('Xtest_edit_insertmode_ex_edit') +endfunc + func Test_edit_browse() " in the GUI this opens a file picker, we only test the terminal behavior CheckNotGui @@ -1644,4 +1675,95 @@ func Test_read_invalid() set encoding=utf-8 endfunc +" Test for ModeChanged pattern +func Test_mode_changes() + let g:index = 0 + let g:mode_seq = ['n', 'i', 'n', 'v', 'V', 'i', 'ix', 'i', 'ic', 'i', 'n', 'no', 'n', 'V', 'v', 's', 'n'] + func! TestMode() + call assert_equal(g:mode_seq[g:index], get(v:event, "old_mode")) + call assert_equal(g:mode_seq[g:index + 1], get(v:event, "new_mode")) + call assert_equal(mode(1), get(v:event, "new_mode")) + let g:index += 1 + endfunc + + au ModeChanged * :call TestMode() + let g:n_to_any = 0 + au ModeChanged n:* let g:n_to_any += 1 + call feedkeys("i\<esc>vVca\<CR>\<C-X>\<C-L>\<esc>ggdG", 'tnix') + + let g:V_to_v = 0 + au ModeChanged V:v let g:V_to_v += 1 + call feedkeys("Vv\<C-G>\<esc>", 'tnix') + call assert_equal(len(filter(g:mode_seq[1:], {idx, val -> val == 'n'})), g:n_to_any) + call assert_equal(1, g:V_to_v) + call assert_equal(len(g:mode_seq) - 1, g:index) + + let g:n_to_i = 0 + au ModeChanged n:i let g:n_to_i += 1 + let g:n_to_niI = 0 + au ModeChanged i:niI let g:n_to_niI += 1 + let g:niI_to_i = 0 + au ModeChanged niI:i let g:niI_to_i += 1 + let g:nany_to_i = 0 + au ModeChanged n*:i let g:nany_to_i += 1 + let g:i_to_n = 0 + au ModeChanged i:n let g:i_to_n += 1 + let g:nori_to_any = 0 + au ModeChanged [ni]:* let g:nori_to_any += 1 + let g:i_to_any = 0 + au ModeChanged i:* let g:i_to_any += 1 + let g:index = 0 + let g:mode_seq = ['n', 'i', 'niI', 'i', 'n'] + call feedkeys("a\<C-O>l\<esc>", 'tnix') + call assert_equal(len(g:mode_seq) - 1, g:index) + call assert_equal(1, g:n_to_i) + call assert_equal(1, g:n_to_niI) + call assert_equal(1, g:niI_to_i) + call assert_equal(2, g:nany_to_i) + call assert_equal(1, g:i_to_n) + call assert_equal(2, g:i_to_any) + call assert_equal(3, g:nori_to_any) + + if has('terminal') + let g:mode_seq += ['c', 'n', 't', 'nt', 'c', 'nt', 'n'] + call feedkeys(":term\<CR>\<C-W>N:bd!\<CR>", 'tnix') + call assert_equal(len(g:mode_seq) - 1, g:index) + call assert_equal(1, g:n_to_i) + call assert_equal(1, g:n_to_niI) + call assert_equal(1, g:niI_to_i) + call assert_equal(2, g:nany_to_i) + call assert_equal(1, g:i_to_n) + call assert_equal(2, g:i_to_any) + call assert_equal(5, g:nori_to_any) + endif + + au! ModeChanged + delfunc TestMode + unlet! g:mode_seq + unlet! g:index + unlet! g:n_to_any + unlet! g:V_to_v + unlet! g:n_to_i + unlet! g:n_to_niI + unlet! g:niI_to_i + unlet! g:nany_to_i + unlet! g:i_to_n + unlet! g:nori_to_any + unlet! g:i_to_any +endfunc + +func Test_recursive_ModeChanged() + au! ModeChanged * norm 0u + sil! norm + au! +endfunc + +func Test_ModeChanged_starts_visual() + " This was triggering ModeChanged before setting VIsual, causing a crash. + au! ModeChanged * norm 0u + sil! norm + + au! ModeChanged +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_environ.vim b/src/nvim/testdir/test_environ.vim index cc15b63824..dd34983ee5 100644 --- a/src/nvim/testdir/test_environ.vim +++ b/src/nvim/testdir/test_environ.vim @@ -22,7 +22,7 @@ endfunc func Test_setenv() unlet! $TESTENV - call setenv('TEST ENV', 'foo') + eval 'foo'->setenv('TEST ENV') call assert_equal('foo', getenv('TEST ENV')) call setenv('TEST ENV', v:null) call assert_equal(v:null, getenv('TEST ENV')) diff --git a/src/nvim/testdir/test_eval_stuff.vim b/src/nvim/testdir/test_eval_stuff.vim index 883ba5de3d..12febfeb93 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 @@ -187,7 +185,7 @@ func Test_let_register() call Assert_reg('"', 'v', "abc", "['abc']", "abc", "['abc']") let @" = "abc\n" call Assert_reg('"', 'V', "abc\n", "['abc']", "abc\n", "['abc']") - let @" = "abc\<C-m>" + let @" = "abc\r" call Assert_reg('"', 'V', "abc\r\n", "['abc\r']", "abc\r\n", "['abc\r']") let @= = '"abc"' call Assert_reg('=', 'v', "abc", "['abc']", '"abc"', "['\"abc\"']") diff --git a/src/nvim/testdir/test_ex_mode.vim b/src/nvim/testdir/test_ex_mode.vim index 1c645ad0f8..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) @@ -85,7 +84,7 @@ endfunc func Test_ex_mode_count_overflow() " this used to cause a crash let lines =<< trim END - call feedkeys("\<Esc>Q\<CR>") + call feedkeys("\<Esc>gQ\<CR>") v9|9silent! vi|333333233333y32333333%O call writefile(['done'], 'Xdidexmode') qall! @@ -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 f2c7da0aa9..16cc20e9a7 100644 --- a/src/nvim/testdir/test_execute_func.vim +++ b/src/nvim/testdir/test_execute_func.vim @@ -99,7 +99,7 @@ func Test_win_execute() if has('textprop') let popupwin = popup_create('the popup win', {'line': 2, 'col': 3}) redraw - let line = win_execute(popupwin, 'echo getline(1)') + let line = 'echo getline(1)'->win_execute(popupwin) call assert_match('the popup win', line) call popup_close(popupwin) @@ -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_exit.vim b/src/nvim/testdir/test_exit.vim index bd3e9eb4d4..befcaec2b2 100644 --- a/src/nvim/testdir/test_exit.vim +++ b/src/nvim/testdir/test_exit.vim @@ -1,6 +1,7 @@ " Tests for exiting Vim. source shared.vim +source check.vim func Test_exiting() let after =<< trim [CODE] @@ -109,4 +110,25 @@ func Test_exit_code() call delete('Xtestout') endfunc +func Test_exit_error_reading_input() + throw 'Skipped: Nvim does not exit after stdin is read' + + CheckNotGui + CheckNotMSWindows + " The early exit causes memory not to be freed somehow + CheckNotAsan + + call writefile([":au VimLeave * call writefile(['l = ' .. v:exiting], 'Xtestout')", ":tabnew", "q:"], 'Xscript', 'b') + + " Nvim requires "-s -" to read stdin as Normal mode input + " if RunVim([], [], '<Xscript') + if RunVim([], [], '-s - <Xscript') + call assert_equal(1, v:shell_error) + call assert_equal(['l = 1'], readfile('Xtestout')) + endif + call delete('Xscript') + call delete('Xtestout') +endfun + + " 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 6343c47fde..5b10e691e5 100644 --- a/src/nvim/testdir/test_expr.vim +++ b/src/nvim/testdir/test_expr.vim @@ -56,7 +56,7 @@ endfunc func Test_strgetchar() call assert_equal(char2nr('a'), strgetchar('axb', 0)) - call assert_equal(char2nr('x'), strgetchar('axb', 1)) + call assert_equal(char2nr('x'), 'axb'->strgetchar(1)) call assert_equal(char2nr('b'), strgetchar('axb', 2)) call assert_equal(-1, strgetchar('axb', -1)) @@ -66,7 +66,7 @@ endfunc func Test_strcharpart() call assert_equal('a', strcharpart('axb', 0, 1)) - call assert_equal('x', strcharpart('axb', 1, 1)) + call assert_equal('x', 'axb'->strcharpart(1, 1)) call assert_equal('b', strcharpart('axb', 2, 1)) call assert_equal('xb', strcharpart('axb', 1)) @@ -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 @@ -493,7 +558,7 @@ func Test_setmatches() let set[0]['conceal'] = 5 let exp[0]['conceal'] = '5' endif - call setmatches(set) + eval set->setmatches() call assert_equal(exp, getmatches()) 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_file_perm.vim b/src/nvim/testdir/test_file_perm.vim new file mode 100644 index 0000000000..1cb09e8647 --- /dev/null +++ b/src/nvim/testdir/test_file_perm.vim @@ -0,0 +1,30 @@ +" Test getting and setting file permissions. + +func Test_file_perm() + call assert_equal('', getfperm('Xtest')) + call assert_equal(0, 'Xtest'->setfperm('r--------')) + + call writefile(['one'], 'Xtest') + call assert_true(len('Xtest'->getfperm()) == 9) + + call assert_equal(1, setfperm('Xtest', 'rwx------')) + if has('win32') + call assert_equal('rw-rw-rw-', getfperm('Xtest')) + else + call assert_equal('rwx------', getfperm('Xtest')) + endif + + call assert_equal(1, setfperm('Xtest', 'r--r--r--')) + call assert_equal('r--r--r--', getfperm('Xtest')) + + call assert_fails("setfperm('Xtest', '---')") + + call assert_equal(1, setfperm('Xtest', 'rwx------')) + call delete('Xtest') + + call assert_fails("call setfperm(['Xfile'], 'rw-rw-rw-')", 'E730:') + call assert_fails("call setfperm('Xfile', [])", 'E730:') + call assert_fails("call setfperm('Xfile', 'rwxrwxrwxrw')", 'E475:') +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_filechanged.vim b/src/nvim/testdir/test_filechanged.vim new file mode 100644 index 0000000000..8e1cb2c3ee --- /dev/null +++ b/src/nvim/testdir/test_filechanged.vim @@ -0,0 +1,247 @@ +" Tests for when a file was changed outside of Vim. + +source check.vim + +func Test_FileChangedShell_reload() + CheckUnix + + augroup testreload + au FileChangedShell Xchanged_r let g:reason = v:fcs_reason | let v:fcs_choice = 'reload' + augroup END + new Xchanged_r + 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_r + checktime + call assert_equal('changed', g:reason) + call assert_equal(2, line('$')) + call assert_equal('extra line', getline(2)) + + " Only triggers once + let g:reason = '' + checktime + call assert_equal('', g:reason) + + " When deleted buffer is not reloaded + silent !rm Xchanged_r + let g:reason = '' + checktime + call assert_equal('deleted', g:reason) + call assert_equal(2, line('$')) + call assert_equal('extra line', getline(2)) + + " When recreated buffer is reloaded + call setline(1, 'buffer is changed') + silent !echo 'new line' >>Xchanged_r + let g:reason = '' + checktime + call assert_equal('conflict', g:reason) + call assert_equal(1, line('$')) + call assert_equal('new line', getline(1)) + + " Only mode changed + silent !chmod +x Xchanged_r + let g:reason = '' + checktime + call assert_equal('mode', g:reason) + call assert_equal(1, line('$')) + call assert_equal('new line', getline(1)) + + " Only time changed + sleep 2 + silent !touch Xchanged_r + let g:reason = '' + checktime + call assert_equal('time', g:reason) + call assert_equal(1, line('$')) + call assert_equal('new line', getline(1)) + + if has('persistent_undo') + " With an undo file the reload can be undone and a change before the + " reload. + set undofile + call setline(2, 'before write') + write + call setline(2, 'after write') + sleep 2 + silent !echo 'different line' >>Xchanged_r + let g:reason = '' + checktime + call assert_equal('conflict', g:reason) + call assert_equal(3, line('$')) + call assert_equal('before write', getline(2)) + call assert_equal('different line', getline(3)) + " undo the reload + undo + call assert_equal(2, line('$')) + call assert_equal('after write', getline(2)) + " undo the change before reload + undo + call assert_equal(2, line('$')) + call assert_equal('before write', getline(2)) + + set noundofile + endif + + au! testreload + bwipe! + call delete(undofile('Xchanged_r')) + 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 + CheckUnix " Using low level feedkeys() does not work on MS-Windows. + + 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: requires a UI to be active' + CheckUnix + CheckNotGui + 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 feedkeys('L', '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 feedkeys('L', '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 feedkeys('L', '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 diff --git a/src/nvim/testdir/test_fileformat.vim b/src/nvim/testdir/test_fileformat.vim index 465613f1cf..81127ea59a 100644 --- a/src/nvim/testdir/test_fileformat.vim +++ b/src/nvim/testdir/test_fileformat.vim @@ -1,5 +1,4 @@ " Test behavior of fileformat after bwipeout of last buffer - func Test_fileformat_after_bw() bwipeout set fileformat& @@ -32,6 +31,251 @@ func Test_fileformat_autocommand() bw! endfunc +" Convert the contents of a file into a literal string +func s:file2str(fname) + let b = readfile(a:fname, 'B') + let s = '' + for c in b + let s .= nr2char(c) + endfor + return s +endfunc + +" Concatenate the contents of files 'f1' and 'f2' and create 'destfile' +func s:concat_files(f1, f2, destfile) + let b1 = readfile(a:f1, 'B') + let b2 = readfile(a:f2, 'B') + let b3 = b1 + b2 + call writefile(b3, a:destfile) +endfun + +" Test for a lot of variations of the 'fileformats' option +func Test_fileformats() + " create three test files, one in each format + call writefile(['unix', 'unix'], 'XXUnix') + call writefile(["dos\r", "dos\r"], 'XXDos') + call writefile(["mac\rmac\r"], 'XXMac', 'b') + " create a file with no End Of Line + call writefile(["noeol"], 'XXEol', 'b') + " create mixed format files + call s:concat_files('XXUnix', 'XXDos', 'XXUxDs') + call s:concat_files('XXUnix', 'XXMac', 'XXUxMac') + call s:concat_files('XXDos', 'XXMac', 'XXDosMac') + call s:concat_files('XXMac', 'XXEol', 'XXMacEol') + call s:concat_files('XXUxDs', 'XXMac', 'XXUxDsMc') + + new + + " Test 1: try reading and writing with 'fileformats' empty + set fileformats= + + " try with 'fileformat' set to 'unix' + set fileformat=unix + e! XXUnix + w! Xtest + call assert_equal("unix\nunix\n", s:file2str('Xtest')) + e! XXDos + w! Xtest + call assert_equal("dos\r\ndos\r\n", s:file2str('Xtest')) + e! XXMac + w! Xtest + call assert_equal("mac\rmac\r\n", s:file2str('Xtest')) + bwipe XXUnix XXDos XXMac + + " try with 'fileformat' set to 'dos' + set fileformat=dos + e! XXUnix + w! Xtest + call assert_equal("unix\r\nunix\r\n", s:file2str('Xtest')) + e! XXDos + w! Xtest + call assert_equal("dos\r\ndos\r\n", s:file2str('Xtest')) + e! XXMac + w! Xtest + call assert_equal("mac\rmac\r\r\n", s:file2str('Xtest')) + bwipe XXUnix XXDos XXMac + + " try with 'fileformat' set to 'mac' + set fileformat=mac + e! XXUnix + w! Xtest + call assert_equal("unix\nunix\n\r", s:file2str('Xtest')) + e! XXDos + w! Xtest + call assert_equal("dos\r\ndos\r\n\r", s:file2str('Xtest')) + e! XXMac + w! Xtest + call assert_equal("mac\rmac\r", s:file2str('Xtest')) + bwipe XXUnix XXDos XXMac + + " Test 2: try reading and writing with 'fileformats' set to one format + + " try with 'fileformats' set to 'unix' + set fileformats=unix + e! XXUxDsMc + w! Xtest + call assert_equal("unix\nunix\ndos\r\ndos\r\nmac\rmac\r\n", + \ s:file2str('Xtest')) + bwipe XXUxDsMc + + " try with 'fileformats' set to 'dos' + set fileformats=dos + e! XXUxDsMc + w! Xtest + call assert_equal("unix\r\nunix\r\ndos\r\ndos\r\nmac\rmac\r\r\n", + \ s:file2str('Xtest')) + bwipe XXUxDsMc + + " try with 'fileformats' set to 'mac' + set fileformats=mac + e! XXUxDsMc + w! Xtest + call assert_equal("unix\nunix\ndos\r\ndos\r\nmac\rmac\r", + \ s:file2str('Xtest')) + bwipe XXUxDsMc + + " Test 3: try reading and writing with 'fileformats' set to two formats + + " try with 'fileformats' set to 'unix,dos' + set fileformats=unix,dos + e! XXUxDsMc + w! Xtest + call assert_equal("unix\nunix\ndos\r\ndos\r\nmac\rmac\r\n", + \ s:file2str('Xtest')) + bwipe XXUxDsMc + + e! XXUxMac + w! Xtest + call assert_equal("unix\nunix\nmac\rmac\r\n", s:file2str('Xtest')) + bwipe XXUxMac + + e! XXDosMac + w! Xtest + call assert_equal("dos\r\ndos\r\nmac\rmac\r\r\n", s:file2str('Xtest')) + bwipe XXDosMac + + " try with 'fileformats' set to 'unix,mac' + set fileformats=unix,mac + e! XXUxDs + w! Xtest + call assert_equal("unix\nunix\ndos\r\ndos\r\n", s:file2str('Xtest')) + bwipe XXUxDs + + e! XXUxDsMc + w! Xtest + call assert_equal("unix\nunix\ndos\r\ndos\r\nmac\rmac\r\n", + \ s:file2str('Xtest')) + bwipe XXUxDsMc + + e! XXDosMac + w! Xtest + call assert_equal("dos\r\ndos\r\nmac\rmac\r", s:file2str('Xtest')) + bwipe XXDosMac + + e! XXEol + exe "normal ggO\<C-R>=&ffs\<CR>:\<C-R>=&ff\<CR>" + w! Xtest + call assert_equal("unix,mac:unix\nnoeol\n", s:file2str('Xtest')) + bwipe! XXEol + + " try with 'fileformats' set to 'dos,mac' + set fileformats=dos,mac + e! XXUxDs + w! Xtest + call assert_equal("unix\r\nunix\r\ndos\r\ndos\r\n", s:file2str('Xtest')) + bwipe XXUxDs + + e! XXUxMac + exe "normal ggO\<C-R>=&ffs\<CR>:\<C-R>=&ff\<CR>" + w! Xtest + call assert_equal("dos,mac:dos\r\nunix\r\nunix\r\nmac\rmac\r\r\n", + \ s:file2str('Xtest')) + bwipe! XXUxMac + + e! XXUxDsMc + w! Xtest + call assert_equal("unix\r\nunix\r\ndos\r\ndos\r\nmac\rmac\r\r\n", + \ s:file2str('Xtest')) + bwipe XXUxDsMc + + e! XXMacEol + exe "normal ggO\<C-R>=&ffs\<CR>:\<C-R>=&ff\<CR>" + w! Xtest + call assert_equal("dos,mac:mac\rmac\rmac\rnoeol\r", s:file2str('Xtest')) + bwipe! XXMacEol + + " Test 4: try reading and writing with 'fileformats' set to three formats + set fileformats=unix,dos,mac + e! XXUxDsMc + w! Xtest + call assert_equal("unix\nunix\ndos\r\ndos\r\nmac\rmac\r\n", + \ s:file2str('Xtest')) + bwipe XXUxDsMc + + e! XXEol + exe "normal ggO\<C-R>=&ffs\<CR>:\<C-R>=&ff\<CR>" + w! Xtest + call assert_equal("unix,dos,mac:unix\nnoeol\n", s:file2str('Xtest')) + bwipe! XXEol + + set fileformats=mac,dos,unix + e! XXUxDsMc + w! Xtest + call assert_equal("unix\nunix\ndos\r\ndos\r\nmac\rmac\r\n", + \ s:file2str('Xtest')) + bwipe XXUxDsMc + + e! XXEol + exe "normal ggO\<C-R>=&ffs\<CR>:\<C-R>=&ff\<CR>" + w! Xtest + call assert_equal("mac,dos,unix:mac\rnoeol\r", s:file2str('Xtest')) + bwipe! XXEol + + " Test 5: try with 'binary' set + set fileformats=mac,unix,dos + set binary + e! XXUxDsMc + w! Xtest + call assert_equal("unix\nunix\ndos\r\ndos\r\nmac\rmac\r", + \ s:file2str('Xtest')) + bwipe XXUxDsMc + + set fileformats=mac + e! XXUxDsMc + w! Xtest + call assert_equal("unix\nunix\ndos\r\ndos\r\nmac\rmac\r", + \ s:file2str('Xtest')) + bwipe XXUxDsMc + + set fileformats=dos + e! XXUxDsMc + w! Xtest + call assert_equal("unix\nunix\ndos\r\ndos\r\nmac\rmac\r", + \ s:file2str('Xtest')) + bwipe XXUxDsMc + + e! XXUnix + w! Xtest + call assert_equal("unix\nunix\n", s:file2str('Xtest')) + bwipe! XXUnix + + set nobinary ff& ffs& + + " cleanup + only + %bwipe! + call delete('XXUnix') + call delete('XXDos') + call delete('XXMac') + call delete('XXEol') + call delete('XXUxDs') + call delete('XXUxMac') + call delete('XXDosMac') + call delete('XXMacEol') + call delete('XXUxDsMc') + call delete('Xtest') +endfunc + " Test for changing the fileformat using ++read func Test_fileformat_plusplus_read() new diff --git a/src/nvim/testdir/test_filetype.vim b/src/nvim/testdir/test_filetype.vim index 9651b8dce0..197a9edb76 100644 --- a/src/nvim/testdir/test_filetype.vim +++ b/src/nvim/testdir/test_filetype.vim @@ -59,7 +59,7 @@ let s:filename_checks = { \ 'aml': ['file.aml'], \ 'ampl': ['file.run'], \ 'ant': ['build.xml'], - \ 'apache': ['.htaccess', '/etc/httpd/file.conf', '/etc/apache2/sites-2/file.com', '/etc/apache2/some.config', '/etc/apache2/conf.file/conf', '/etc/apache2/mods-some/file', '/etc/apache2/sites-some/file', '/etc/httpd/conf.d/file.config', '/etc/apache2/conf.file/file', '/etc/apache2/file.conf', '/etc/apache2/file.conf-file', '/etc/apache2/mods-file/file', '/etc/apache2/sites-file/file', '/etc/apache2/sites-file/file.com', '/etc/httpd/conf.d/file.conf', '/etc/httpd/conf.d/file.conf-file', 'access.conf', 'access.conf-file', 'any/etc/apache2/conf.file/file', 'any/etc/apache2/file.conf', 'any/etc/apache2/file.conf-file', 'any/etc/apache2/mods-file/file', 'any/etc/apache2/sites-file/file', 'any/etc/apache2/sites-file/file.com', 'any/etc/httpd/conf.d/file.conf', 'any/etc/httpd/conf.d/file.conf-file', 'any/etc/httpd/file.conf', 'apache.conf', 'apache.conf-file', 'apache2.conf', 'apache2.conf-file', 'httpd.conf', 'httpd.conf-file', 'srm.conf', 'srm.conf-file'], + \ 'apache': ['.htaccess', '/etc/httpd/file.conf', '/etc/apache2/sites-2/file.com', '/etc/apache2/some.config', '/etc/apache2/conf.file/conf', '/etc/apache2/mods-some/file', '/etc/apache2/sites-some/file', '/etc/httpd/conf.d/file.config', '/etc/apache2/conf.file/file', '/etc/apache2/file.conf', '/etc/apache2/file.conf-file', '/etc/apache2/mods-file/file', '/etc/apache2/sites-file/file', '/etc/apache2/sites-file/file.com', '/etc/httpd/conf.d/file.conf', '/etc/httpd/conf.d/file.conf-file', 'access.conf', 'access.conf-file', 'any/etc/apache2/conf.file/file', 'any/etc/apache2/file.conf', 'any/etc/apache2/file.conf-file', 'any/etc/apache2/mods-file/file', 'any/etc/apache2/sites-file/file', 'any/etc/apache2/sites-file/file.com', 'any/etc/httpd/conf.d/file.conf', 'any/etc/httpd/conf.d/file.conf-file', 'any/etc/httpd/file.conf', 'apache.conf', 'apache.conf-file', 'apache2.conf', 'apache2.conf-file', 'httpd.conf', 'httpd.conf-file', 'srm.conf', 'srm.conf-file', '/etc/httpd/mods-some/file', '/etc/httpd/sites-some/file', '/etc/httpd/conf.file/conf'], \ 'apachestyle': ['/etc/proftpd/file.config,/etc/proftpd/conf.file/file', '/etc/proftpd/conf.file/file', '/etc/proftpd/file.conf', '/etc/proftpd/file.conf-file', 'any/etc/proftpd/conf.file/file', 'any/etc/proftpd/file.conf', 'any/etc/proftpd/file.conf-file', 'proftpd.conf', 'proftpd.conf-file'], \ 'applescript': ['file.scpt'], \ 'aptconf': ['apt.conf', '/.aptitude/config', 'any/.aptitude/config'], @@ -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'], @@ -97,7 +99,7 @@ let s:filename_checks = { \ 'cdrtoc': ['file.toc'], \ 'cf': ['file.cfm', 'file.cfi', 'file.cfc'], \ 'cfengine': ['cfengine.conf'], - \ 'cfg': ['file.cfg', 'file.hgrc', 'filehgrc', 'hgrc', 'some-hgrc'], + \ 'cfg': ['file.hgrc', 'filehgrc', 'hgrc', 'some-hgrc'], \ 'ch': ['file.chf'], \ 'chaiscript': ['file.chai'], \ 'chaskell': ['file.chs'], @@ -114,10 +116,11 @@ let s:filename_checks = { \ 'conf': ['auto.master'], \ 'config': ['configure.in', 'configure.ac', '/etc/hostname.file'], \ 'context': ['tex/context/any/file.tex', 'file.mkii', 'file.mkiv', 'file.mkvi', 'file.mkxl', 'file.mklx'], + \ 'cook': ['file.cook'], \ 'cpp': ['file.cxx', 'file.c++', 'file.hh', 'file.hxx', 'file.hpp', 'file.ipp', 'file.moc', 'file.tcc', 'file.inl', 'file.tlh'], \ 'crm': ['file.crm'], \ 'crontab': ['crontab', 'crontab.file', '/etc/cron.d/file', 'any/etc/cron.d/file'], - \ 'cs': ['file.cs'], + \ 'cs': ['file.cs', 'file.csx'], \ 'csc': ['file.csc'], \ 'csdl': ['file.csdl'], \ 'csp': ['file.csp', 'file.fdr'], @@ -130,6 +133,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'], @@ -142,16 +146,17 @@ let s:filename_checks = { \ 'desc': ['file.desc'], \ 'desktop': ['file.desktop', '.directory', 'file.directory'], \ 'dictconf': ['dict.conf', '.dictrc'], - \ 'dictdconf': ['dictd.conf'], + \ 'dictdconf': ['dictd.conf', 'dictdfile.conf', 'dictd-file.conf'], \ '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'], - \ 'dosbatch': ['file.bat', 'file.sys'], + \ 'dockerfile': ['Containerfile', 'Dockerfile', 'file.Dockerfile', 'Dockerfile.debian', 'Containerfile.something'], + \ 'dosbatch': ['file.bat'], \ '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'], @@ -164,6 +169,7 @@ let s:filename_checks = { \ 'eelixir': ['file.eex', 'file.leex'], \ 'elm': ['file.elm'], \ 'elmfilt': ['filter-rules'], + \ 'elvish': ['file.elv'], \ 'epuppet': ['file.epp'], \ 'erlang': ['file.erl', 'file.hrl', 'file.yaws'], \ 'eruby': ['file.erb', 'file.rhtml'], @@ -182,49 +188,64 @@ let s:filename_checks = { \ 'fgl': ['file.4gl', 'file.4gh', 'file.m4gl'], \ 'fish': ['file.fish'], \ 'focexec': ['file.fex', 'file.focexec'], - \ 'forth': ['file.fs', 'file.ft', 'file.fth'], + \ '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'], + \ 'gdb': ['.gdbinit', 'gdbinit', 'file.gdb', '.config/gdbearlyinit', '.gdbearlyinit'], + \ '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'], \ 'hostsaccess': ['/etc/hosts.allow', '/etc/hosts.deny', 'any/etc/hosts.allow', 'any/etc/hosts.deny'], + \ 'i3config': ['/home/user/.i3/config', '/home/user/.config/i3/config', '/etc/i3/config', '/etc/xdg/i3/config'], \ 'logcheck': ['/etc/logcheck/file.d-some/file', '/etc/logcheck/file.d/file', 'any/etc/logcheck/file.d-some/file', 'any/etc/logcheck/file.d/file'], \ 'modula3': ['file.m3', 'file.mg', 'file.i3', 'file.ig'], \ 'natural': ['file.NSA', 'file.NSC', 'file.NSG', 'file.NSL', 'file.NSM', 'file.NSN', 'file.NSP', 'file.NSS'], @@ -255,12 +276,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'], + \ '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'], @@ -268,12 +291,14 @@ let s:filename_checks = { \ 'kivy': ['file.kv'], \ 'kix': ['file.kix'], \ 'kotlin': ['file.kt', 'file.ktm', 'file.kts'], + \ 'krl': ['file.sub', 'file.Sub', 'file.SUB'], \ 'kscript': ['file.ks'], \ 'kwt': ['file.k'], \ 'lace': ['file.ace', 'file.ACE'], \ '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'], @@ -283,7 +308,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'], @@ -338,7 +363,7 @@ let s:filename_checks = { \ 'msql': ['file.msql'], \ 'mupad': ['file.mu'], \ 'mush': ['file.mush'], - \ 'muttrc': ['Muttngrc', 'Muttrc', '.muttngrc', '.muttngrc-file', '.muttrc', '.muttrc-file', '/.mutt/muttngrc', '/.mutt/muttngrc-file', '/.mutt/muttrc', '/.mutt/muttrc-file', '/.muttng/muttngrc', '/.muttng/muttngrc-file', '/.muttng/muttrc', '/.muttng/muttrc-file', '/etc/Muttrc.d/file', 'Muttngrc-file', 'Muttrc-file', 'any/.mutt/muttngrc', 'any/.mutt/muttngrc-file', 'any/.mutt/muttrc', 'any/.mutt/muttrc-file', 'any/.muttng/muttngrc', 'any/.muttng/muttngrc-file', 'any/.muttng/muttrc', 'any/.muttng/muttrc-file', 'any/etc/Muttrc.d/file', 'muttngrc', 'muttngrc-file', 'muttrc', 'muttrc-file'], + \ 'muttrc': ['Muttngrc', 'Muttrc', '.muttngrc', '.muttngrc-file', '.muttrc', '.muttrc-file', '/.mutt/muttngrc', '/.mutt/muttngrc-file', '/.mutt/muttrc', '/.mutt/muttrc-file', '/.muttng/muttngrc', '/.muttng/muttngrc-file', '/.muttng/muttrc', '/.muttng/muttrc-file', '/etc/Muttrc.d/file', '/etc/Muttrc.d/file.rc', 'Muttngrc-file', 'Muttrc-file', 'any/.mutt/muttngrc', 'any/.mutt/muttngrc-file', 'any/.mutt/muttrc', 'any/.mutt/muttrc-file', 'any/.muttng/muttngrc', 'any/.muttng/muttngrc-file', 'any/.muttng/muttrc', 'any/.muttng/muttrc-file', 'any/etc/Muttrc.d/file', 'muttngrc', 'muttngrc-file', 'muttrc', 'muttrc-file'], \ 'mysql': ['file.mysql'], \ 'n1ql': ['file.n1ql', 'file.nql'], \ 'named': ['namedfile.conf', 'rndcfile.conf', 'named-file.conf', 'named.conf', 'rndc-file.conf', 'rndc-file.key', 'rndc.conf', 'rndc.key'], @@ -347,6 +372,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'], @@ -358,6 +384,7 @@ let s:filename_checks = { \ 'opam': ['opam', 'file.opam', 'file.opam.template'], \ 'openroad': ['file.or'], \ 'ora': ['file.ora'], + \ 'org': ['file.org', 'file.org_archive'], \ 'pamconf': ['/etc/pam.conf', '/etc/pam.d/file', 'any/etc/pam.conf', 'any/etc/pam.d/file'], \ 'pamenv': ['/etc/security/pam_env.conf', '/home/user/.pam_environment', '.pam_environment', 'pam_env.conf'], \ 'papp': ['file.papp', 'file.pxml', 'file.pxsl'], @@ -369,7 +396,7 @@ let s:filename_checks = { \ 'perl': ['file.plx', 'file.al', 'file.psgi', 'gitolite.rc', '.gitolite.rc', 'example.gitolite.rc'], \ 'pf': ['pf.conf'], \ 'pfmain': ['main.cf'], - \ 'php': ['file.php', 'file.php9', 'file.phtml', 'file.ctp'], + \ 'php': ['file.php', 'file.php9', 'file.phtml', 'file.ctp', 'file.phpt'], \ 'lpc': ['file.lpc', 'file.ulpc'], \ 'pike': ['file.pike', 'file.pmod'], \ 'cmod': ['file.cmod'], @@ -389,6 +416,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'], @@ -399,10 +427,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'], @@ -413,6 +443,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'], @@ -425,25 +456,24 @@ let s:filename_checks = { \ 'rpl': ['file.rpl'], \ 'rst': ['file.rst'], \ 'rtf': ['file.rtf'], - \ 'ruby': ['.irbrc', 'irbrc', 'file.rb', 'file.rbw', 'file.gemspec', 'file.ru', 'Gemfile', 'file.builder', 'file.rxml', 'file.rjs', 'file.rant', 'file.rake', 'rakefile', 'Rakefile', 'rantfile', 'Rantfile', 'rakefile-file', 'Rakefile-file', 'Puppetfile'], + \ 'ruby': ['.irbrc', 'irbrc', 'file.rb', 'file.rbw', 'file.gemspec', 'file.ru', 'Gemfile', 'file.builder', 'file.rxml', 'file.rjs', 'file.rant', 'file.rake', 'rakefile', 'Rakefile', 'rantfile', 'Rantfile', 'rakefile-file', 'Rakefile-file', 'Puppetfile', 'Vagrantfile'], \ 'rust': ['file.rs'], \ 'samba': ['smb.conf'], \ 'sas': ['file.sas'], \ 'sass': ['file.sass'], \ 'sather': ['file.sa'], \ 'sbt': ['file.sbt'], - \ 'scala': ['file.scala', 'file.sc'], - \ 'scheme': ['file.scm', 'file.ss', 'file.rkt', 'file.rktd', 'file.rktl'], + \ 'scala': ['file.scala'], + \ 'scheme': ['file.scm', 'file.ss', 'file.sld', 'file.rkt', 'file.rktd', 'file.rktl'], \ 'scilab': ['file.sci', 'file.sce'], \ 'screen': ['.screenrc', 'screenrc'], \ 'sexplib': ['file.sexp'], - \ 'scdoc': ['file.scd'], \ 'scss': ['file.scss'], \ 'sd': ['file.sd'], \ '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'], @@ -454,6 +484,8 @@ 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'], \ 'slpspi': ['/etc/slp.spi', 'any/etc/slp.spi'], @@ -475,13 +507,16 @@ let s:filename_checks = { \ 'sqlj': ['file.sqlj'], \ 'sqr': ['file.sqr', 'file.sqi'], \ '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'], + \ 'supercollider': ['file.quark'], + \ 'surface': ['file.sface'], \ 'svg': ['file.svg'], \ 'svn': ['svn-commitfile.tmp', 'svn-commit-file.tmp', 'svn-commit.tmp'], \ 'swift': ['file.swift'], @@ -495,15 +530,18 @@ 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'], - \ 'text': ['file.text', 'README', '/usr/share/doc/bash-completion/AUTHORS'], + \ 'text': ['file.text', 'file.txt', 'README', 'LICENSE', 'COPYING', 'AUTHORS', '/usr/share/doc/bash-completion/AUTHORS', '/etc/apt/apt.conf.d/README', '/etc/Muttrc.d/README'], \ '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'], @@ -515,6 +553,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'], @@ -529,6 +568,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'], @@ -553,7 +593,7 @@ let s:filename_checks = { \ 'xhtml': ['file.xhtml', 'file.xht'], \ 'xinetd': ['/etc/xinetd.conf', '/etc/xinetd.d/file', 'any/etc/xinetd.conf', 'any/etc/xinetd.d/file'], \ 'xmath': ['file.msc', 'file.msf'], - \ 'xml': ['/etc/blkid.tab', '/etc/blkid.tab.old', 'file.xmi', 'file.csproj', 'file.csproj.user', 'file.ui', 'file.tpm', '/etc/xdg/menus/file.menu', 'fglrxrc', 'file.xlf', 'file.xliff', 'file.xul', 'file.wsdl', 'file.wpl', 'any/etc/blkid.tab', 'any/etc/blkid.tab.old', 'any/etc/xdg/menus/file.menu'], + \ 'xml': ['/etc/blkid.tab', '/etc/blkid.tab.old', 'file.xmi', 'file.csproj', 'file.csproj.user', 'file.fsproj', 'file.fsproj.user', 'file.vbproj', 'file.vbproj.user', 'file.ui', 'file.tpm', '/etc/xdg/menus/file.menu', 'fglrxrc', 'file.xlf', 'file.xliff', 'file.xul', 'file.wsdl', 'file.wpl', 'any/etc/blkid.tab', 'any/etc/blkid.tab.old', 'any/etc/xdg/menus/file.menu', 'file.atom', 'file.rss', 'file.cdxml', 'file.psc1', 'file.mpd'], \ 'xmodmap': ['anyXmodmap', 'Xmodmap', 'some-Xmodmap', 'some-xmodmap', 'some-xmodmap-file', 'xmodmap', 'xmodmap-file'], \ 'xf86conf': ['xorg.conf', 'xorg.conf-4'], \ 'xpm': ['file.xpm'], @@ -564,8 +604,10 @@ 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'], \ 'zimbu': ['file.zu'], \ 'zimbutempl': ['file.zut'], \ 'zsh': ['.zprofile', '/etc/zprofile', '.zfbfmarks', 'file.zsh', '.zcompdump', '.zlogin', '.zlogout', '.zshenv', '.zshrc', '.zcompdump-file', '.zlog', '.zlog-file', '.zsh', '.zsh-file', 'any/etc/zprofile', 'zlog', 'zlog-file', 'zsh', 'zsh-file'], @@ -574,7 +616,7 @@ let s:filename_checks = { \ } let s:filename_case_checks = { - \ 'modula2': ['file.DEF', 'file.MOD'], + \ 'modula2': ['file.DEF'], \ 'bzl': ['file.BUILD', 'BUILD'], \ } @@ -647,7 +689,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']], @@ -663,6 +705,7 @@ let s:script_checks = { \ 'fennel': [['#!/path/fennel']], \ 'routeros': [['#!/path/rsc']], \ 'fish': [['#!/path/fish']], + \ 'forth': [['#!/path/gforth']], \ } " Various forms of "env" optional arguments. @@ -699,83 +742,227 @@ 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') + " FreeBASIC + + call writefile(["/' FreeBASIC multiline comment '/"], 'Xfile.bas') + split Xfile.bas + call assert_equal('freebasic', &filetype) + bwipe! + + call writefile(['#define TESTING'], 'Xfile.bas') + split Xfile.bas + call assert_equal('freebasic', &filetype) + bwipe! + + call writefile(['option byval'], 'Xfile.bas') + split Xfile.bas + call assert_equal('freebasic', &filetype) + bwipe! + + 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_ts_file() +" Test dist#ft#FTcfg() +func Test_cfg_file() filetype on - call writefile(['<?xml version="1.0" encoding="utf-8"?>'], 'Xfile.ts') - split Xfile.ts - call assert_equal('xml', &filetype) + " *.cfg defaults to cfg + call writefile(['looks like cfg'], 'cfgfile.cfg') + split cfgfile.cfg + call assert_equal('cfg', &filetype) + + let g:filetype_cfg = 'other' + edit + call assert_equal('other', &filetype) bwipe! + unlet g:filetype_cfg + + " RAPID cfg + let ext = 'cfg' + for i in ['EIO', 'MMC', 'MOC', 'PROC', 'SIO', 'SYS'] + call writefile([i .. ':CFG'], 'cfgfile.' .. ext) + execute "split cfgfile." .. ext + call assert_equal('rapid', &filetype) + bwipe! + call delete('cfgfile.' .. ext) + " check different case of file extension + let ext = substitute(ext, '\(\l\)', '\u\1', '') + endfor - call writefile(['// looks like Typescript'], 'Xfile.ts') - split Xfile.ts - call assert_equal('typescript', &filetype) + filetype off +endfunc + +func Test_d_file() + filetype on + + call writefile(['looks like D'], 'Xfile.d') + split Xfile.d + call assert_equal('d', &filetype) + bwipe! + + 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.hook') filetype off endfunc -func Test_ttl_file() +func Test_dat_file() filetype on - call writefile(['@base <http://example.org/> .'], 'Xfile.ttl') - split Xfile.ttl - call assert_equal('turtle', &filetype) + " KRL header start with "&WORD", but is not always present. + call writefile(['&ACCESS'], 'datfile.dat') + split datfile.dat + call assert_equal('krl', &filetype) bwipe! + call delete('datfile.dat') - call writefile(['looks like Tera Term Language'], 'Xfile.ttl') - split Xfile.ttl - call assert_equal('teraterm', &filetype) + " KRL defdat with leading spaces, for KRL file extension is not case + " sensitive. + call writefile([' DEFDAT datfile'], 'datfile.Dat') + split datfile.Dat + call assert_equal('krl', &filetype) bwipe! + call delete('datfile.Dat') + + " KRL defdat with embedded spaces, file starts with empty line(s). + call writefile(['', 'defdat datfile public'], 'datfile.DAT') + split datfile.DAT + call assert_equal('krl', &filetype) + bwipe! + + " User may overrule file inspection + let g:filetype_dat = 'dat' + split datfile.DAT + call assert_equal('dat', &filetype) + bwipe! + call delete('datfile.DAT') + unlet g:filetype_dat - 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 @@ -816,20 +1003,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 @@ -866,6 +1216,16 @@ func Test_m_file() call assert_equal('objc', &filetype) bwipe! + call writefile(['#include <header.h>'], 'Xfile.m') + split Xfile.m + call assert_equal('objc', &filetype) + bwipe! + + call writefile(['#define FORTY_TWO'], 'Xfile.m') + split Xfile.m + call assert_equal('objc', &filetype) + bwipe! + " Octave call writefile(['# Octave line comment'], 'Xfile.m') @@ -923,6 +1283,380 @@ func Test_m_file() filetype off endfunc +func Test_mod_file() + filetype on + + " *.mod defaults to Modsim III + call writefile(['locks like Modsim III'], 'modfile.mod') + split modfile.mod + call assert_equal('modsim3', &filetype) + bwipe! + + " Users preference set by g:filetype_mod + let g:filetype_mod = 'lprolog' + split modfile.mod + call assert_equal('lprolog', &filetype) + unlet g:filetype_mod + bwipe! + + " RAPID header start with a line containing only "%%%", + " but is not always present. + call writefile(['%%%'], 'modfile.mod') + split modfile.mod + call assert_equal('rapid', &filetype) + bwipe! + call delete('modfile.mod') + + " RAPID supports umlauts in module names, leading spaces, + " the .mod extension is not case sensitive. + call writefile([' module ÜmlautModule'], 'modfile.Mod') + split modfile.Mod + call assert_equal('rapid', &filetype) + bwipe! + call delete('modfile.Mod') + + " RAPID is not case sensitive, embedded spaces, sysmodule, + " file starts with empty line(s). + call writefile(['', 'MODULE rapidmödüle (SYSMODULE,NOSTEPIN)'], 'modfile.MOD') + split modfile.MOD + call assert_equal('rapid', &filetype) + bwipe! + + " Modula-2 MODULE not start of line + call writefile(['IMPLEMENTATION MODULE Module2Mod;'], 'modfile.MOD') + split modfile.MOD + call assert_equal('modula2', &filetype) + bwipe! + + " Modula-2 with comment and empty lines prior MODULE + call writefile(['', '(* with', ' comment *)', '', 'MODULE Module2Mod;'], 'modfile.MOD') + split modfile.MOD + call assert_equal('modula2', &filetype) + bwipe! + call delete('modfile.MOD') + + " LambdaProlog module + call writefile(['module lpromod.'], 'modfile.mod') + split modfile.mod + call assert_equal('lprolog', &filetype) + bwipe! + + " LambdaProlog with comment and empty lines prior module + call writefile(['', '% with', '% comment', '', 'module lpromod.'], 'modfile.mod') + split modfile.mod + call assert_equal('lprolog', &filetype) + bwipe! + call delete('modfile.mod') + + " go.mod + call writefile(['module example.com/M'], 'go.mod') + split go.mod + call assert_equal('gomod', &filetype) + bwipe! + call delete('go.mod') + + filetype off +endfunc + +func Test_patch_file() + filetype on + + call writefile([], 'Xfile.patch') + split Xfile.patch + call assert_equal('diff', &filetype) + bwipe! + + 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_perl_file() + filetype on + + " only tests one case, should do more + let lines =<< trim END + + use a + END + call writefile(lines, "Xfile.t") + split Xfile.t + call assert_equal('perl', &filetype) + bwipe + + call delete('Xfile.t') + filetype off +endfunc + +func Test_pp_file() + filetype on + + call writefile(['looks like puppet'], 'Xfile.pp') + split Xfile.pp + call assert_equal('puppet', &filetype) + bwipe! + + let g:filetype_pp = 'pascal' + split Xfile.pp + call assert_equal('pascal', &filetype) + bwipe! + unlet g:filetype_pp + + " Test dist#ft#FTpp() + call writefile(['{ pascal comment'], 'Xfile.pp') + split Xfile.pp + call assert_equal('pascal', &filetype) + bwipe! + + call writefile(['procedure pascal'], 'Xfile.pp') + split Xfile.pp + call assert_equal('pascal', &filetype) + bwipe! + + call delete('Xfile.pp') + filetype off +endfunc + +" Test dist#ft#FTprg() +func Test_prg_file() + filetype on + + " *.prg defaults to clipper + call writefile(['looks like clipper'], 'prgfile.prg') + split prgfile.prg + call assert_equal('clipper', &filetype) + bwipe! + + " Users preference set by g:filetype_prg + let g:filetype_prg = 'eviews' + split prgfile.prg + call assert_equal('eviews', &filetype) + unlet g:filetype_prg + bwipe! + + " RAPID header start with a line containing only "%%%", + " but is not always present. + call writefile(['%%%'], 'prgfile.prg') + split prgfile.prg + call assert_equal('rapid', &filetype) + bwipe! + call delete('prgfile.prg') + + " RAPID supports umlauts in module names, leading spaces, + " the .prg extension is not case sensitive. + call writefile([' module ÜmlautModule'], 'prgfile.Prg') + split prgfile.Prg + call assert_equal('rapid', &filetype) + bwipe! + call delete('prgfile.Prg') + + " RAPID is not case sensitive, embedded spaces, sysmodule, + " file starts with empty line(s). + call writefile(['', 'MODULE rapidmödüle (SYSMODULE,NOSTEPIN)'], 'prgfile.PRG') + split prgfile.PRG + call assert_equal('rapid', &filetype) + bwipe! + call delete('prgfile.PRG') + + filetype off +endfunc + +" Test dist#ft#FTsc() +func Test_sc_file() + filetype on + + " SC file mehtods are defined 'Class : Method' + call writefile(['SCNvimDocRenderer : SCDocHTMLRenderer {'], 'srcfile.sc') + split srcfile.sc + call assert_equal('supercollider', &filetype) + bwipe! + call delete('srcfile.sc') + + " SC classes are defined with '+ Class {}' + call writefile(['+ SCNvim {', '*methodArgs {|method|'], 'srcfile.sc') + split srcfile.sc + call assert_equal('supercollider', &filetype) + bwipe! + call delete('srcfile.sc') + + " Some SC class files start with comment and define methods many lines later + call writefile(['// Query', '//Method','^this {'], 'srcfile.sc') + split srcfile.sc + call assert_equal('supercollider', &filetype) + bwipe! + call delete('srcfile.sc') + + " Some SC class files put comments between method declaration after class + call writefile(['PingPong {', '//comment','*ar { arg'], 'srcfile.sc') + split srcfile.sc + call assert_equal('supercollider', &filetype) + bwipe! + call delete('srcfile.sc') + + filetype off +endfunc + +" Test dist#ft#FTscd() +func Test_scd_file() + filetype on + + call writefile(['ijq(1)'], 'srcfile.scd') + split srcfile.scd + call assert_equal('scdoc', &filetype) + bwipe! + call delete('srcfile.scd') + + filetype off +endfunc + +func Test_src_file() + filetype on + + " KRL header start with "&WORD", but is not always present. + call writefile(['&ACCESS'], 'srcfile.src') + split srcfile.src + call assert_equal('krl', &filetype) + bwipe! + call delete('srcfile.src') + + " KRL def with leading spaces, for KRL file extension is not case sensitive. + call writefile([' DEF srcfile()'], 'srcfile.Src') + split srcfile.Src + call assert_equal('krl', &filetype) + bwipe! + call delete('srcfile.Src') + + " KRL global def with embedded spaces, file starts with empty line(s). + call writefile(['', 'global def srcfile()'], 'srcfile.SRC') + split srcfile.SRC + call assert_equal('krl', &filetype) + bwipe! + + " User may overrule file inspection + let g:filetype_src = 'src' + split srcfile.SRC + call assert_equal('src', &filetype) + bwipe! + call delete('srcfile.SRC') + unlet g:filetype_src + + filetype off +endfunc + +func Test_sys_file() + filetype on + + " *.sys defaults to Batch file for MSDOS + call writefile(['looks like dos batch'], 'sysfile.sys') + split sysfile.sys + call assert_equal('bat', &filetype) + bwipe! + + " RAPID header start with a line containing only "%%%", + " but is not always present. + call writefile(['%%%'], 'sysfile.sys') + split sysfile.sys + call assert_equal('rapid', &filetype) + bwipe! + call delete('sysfile.sys') + + " RAPID supports umlauts in module names, leading spaces, + " the .sys extension is not case sensitive. + call writefile([' module ÜmlautModule'], 'sysfile.Sys') + split sysfile.Sys + call assert_equal('rapid', &filetype) + bwipe! + call delete('sysfile.Sys') + + " RAPID is not case sensitive, embedded spaces, sysmodule, + " file starts with empty line(s). + call writefile(['', 'MODULE rapidmödüle (SYSMODULE,NOSTEPIN)'], 'sysfile.SYS') + split sysfile.SYS + call assert_equal('rapid', &filetype) + bwipe! + call delete('sysfile.SYS') + + 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(['provider "azurerm" {'], 'Xfile.tf') + split Xfile.tf + call assert_equal('terraform', &filetype) + bwipe! + + call delete('Xfile.tf') + filetype off +endfunc + +func Test_ts_file() + filetype on + + call writefile(['<?xml version="1.0" encoding="utf-8"?>'], 'Xfile.ts') + split Xfile.ts + call assert_equal('xml', &filetype) + bwipe! + + call writefile(['// looks like Typescript'], 'Xfile.ts') + split Xfile.ts + call assert_equal('typescript', &filetype) + bwipe! + + 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! + + call writefile(['looks like Tera Term Language'], 'Xfile.ttl') + split Xfile.ttl + call assert_equal('teraterm', &filetype) + bwipe! + + call delete('Xfile.ttl') + filetype off +endfunc + func Test_xpm_file() filetype on @@ -935,4 +1669,5 @@ func Test_xpm_file() 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_fold.vim b/src/nvim/testdir/test_fold.vim index 5586fe2151..6da1b3d4a0 100644 --- a/src/nvim/testdir/test_fold.vim +++ b/src/nvim/testdir/test_fold.vim @@ -809,8 +809,7 @@ func Test_undo_fold_deletion() g/"/d undo redo - " eval getline(1, '$')->assert_equal(['']) - eval assert_equal(getline(1, '$'), ['']) + eval getline(1, '$')->assert_equal(['']) set fdm&vim bwipe! diff --git a/src/nvim/testdir/test_functions.vim b/src/nvim/testdir/test_functions.vim index 4a2ade5afa..f8be250f73 100644 --- a/src/nvim/testdir/test_functions.vim +++ b/src/nvim/testdir/test_functions.vim @@ -143,7 +143,7 @@ func Test_str2nr() call assert_equal(-123456789, str2nr('-123456789')) call assert_equal(5, str2nr('101', 2)) - call assert_equal(5, str2nr('0b101', 2)) + call assert_equal(5, '0b101'->str2nr(2)) call assert_equal(5, str2nr('0B101', 2)) call assert_equal(-5, str2nr('-101', 2)) call assert_equal(-5, str2nr('-0b101', 2)) @@ -200,7 +200,7 @@ func Test_strftime() " of strftime() can be 17 or 18, depending on timezone. call assert_match('^2017-01-1[78]$', strftime('%Y-%m-%d', 1484695512)) " - call assert_match('^\d\d\d\d-\(0\d\|1[012]\)-\([012]\d\|3[01]\) \([01]\d\|2[0-3]\):[0-5]\d:\([0-5]\d\|60\)$', strftime('%Y-%m-%d %H:%M:%S')) + call assert_match('^\d\d\d\d-\(0\d\|1[012]\)-\([012]\d\|3[01]\) \([01]\d\|2[0-3]\):[0-5]\d:\([0-5]\d\|60\)$', '%Y-%m-%d %H:%M:%S'->strftime()) call assert_fails('call strftime([])', 'E730:') call assert_fails('call strftime("%Y", [])', 'E745:') @@ -307,13 +307,19 @@ func Test_resolve_unix() call assert_equal('/', resolve('/')) endfunc +func s:normalize_fname(fname) + let ret = substitute(a:fname, '\', '/', 'g') + let ret = substitute(ret, '//', '/', 'g') + return ret->tolower() +endfunc + func Test_simplify() call assert_equal('', simplify('')) call assert_equal('/', simplify('/')) call assert_equal('/', simplify('/.')) call assert_equal('/', simplify('/..')) call assert_equal('/...', simplify('/...')) - call assert_equal('./dir/file', simplify('./dir/file')) + call assert_equal('./dir/file', './dir/file'->simplify()) call assert_equal('./dir/file', simplify('.///dir//file')) call assert_equal('./dir/file', simplify('./dir/./file')) call assert_equal('./file', simplify('./dir/../file')) @@ -326,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) - call setbufvar(dummy_buf, '&buftype', 'nofile') - 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')) @@ -370,12 +345,31 @@ 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() call assert_equal('de', strpart('abcdefg', 3, 2)) call assert_equal('ab', strpart('abcdefg', -2, 4)) - call assert_equal('abcdefg', strpart('abcdefg', -2)) + call assert_equal('abcdefg', 'abcdefg'->strpart(-2)) call assert_equal('fg', strpart('abcdefg', 5, 4)) call assert_equal('defg', strpart('abcdefg', 3)) @@ -469,7 +463,7 @@ func Test_toupper() \ toupper(' !"#$%&''()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~')) " Test with a few lowercase diacritics. - call assert_equal("AÀÁÂÃÄÅĀĂĄǍǞǠẢ", toupper("aàáâãäåāăąǎǟǡả")) + call assert_equal("AÀÁÂÃÄÅĀĂĄǍǞǠẢ", "aàáâãäåāăąǎǟǡả"->toupper()) call assert_equal("BḂḆ", toupper("bḃḇ")) call assert_equal("CÇĆĈĊČ", toupper("cçćĉċč")) call assert_equal("DĎĐḊḎḐ", toupper("dďđḋḏḑ")) @@ -532,6 +526,11 @@ func Test_toupper() call toupper("123\xC0\x80\xC0") endfunc +func Test_tr() + call assert_equal('foo', tr('bar', 'bar', 'foo')) + call assert_equal('zxy', 'cab'->tr('abc', 'xyz')) +endfunc + " Tests for the mode() function let current_modes = '' func Save_mode() @@ -809,11 +808,11 @@ endfunc func Test_stridx() call assert_equal(-1, stridx('', 'l')) call assert_equal(0, stridx('', '')) - call assert_equal(0, stridx('hello', '')) + call assert_equal(0, 'hello'->stridx('')) call assert_equal(-1, stridx('hello', 'L')) call assert_equal(2, stridx('hello', 'l', -1)) call assert_equal(2, stridx('hello', 'l', 0)) - call assert_equal(2, stridx('hello', 'l', 1)) + call assert_equal(2, 'hello'->stridx('l', 1)) call assert_equal(3, stridx('hello', 'l', 3)) call assert_equal(-1, stridx('hello', 'l', 4)) call assert_equal(-1, stridx('hello', 'l', 10)) @@ -826,7 +825,7 @@ func Test_strridx() call assert_equal(0, strridx('', '')) call assert_equal(5, strridx('hello', '')) call assert_equal(-1, strridx('hello', 'L')) - call assert_equal(3, strridx('hello', 'l')) + call assert_equal(3, 'hello'->strridx('l')) call assert_equal(3, strridx('hello', 'l', 10)) call assert_equal(3, strridx('hello', 'l', 3)) call assert_equal(2, strridx('hello', 'l', 2)) @@ -1219,7 +1218,7 @@ func Test_shellescape() let save_shell = &shell set shell=bash call assert_equal("'text'", shellescape('text')) - call assert_equal("'te\"xt'", shellescape('te"xt')) + call assert_equal("'te\"xt'", 'te"xt'->shellescape()) call assert_equal("'te'\\''xt'", shellescape("te'xt")) call assert_equal("'te%xt'", shellescape("te%xt")) @@ -1263,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, ... ) @@ -1293,7 +1323,7 @@ endfunc func Test_trim() call assert_equal("Testing", trim(" \t\r\r\x0BTesting \t\n\r\n\t\x0B\x0B")) - call assert_equal("Testing", trim(" \t \r\r\n\n\x0BTesting \t\n\r\n\t\x0B\x0B")) + call assert_equal("Testing", " \t \r\r\n\n\x0BTesting \t\n\r\n\t\x0B\x0B"->trim()) call assert_equal("RESERVE", trim("xyz \twwRESERVEzyww \t\t", " wxyz\t")) call assert_equal("wRE \tSERVEzyww", trim("wRE \tSERVEzyww")) call assert_equal("abcd\t xxxx tail", trim(" \tabcd\t xxxx tail")) @@ -1320,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 - call setline(1, 10->range()) - 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 = '' @@ -1483,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 @@ -1508,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') @@ -1548,54 +1527,99 @@ 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('') - call writefile(['some', 'text'], '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 Test_readdir() - if isdirectory('Xdir') - call delete('Xdir', 'rf') +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() call mkdir('Xdir') call writefile([], 'Xdir/foo.txt') call writefile([], 'Xdir/bar.txt') @@ -1625,6 +1649,30 @@ func Test_readdir() call delete('Xdir', 'rf') endfunc +func Test_delete_rf() + call mkdir('Xdir') + call writefile([], 'Xdir/foo.txt') + call writefile([], 'Xdir/bar.txt') + call mkdir('Xdir/[a-1]') " issue #696 + call writefile([], 'Xdir/[a-1]/foo.txt') + call writefile([], 'Xdir/[a-1]/bar.txt') + call assert_true(filereadable('Xdir/foo.txt')) + call assert_true('Xdir/[a-1]/foo.txt'->filereadable()) + + call assert_equal(0, delete('Xdir', 'rf')) + call assert_false(filereadable('Xdir/foo.txt')) + call assert_false(filereadable('Xdir/[a-1]/foo.txt')) + + if has('unix') + call mkdir('Xdir/Xdir2', 'p') + silent !chmod 555 Xdir + call assert_equal(-1, delete('Xdir/Xdir2', 'rf')) + call assert_equal(-1, delete('Xdir', 'rf')) + silent !chmod 755 Xdir + call assert_equal(0, delete('Xdir', 'rf')) + endif +endfunc + func Test_call() call assert_equal(3, call('len', [123])) call assert_equal(3, 'len'->call([123])) @@ -1647,6 +1695,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:') @@ -1664,6 +1755,102 @@ 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 Test_getmousepos() + enew! + call setline(1, "\t\t\t1234") + " call test_setmouse(1, 1) + call nvim_input_mouse('left', 'press', '', 0, 0, 0) + call getchar() " wait for and consume the mouse press + call assert_equal(#{ + \ screenrow: 1, + \ screencol: 1, + \ winid: win_getid(), + \ winrow: 1, + \ wincol: 1, + \ line: 1, + \ column: 1, + \ }, getmousepos()) + " call test_setmouse(1, 25) + call nvim_input_mouse('left', 'press', '', 0, 0, 24) + call getchar() " wait for and consume the mouse press + call assert_equal(#{ + \ screenrow: 1, + \ screencol: 25, + \ winid: win_getid(), + \ winrow: 1, + \ wincol: 25, + \ line: 1, + \ column: 4, + \ }, getmousepos()) + " call test_setmouse(1, 50) + call nvim_input_mouse('left', 'press', '', 0, 0, 49) + call getchar() " wait for and consume the mouse press + call assert_equal(#{ + \ screenrow: 1, + \ screencol: 50, + \ winid: win_getid(), + \ winrow: 1, + \ wincol: 50, + \ line: 1, + \ column: 8, + \ }, getmousepos()) + + " If the mouse is positioned past the last buffer line, "line" and "column" + " should act like it's positioned on the last buffer line. + " call test_setmouse(2, 25) + call nvim_input_mouse('left', 'press', '', 0, 1, 24) + call getchar() " wait for and consume the mouse press + call assert_equal(#{ + \ screenrow: 2, + \ screencol: 25, + \ winid: win_getid(), + \ winrow: 2, + \ wincol: 25, + \ line: 1, + \ column: 4, + \ }, getmousepos()) + " call test_setmouse(2, 50) + call nvim_input_mouse('left', 'press', '', 0, 1, 49) + call getchar() " wait for and consume the mouse press + call assert_equal(#{ + \ screenrow: 2, + \ screencol: 50, + \ winid: win_getid(), + \ winrow: 2, + \ wincol: 50, + \ line: 1, + \ column: 8, + \ }, getmousepos()) + bwipe! +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 6fd9477ce9..efdf44a0d6 100644 --- a/src/nvim/testdir/test_highlight.vim +++ b/src/nvim/testdir/test_highlight.vim @@ -146,7 +146,7 @@ func Test_highlight_eol_with_cursorline_vertsplit() " 'abcd |abcd ' " ^^^^ ^^^^^^^^^ no highlight " ^ 'Search' highlight - " ^ 'VertSplit' highlight + " ^ 'WinSeparator' highlight let attrs0 = ScreenAttrs(1, 15)[0] call assert_equal(repeat([attrs0[0]], 4), attrs0[0:3]) call assert_equal(repeat([attrs0[0]], 9), attrs0[6:14]) @@ -160,7 +160,7 @@ func Test_highlight_eol_with_cursorline_vertsplit() " 'abcd |abcd ' " ^^^^ underline " ^ 'Search' highlight with underline - " ^ 'VertSplit' highlight + " ^ 'WinSeparator' highlight " ^^^^^^^^^ no highlight " underline @@ -597,6 +597,86 @@ func Test_cursorline_with_visualmode() call delete('Xtest_cursorline_with_visualmode') endfunc +func Test_cursorcolumn_insert_on_tab() + CheckScreendump + + let lines =<< trim END + call setline(1, ['123456789', "a\tb"]) + set cursorcolumn + call cursor(2, 2) + END + call writefile(lines, 'Xcuc_insert_on_tab') + + let buf = RunVimInTerminal('-S Xcuc_insert_on_tab', #{rows: 8}) + call TermWait(buf) + call VerifyScreenDump(buf, 'Test_cursorcolumn_insert_on_tab_1', {}) + + call term_sendkeys(buf, 'i') + call TermWait(buf) + call VerifyScreenDump(buf, 'Test_cursorcolumn_insert_on_tab_2', {}) + + call term_sendkeys(buf, "\<C-O>") + call TermWait(buf) + call VerifyScreenDump(buf, 'Test_cursorcolumn_insert_on_tab_3', {}) + + call term_sendkeys(buf, 'i') + call TermWait(buf) + call VerifyScreenDump(buf, 'Test_cursorcolumn_insert_on_tab_2', {}) + + call StopVimInTerminal(buf) + call delete('Xcuc_insert_on_tab') +endfunc + +func Test_cursorcolumn_callback() + CheckScreendump + CheckFeature timers + + let lines =<< trim END + call setline(1, ['aaaaa', 'bbbbb', 'ccccc', 'ddddd']) + set cursorcolumn + call cursor(4, 5) + + func Func(timer) + call cursor(1, 1) + endfunc + + call timer_start(300, 'Func') + END + call writefile(lines, 'Xcuc_timer') + + let buf = RunVimInTerminal('-S Xcuc_timer', #{rows: 8}) + call TermWait(buf, 310) + call VerifyScreenDump(buf, 'Test_cursorcolumn_callback_1', {}) + + call StopVimInTerminal(buf) + call delete('Xcuc_timer') +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 @@ -651,6 +731,23 @@ func Test_1_highlight_Normalgroup_exists() endif endfunc +function Test_no_space_before_xxx() + " Note: we need to create this highlight group in the test because it does not exist in Neovim + execute('hi StatusLineTermNC ctermfg=green') + let l:org_columns = &columns + set columns=17 + let l:hi_StatusLineTermNC = join(split(execute('hi StatusLineTermNC'))) + call assert_match('StatusLineTermNC xxx', l:hi_StatusLineTermNC) + let &columns = l:org_columns +endfunction + +" Test for :highlight command errors +func Test_highlight_cmd_errors() + if has('gui_running') || has('nvim') + call assert_fails('hi ' .. repeat('a', 201) .. ' ctermfg=black', 'E1249:') + endif +endfunc + " Test for using RGB color values in a highlight group func Test_xxlast_highlight_RGB_color() CheckCanRunGui diff --git a/src/nvim/testdir/test_ins_complete.vim b/src/nvim/testdir/test_ins_complete.vim index ce75799551..24eaf9e8b1 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 @@ -636,4 +707,23 @@ func Test_z1_complete_no_history() close! endfunc +func FooBarComplete(findstart, base) + if a:findstart + return col('.') - 1 + else + return ["Foo", "Bar", "}"] + endif +endfunc + +func Test_complete_smartindent() + new + setlocal smartindent completefunc=FooBarComplete + + exe "norm! o{\<cr>\<c-x>\<c-u>\<c-p>}\<cr>\<esc>" + let result = getline(1,'$') + call assert_equal(['', '{','}',''], result) + bw! + delfunction! FooBarComplete +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_jumplist.vim b/src/nvim/testdir/test_jumplist.vim index 9cfbbe2029..91ad940e18 100644 --- a/src/nvim/testdir/test_jumplist.vim +++ b/src/nvim/testdir/test_jumplist.vim @@ -64,3 +64,44 @@ func Test_getjumplist() call delete("Xtest") endfunc + +func Test_jumplist_invalid() + new + clearjumps + " put some randome text + put ='a' + let prev = bufnr('%') + setl nomodified bufhidden=wipe + e XXJumpListBuffer + let bnr = bufnr('%') + " 1) empty jumplist + let expected = [[ + \ {'lnum': 2, 'bufnr': prev, 'col': 0, 'coladd': 0}], 1] + call assert_equal(expected, getjumplist()) + let jumps = execute(':jumps') + call assert_equal('>', jumps[-1:]) + " now jump back + exe ":norm! \<c-o>" + let expected = [[ + \ {'lnum': 2, 'bufnr': prev, 'col': 0, 'coladd': 0}, + \ {'lnum': 1, 'bufnr': bnr, 'col': 0, 'coladd': 0}], 0] + call assert_equal(expected, getjumplist()) + let jumps = execute(':jumps') + call assert_match('> 0 2 0 -invalid-', jumps) +endfunc + +" Test for '' mark in an empty buffer + +func Test_empty_buffer() + new + insert +a +b +c +d +. + call assert_equal(1, line("''")) + bwipe! +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_jumps.vim b/src/nvim/testdir/test_jumps.vim deleted file mode 100644 index 5a3717d165..0000000000 --- a/src/nvim/testdir/test_jumps.vim +++ /dev/null @@ -1,11 +0,0 @@ -func Test_empty_buffer() - new - insert -a -b -c -d -. - call assert_equal(1, line("''")) - bwipe! -endfunc 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 751be8eff5..b239c9c6b5 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! @@ -25,7 +27,7 @@ func Test_listchars() redraw! for i in range(1, 5) call cursor(i, 1) - call assert_equal([expected[i - 1]], ScreenLines(i, virtcol('$'))) + call assert_equal([expected[i - 1]], ScreenLines(i, '$'->virtcol())) endfor set listchars-=trail:< @@ -112,7 +114,7 @@ func Test_listchars() " Test lead and trail normal ggdG - set listchars=eol:$ + set listchars=eol:$ " Accommodate Nvim default set listchars+=lead:>,trail:<,space:x set list @@ -142,7 +144,7 @@ func Test_listchars() " Test multispace normal ggdG - set listchars=eol:$ + set listchars=eol:$ " Accommodate Nvim default set listchars+=multispace:yYzZ set list @@ -305,7 +307,7 @@ func Test_listchars_invalid() enew! set ff=unix - set listchars=eol:$ + set listchars=eol:$ " Accommodate Nvim default set list set ambiwidth=double @@ -331,7 +333,7 @@ func Test_listchars_invalid() call assert_fails('set listchars=space:xx', 'E474:') call assert_fails('set listchars=tab:xxxx', 'E474:') - " Has non-single width character + " Has double-width character call assert_fails('set listchars=space:·', 'E474:') call assert_fails('set listchars=tab:·x', 'E474:') call assert_fails('set listchars=tab:x·', 'E474:') @@ -339,6 +341,20 @@ func Test_listchars_invalid() call assert_fails('set listchars=multispace:·', 'E474:') call assert_fails('set listchars=multispace:xxx·', 'E474:') + " Has control character + call assert_fails("set listchars=space:\x01", 'E474:') + call assert_fails("set listchars=tab:\x01x", 'E474:') + call assert_fails("set listchars=tab:x\x01", 'E474:') + call assert_fails("set listchars=tab:xx\x01", 'E474:') + call assert_fails("set listchars=multispace:\x01", 'E474:') + call assert_fails("set listchars=multispace:xxx\x01", 'E474:') + call assert_fails('set listchars=space:\\x01', 'E474:') + call assert_fails('set listchars=tab:\\x01x', 'E474:') + call assert_fails('set listchars=tab:x\\x01', 'E474:') + call assert_fails('set listchars=tab:xx\\x01', 'E474:') + call assert_fails('set listchars=multispace:\\x01', 'E474:') + call assert_fails('set listchars=multispace:xxx\\x01', 'E474:') + enew! set ambiwidth& listchars& ff& endfunction @@ -369,3 +385,174 @@ func Test_listchars_composing() enew! set listchars& ff& endfunction + +" Check for the value of the 'listchars' option +func s:CheckListCharsValue(expected) + call assert_equal(a:expected, &listchars) + call assert_equal(a:expected, getwinvar(0, '&listchars')) +endfunc + +" Test for using a window local value for 'listchars' +func Test_listchars_window_local() + %bw! + set list listchars& + let l:default_listchars = &listchars " Accommodate Nvim default + new + " set a local value for 'listchars' + setlocal listchars=tab:+-,eol:# + call s:CheckListCharsValue('tab:+-,eol:#') + " When local value is reset, global value should be used + setlocal listchars= + call s:CheckListCharsValue(l:default_listchars) " Accommodate Nvim default + " Use 'setlocal <' to copy global value + setlocal listchars=space:.,extends:> + setlocal listchars< + call s:CheckListCharsValue(l:default_listchars) " Accommodate Nvim default + " Use 'set <' to copy global value + setlocal listchars=space:.,extends:> + set listchars< + call s:CheckListCharsValue(l:default_listchars) " Accommodate Nvim default + " Changing global setting should not change the local setting + setlocal listchars=space:.,extends:> + setglobal listchars=tab:+-,eol:# + call s:CheckListCharsValue('space:.,extends:>') + " when split opening a new window, local value should be copied + split + call s:CheckListCharsValue('space:.,extends:>') + " clearing local value in one window should not change the other window + set listchars& + call s:CheckListCharsValue(l:default_listchars) " Accommodate Nvim default + close + call s:CheckListCharsValue('space:.,extends:>') + + " use different values for 'listchars' items in two different windows + call setline(1, ["\t one two "]) + setlocal listchars=tab:<->,lead:_,space:.,trail:@,eol:# + split + setlocal listchars=tab:[.],lead:#,space:_,trail:.,eol:& + split + set listchars=tab:+-+,lead:^,space:>,trail:<,eol:% + call assert_equal(['+------+^^one>>two<<%'], ScreenLines(1, virtcol('$'))) + close + call assert_equal(['[......]##one__two..&'], ScreenLines(1, virtcol('$'))) + close + call assert_equal(['<------>__one..two@@#'], ScreenLines(1, virtcol('$'))) + " changing the global setting should not change the local value + setglobal listchars=tab:[.],lead:#,space:_,trail:.,eol:& + call assert_equal(['<------>__one..two@@#'], ScreenLines(1, virtcol('$'))) + set listchars< + call assert_equal(['[......]##one__two..&'], ScreenLines(1, virtcol('$'))) + + " Using setglobal in a window with local setting should not affect the + " window. But should impact other windows using the global setting. + enew! | only + call setline(1, ["\t one two "]) + set listchars=tab:[.],lead:#,space:_,trail:.,eol:& + split + setlocal listchars=tab:+-+,lead:^,space:>,trail:<,eol:% + split + setlocal listchars=tab:<->,lead:_,space:.,trail:@,eol:# + setglobal listchars=tab:{.},lead:-,space:=,trail:#,eol:$ + call assert_equal(['<------>__one..two@@#'], ScreenLines(1, virtcol('$'))) + close + call assert_equal(['+------+^^one>>two<<%'], ScreenLines(1, virtcol('$'))) + close + call assert_equal(['{......}--one==two##$'], ScreenLines(1, virtcol('$'))) + + " Setting the global setting to the default value should not impact a window + " using a local setting. + split + setlocal listchars=tab:<->,lead:_,space:.,trail:@,eol:# + setglobal listchars=eol:$ " Accommodate Nvim default + call assert_equal(['<------>__one..two@@#'], ScreenLines(1, virtcol('$'))) + close + call assert_equal(['^I one two $'], ScreenLines(1, virtcol('$'))) + + " Setting the local setting to the default value should not impact a window + " using a global setting. + set listchars=tab:{.},lead:-,space:=,trail:#,eol:$ + split + setlocal listchars=tab:<->,lead:_,space:.,trail:@,eol:# + call assert_equal(['<------>__one..two@@#'], ScreenLines(1, virtcol('$'))) + setlocal listchars=eol:$ " Accommodate Nvim default + call assert_equal(['^I one two $'], ScreenLines(1, virtcol('$'))) + close + call assert_equal(['{......}--one==two##$'], ScreenLines(1, virtcol('$'))) + + " Using set in a window with a local setting should change it to use the + " global setting and also impact other windows using the global setting. + split + setlocal listchars=tab:<->,lead:_,space:.,trail:@,eol:# + call assert_equal(['<------>__one..two@@#'], ScreenLines(1, virtcol('$'))) + set listchars=tab:+-+,lead:^,space:>,trail:<,eol:% + call assert_equal(['+------+^^one>>two<<%'], ScreenLines(1, virtcol('$'))) + close + call assert_equal(['+------+^^one>>two<<%'], ScreenLines(1, virtcol('$'))) + + " Setting invalid value for a local setting should not impact the local and + " global settings. + split + setlocal listchars=tab:<->,lead:_,space:.,trail:@,eol:# + let cmd = 'setlocal listchars=tab:{.},lead:-,space:=,trail:#,eol:$,x' + call assert_fails(cmd, 'E474:') + call assert_equal(['<------>__one..two@@#'], ScreenLines(1, virtcol('$'))) + close + call assert_equal(['+------+^^one>>two<<%'], ScreenLines(1, virtcol('$'))) + + " Setting invalid value for a global setting should not impact the local and + " global settings. + split + setlocal listchars=tab:<->,lead:_,space:.,trail:@,eol:# + let cmd = 'setglobal listchars=tab:{.},lead:-,space:=,trail:#,eol:$,x' + call assert_fails(cmd, 'E474:') + call assert_equal(['<------>__one..two@@#'], ScreenLines(1, virtcol('$'))) + close + call assert_equal(['+------+^^one>>two<<%'], ScreenLines(1, virtcol('$'))) + + " Closing window with local lcs-multispace should not cause a memory leak. + setlocal listchars=multispace:---+ + split + call s:CheckListCharsValue('multispace:---+') + close + + %bw! + 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', {}) + call term_sendkeys(buf, "\<C-W>h") + call term_sendkeys(buf, ":set nowrap foldcolumn=4\<CR>") + call term_sendkeys(buf, "15\<C-W><") + call VerifyScreenDump(buf, 'Test_listchars_06', {}) + call term_sendkeys(buf, "4\<C-W><") + call VerifyScreenDump(buf, 'Test_listchars_07', {}) + + " 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.vim b/src/nvim/testdir/test_listlbr.vim index e0518de3c2..2fda12d8b4 100644 --- a/src/nvim/testdir/test_listlbr.vim +++ b/src/nvim/testdir/test_listlbr.vim @@ -43,6 +43,7 @@ endfunc func Test_linebreak_with_list() throw 'skipped: Nvim does not support enc=latin1' + set listchars= call s:test_windows('setl ts=4 sbr=+ list listchars=') call setline(1, "\tabcdef hijklmn\tpqrstuvwxyz_1060ABCDEFGHIJKLMNOP ") let lines = s:screen_lines([1, 4], winwidth(0)) @@ -54,6 +55,7 @@ func Test_linebreak_with_list() \ ] call s:compare_lines(expect, lines) call s:close_windows() + set listchars&vim endfunc func Test_linebreak_with_nolist() 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..a8dd0ca286 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:') @@ -568,4 +646,68 @@ func Test_abbreviate_multi_byte() bwipe! endfunc +" Test for <Plug> always being mapped, even when used with "noremap". +func Test_plug_remap() + let g:foo = 0 + nnoremap <Plug>(Increase_x) <Cmd>let g:foo += 1<CR> + nmap <F2> <Plug>(Increase_x) + nnoremap <F3> <Plug>(Increase_x) + call feedkeys("\<F2>", 'xt') + call assert_equal(1, g:foo) + call feedkeys("\<F3>", 'xt') + call assert_equal(2, g:foo) + nnoremap x <Nop> + nmap <F4> x<Plug>(Increase_x)x + nnoremap <F5> x<Plug>(Increase_x)x + call setline(1, 'Some text') + normal! gg$ + call feedkeys("\<F4>", 'xt') + call assert_equal(3, g:foo) + call assert_equal('Some text', getline(1)) + call feedkeys("\<F5>", 'xt') + call assert_equal(4, g:foo) + call assert_equal('Some te', getline(1)) + nunmap <Plug>(Increase_x) + nunmap <F2> + nunmap <F3> + nunmap <F4> + nunmap <F5> + unlet g:foo + %bw! +endfunc + +" Test for mapping <LeftDrag> in Insert mode +func Test_mouse_drag_insert_map() + CheckFunction test_setmouse + set mouse=a + func ClickExpr() + call test_setmouse(1, 1) + return "\<LeftMouse>" + endfunc + func DragExpr() + call test_setmouse(1, 2) + return "\<LeftDrag>" + endfunc + inoremap <expr> <F2> ClickExpr() + imap <expr> <F3> DragExpr() + + inoremap <LeftDrag> <LeftDrag><Cmd>let g:dragged = 1<CR> + exe "normal i\<F2>\<F3>" + call assert_equal(1, g:dragged) + call assert_equal('v', mode()) + exe "normal! \<C-\>\<C-N>" + unlet g:dragged + + inoremap <LeftDrag> <LeftDrag><C-\><C-N> + exe "normal i\<F2>\<F3>" + call assert_equal('n', mode()) + + iunmap <LeftDrag> + iunmap <F2> + iunmap <F3> + delfunc ClickExpr + delfunc DragExpr + set mouse& +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_marks.vim b/src/nvim/testdir/test_marks.vim index 2fd82a4b6d..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 @@ -227,7 +252,9 @@ func Test_getmarklist() call cursor(2, 2) normal mr call assert_equal({'mark' : "'r", 'pos' : [bufnr(), 2, 2, 0]}, - \ getmarklist(bufnr())[0]) - call assert_equal([], getmarklist({})) + \ bufnr()->getmarklist()[0]) + 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 34c8c49dd5..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(screenchar(a:lnum, c))) + 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 08586dffe1..82c4cc128b 100644 --- a/src/nvim/testdir/test_messages.vim +++ b/src/nvim/testdir/test_messages.vim @@ -2,6 +2,9 @@ source check.vim source shared.vim +source term_util.vim +source view_util.vim +source screendump.vim func Test_messages() let oldmore = &more @@ -40,7 +43,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 +58,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') @@ -87,7 +90,7 @@ func Test_echoerr() if has('float') call assert_equal("\n1.23 IgNoRe", execute(':echoerr 1.23 "IgNoRe"')) endif - call test_ignore_error('<lambda>') + eval '<lambda>'->test_ignore_error() call assert_match("function('<lambda>\\d*')", execute(':echoerr {-> 1234}')) call test_ignore_error('RESET') endfunc @@ -108,3 +111,61 @@ func Test_echospace() set ruler& showcmd& endfunc + +func Test_quit_long_message() + CheckScreendump + + let content =<< trim END + echom range(9999)->join("\x01") + END + call writefile(content, 'Xtest_quit_message') + let buf = RunVimInTerminal('-S Xtest_quit_message', #{rows: 6}) + call term_sendkeys(buf, "q") + call VerifyScreenDump(buf, 'Test_quit_long_message', {}) + + " clean up + call StopVimInTerminal(buf) + call delete('Xtest_quit_message') +endfunc + +" this was missing a terminating NUL +func Test_echo_string_partial() + function CountSpaces() + endfunction + call assert_equal("function('CountSpaces', [{'ccccccccccc': ['ab', 'cd'], 'aaaaaaaaaaa': v:false, 'bbbbbbbbbbbb': ''}])", string(function('CountSpaces', [#{aaaaaaaaaaa: v:false, bbbbbbbbbbbb: '', ccccccccccc: ['ab', 'cd']}]))) +endfunc + +" Message output was previously overwritten by the fileinfo display, shown +" when switching buffers. If a buffer is switched to, then a message if +" echoed, we should show the message, rather than overwriting it with +" fileinfo. +func Test_fileinfo_after_echo() + CheckScreendump + + let content =<< trim END + file a.txt + + hide edit b.txt + call setline(1, "hi") + setlocal modified + + hide buffer a.txt + + autocmd CursorHold * buf b.txt | w | echo "'b' written" + END + + call writefile(content, 'Xtest_fileinfo_after_echo') + let buf = RunVimInTerminal('-S Xtest_fileinfo_after_echo', #{rows: 6}) + call term_sendkeys(buf, ":set updatetime=50\<CR>") + call term_sendkeys(buf, "0$") + call VerifyScreenDump(buf, 'Test_fileinfo_after_echo', {}) + + call term_sendkeys(buf, ":q\<CR>") + + " clean up + call StopVimInTerminal(buf) + call delete('Xtest_fileinfo_after_echo') + call delete('b.txt') +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_mksession.vim b/src/nvim/testdir/test_mksession.vim index c96c6a9678..5dbe2cd366 100644 --- a/src/nvim/testdir/test_mksession.vim +++ b/src/nvim/testdir/test_mksession.vim @@ -156,8 +156,7 @@ func Test_mksession_zero_winheight() wincmd _ mksession! Xtest_mks_zero set winminheight& - " let text = readfile('Xtest_mks_zero')->join() - let text = join(readfile('Xtest_mks_zero')) + let text = readfile('Xtest_mks_zero')->join() call delete('Xtest_mks_zero') close " check there is no divide by zero @@ -220,6 +219,7 @@ func Test_mksession_one_buffer_two_windows() let count1 = 0 let count2 = 0 let count2buf = 0 + let bufexists = 0 for line in lines if line =~ 'edit \f*Xtest1$' let count1 += 1 @@ -230,10 +230,14 @@ func Test_mksession_one_buffer_two_windows() if line =~ 'buffer \f\{-}Xtest2' let count2buf += 1 endif + if line =~ 'bufexists(fnamemodify(.*, ":p")' + let bufexists += 1 + endif endfor call assert_equal(1, count1, 'Xtest1 count') call assert_equal(2, count2, 'Xtest2 count') call assert_equal(2, count2buf, 'Xtest2 buffer count') + call assert_equal(2, bufexists) close bwipe! @@ -310,6 +314,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() @@ -697,6 +726,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') @@ -736,6 +795,49 @@ func Test_mksession_winminheight() set sessionoptions& endfunc +" Test for mksession with and without options restores shortmess +func Test_mksession_shortmess() + " Without options + set sessionoptions-=options + split + mksession! Xtest_mks.out + let found_save = 0 + let found_restore = 0 + let lines = readfile('Xtest_mks.out') + for line in lines + let line = trim(line) + + if line ==# 'let s:shortmess_save = &shortmess' + let found_save += 1 + endif + + if found_save !=# 0 && line ==# 'let &shortmess = s:shortmess_save' + let found_restore += 1 + endif + endfor + call assert_equal(1, found_save) + call assert_equal(1, found_restore) + call delete('Xtest_mks.out') + close + set sessionoptions& + + " With options + set sessionoptions+=options + split + mksession! Xtest_mks.out + let found_restore = 0 + let lines = readfile('Xtest_mks.out') + for line in lines + if line =~# 's:shortmess_save' + let found_restore += 1 + endif + endfor + call assert_equal(0, found_restore) + call delete('Xtest_mks.out') + close + set sessionoptions& +endfunc + " Test for mksession with 'compatible' option func Test_mksession_compatible() throw 'skipped: Nvim does not support "compatible" option' diff --git a/src/nvim/testdir/test_normal.vim b/src/nvim/testdir/test_normal.vim index aff22f5d01..1feffe1f8c 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) @@ -1835,6 +1843,16 @@ fun! Test_normal33_g_cmd2() bw! endfunc +func Test_normal_ex_substitute() + " This was hanging on the substitute prompt. + new + call setline(1, 'a') + exe "normal! gggQs/a/b/c\<CR>" + call assert_equal('a', getline(1)) + bwipe! +endfunc + +" Test for g CTRL-G func Test_g_ctrl_g() new @@ -2759,4 +2777,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..521b0cf706 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>") @@ -298,6 +298,31 @@ func Test_relativenumber_colors() call delete('XTest_relnr') endfunc +func Test_relativenumber_callback() + CheckScreendump + CheckFeature timers + + let lines =<< trim END + call setline(1, ['aaaaa', 'bbbbb', 'ccccc', 'ddddd']) + set relativenumber + call cursor(4, 1) + + func Func(timer) + call cursor(1, 1) + endfunc + + call timer_start(300, 'Func') + END + call writefile(lines, 'Xrnu_timer') + + let buf = RunVimInTerminal('-S Xrnu_timer', #{rows: 8}) + call TermWait(buf, 310) + call VerifyScreenDump(buf, 'Test_relativenumber_callback_1', {}) + + call StopVimInTerminal(buf) + call delete('Xrnu_timer') +endfunc + " Test for displaying line numbers with 'rightleft' func Test_number_rightleft() CheckFeature rightleft diff --git a/src/nvim/testdir/test_options.vim b/src/nvim/testdir/test_options.vim index 41c689849b..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 @@ -235,8 +235,7 @@ func Test_set_completion() call feedkeys(":set filetype=sshdconfi\<Tab>\<C-B>\"\<CR>", 'xt') call assert_equal('"set filetype=sshdconfig', @:) call feedkeys(":set filetype=a\<C-A>\<C-B>\"\<CR>", 'xt') - " call assert_equal('"set filetype=' .. getcompletion('a*', 'filetype')->join(), @:) - call assert_equal('"set filetype=' .. join(getcompletion('a*', 'filetype')), @:) + call assert_equal('"set filetype=' .. getcompletion('a*', 'filetype')->join(), @:) endfunc func Test_set_errors() @@ -260,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:') @@ -313,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') @@ -369,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') @@ -648,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& @@ -733,4 +783,25 @@ func Test_opt_reset_scroll() call delete('Xscroll') endfunc +" Test for the 'cdhome' option +func Test_opt_cdhome() + if has('unix') || has('vms') + throw 'Skipped: only works on non-Unix' + endif + + set cdhome& + call assert_equal(0, &cdhome) + set cdhome + + " This paragraph is copied from Test_cd_no_arg(). + let path = getcwd() + cd + call assert_equal($HOME, getcwd()) + call assert_notequal(path, getcwd()) + exe 'cd ' .. fnameescape(path) + call assert_equal(path, getcwd()) + + set cdhome& +endfunc + " vim: shiftwidth=2 sts=2 expandtab 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_prompt_buffer.vim b/src/nvim/testdir/test_prompt_buffer.vim index 3da46eb1a6..8f94a8572b 100644 --- a/src/nvim/testdir/test_prompt_buffer.vim +++ b/src/nvim/testdir/test_prompt_buffer.vim @@ -41,6 +41,10 @@ func WriteScript(name) \ ' set nomodified', \ 'endfunc', \ '', + \ 'func SwitchWindows()', + \ ' call timer_start(0, {-> execute("wincmd p|wincmd p", "")})', + \ 'endfunc', + \ '', \ 'call setline(1, "other buffer")', \ 'set nomodified', \ 'new', @@ -89,9 +93,12 @@ func Test_prompt_editing() call term_sendkeys(buf, left . left . left . bs . '-') call WaitForAssert({-> assert_equal('cmd: -hel', term_getline(buf, 1))}) + call term_sendkeys(buf, "\<C-O>lz") + call WaitForAssert({-> assert_equal('cmd: -hzel', term_getline(buf, 1))}) + let end = "\<End>" call term_sendkeys(buf, end . "x") - call WaitForAssert({-> assert_equal('cmd: -helx', term_getline(buf, 1))}) + call WaitForAssert({-> assert_equal('cmd: -hzelx', term_getline(buf, 1))}) call term_sendkeys(buf, "\<C-U>exit\<CR>") call WaitForAssert({-> assert_equal('other buffer', term_getline(buf, 1))}) @@ -100,6 +107,28 @@ func Test_prompt_editing() call delete(scriptName) endfunc +func Test_prompt_switch_windows() + throw 'skipped: TODO' + call CanTestPromptBuffer() + let scriptName = 'XpromptSwitchWindows' + call WriteScript(scriptName) + + let buf = RunVimInTerminal('-S ' . scriptName, {'rows': 12}) + call WaitForAssert({-> assert_equal('cmd:', term_getline(buf, 1))}) + call WaitForAssert({-> assert_match('-- INSERT --', term_getline(buf, 12))}) + + call term_sendkeys(buf, "\<C-O>:call SwitchWindows()\<CR>") + call term_wait(buf, 50) + call WaitForAssert({-> assert_match('-- INSERT --', term_getline(buf, 12))}) + + call term_sendkeys(buf, "\<Esc>") + call term_wait(buf, 50) + call WaitForAssert({-> assert_match('^ *$', term_getline(buf, 12))}) + + call StopVimInTerminal(buf) + call delete(scriptName) +endfunc + func Test_prompt_garbage_collect() func MyPromptCallback(x, text) " NOP @@ -126,6 +155,14 @@ func Test_prompt_garbage_collect() bwipe! endfunc +func Test_prompt_backspace() + new + set buftype=prompt + call feedkeys("A123456\<Left>\<BS>\<Esc>", 'xt') + call assert_equal('% 12346', getline(1)) + bwipe! +endfunc + " Test for editing the prompt buffer func Test_prompt_buffer_edit() new @@ -145,10 +182,9 @@ func Test_prompt_buffer_edit() call assert_beeps("normal! \<C-X>") " pressing CTRL-W in the prompt buffer should trigger the window commands call assert_equal(1, winnr()) - " In Nvim, CTRL-W commands aren't usable from insert mode in a prompt buffer - " exe "normal A\<C-W>\<C-W>" - " call assert_equal(2, winnr()) - " wincmd w + exe "normal A\<C-W>\<C-W>" + call assert_equal(2, winnr()) + wincmd w close! call assert_equal(0, prompt_setprompt([], '')) endfunc @@ -165,9 +201,7 @@ func Test_prompt_buffer_getbufinfo() call assert_equal('This is a test: ', prompt_getprompt('%')) call prompt_setprompt( bufnr( '%' ), '' ) - " Nvim doesn't support method call syntax yet. - " call assert_equal('', '%'->prompt_getprompt()) - call assert_equal('', prompt_getprompt('%')) + call assert_equal('', '%'->prompt_getprompt()) call prompt_setprompt( bufnr( '%' ), 'Another: ' ) call assert_equal('Another: ', prompt_getprompt('%')) @@ -189,4 +223,38 @@ func Test_prompt_buffer_getbufinfo() %bwipe! endfunc +function! Test_prompt_while_writing_to_hidden_buffer() + throw 'skipped: TODO' + call CanTestPromptBuffer() + CheckUnix + + " Make a job continuously write to a hidden buffer, check that the prompt + " buffer is not affected. + let scriptName = 'XpromptscriptHiddenBuf' + let script =<< trim END + set buftype=prompt + call prompt_setprompt( bufnr(), 'cmd:' ) + let job = job_start(['/bin/sh', '-c', + \ 'while true; + \ do echo line; + \ sleep 0.1; + \ done'], #{out_io: 'buffer', out_name: ''}) + startinsert + END + eval script->writefile(scriptName) + + let buf = RunVimInTerminal('-S ' .. scriptName, {}) + call WaitForAssert({-> assert_equal('cmd:', term_getline(buf, 1))}) + + call term_sendkeys(buf, 'test') + call WaitForAssert({-> assert_equal('cmd:test', term_getline(buf, 1))}) + call term_sendkeys(buf, 'test') + call WaitForAssert({-> assert_equal('cmd:testtest', term_getline(buf, 1))}) + call term_sendkeys(buf, 'test') + call WaitForAssert({-> assert_equal('cmd:testtesttest', term_getline(buf, 1))}) + + call StopVimInTerminal(buf) + call delete(scriptName) +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_put.vim b/src/nvim/testdir/test_put.vim index 15745d5619..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') @@ -39,7 +41,7 @@ func Test_put_lines() call assert_equal(['Line 3', '', 'Line 1', 'Line2'], getline(1, '$')) " clean up bw! - call setreg('a', a[0], a[1]) + eval a[0]->setreg('a', a[1]) endfunc func Test_put_expr() @@ -111,3 +113,93 @@ func Test_put_p_indent_visual() call assert_equal('select that text', getline(2)) 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 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 b38a59e98f..29722ef09b 100644 --- a/src/nvim/testdir/test_quickfix.vim +++ b/src/nvim/testdir/test_quickfix.vim @@ -1,4 +1,4 @@ -" Test for the quickfix commands. +" Test for the quickfix feature. source check.vim CheckFeature quickfix @@ -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> @@ -169,8 +169,8 @@ func XlistTests(cchar) \ {'lnum':30,'col':15,'type':'W','filename':'Data/Text.hs','text':'FileWarning','nr':33,'valid':v:true}]) let l = split(execute('Xlist', ""), "\n") call assert_equal([' 1 Data.Text:10 col 5 warning 11: ModuleWarning', - \ ' 2 Data.Text:20 col 10 warning 22: ModuleWarning', - \ ' 3 Data/Text.hs:30 col 15 warning 33: FileWarning'], l) + \ ' 2 Data.Text:20 col 10 warning 22: ModuleWarning', + \ ' 3 Data/Text.hs:30 col 15 warning 33: FileWarning'], l) " For help entries in the quickfix list, only the filename without directory " should be displayed @@ -796,101 +796,102 @@ func ReadTestProtocol(name) endfunc func Test_locationlist() - enew + enew - augroup testgroup - au! - autocmd BufReadCmd test://* call ReadTestProtocol(expand("<amatch>")) - augroup END + augroup testgroup + au! + autocmd BufReadCmd test://* call ReadTestProtocol(expand("<amatch>")) + augroup END - let words = [ "foo", "bar", "baz", "quux", "shmoo", "spam", "eggs" ] + let words = [ "foo", "bar", "baz", "quux", "shmoo", "spam", "eggs" ] - let qflist = [] - for word in words - call add(qflist, {'filename': 'test://' . word . '.txt', 'text': 'file ' . word . '.txt', }) - " NOTE: problem 1: - " intentionally not setting 'lnum' so that the quickfix entries are not - " valid - call setloclist(0, qflist, ' ') - endfor + let qflist = [] + for word in words + call add(qflist, {'filename': 'test://' . word . '.txt', 'text': 'file ' . word . '.txt', }) + " NOTE: problem 1: + " intentionally not setting 'lnum' so that the quickfix entries are not + " valid + eval qflist->setloclist(0, ' ') + endfor - " Test A - lrewind - enew - lopen - 4lnext - vert split - wincmd L - lopen - wincmd p - lnext - let fileName = expand("%") - wincmd p - let locationListFileName = substitute(getline(line('.')), '\([^|]*\)|.*', '\1', '') - let fileName = substitute(fileName, '\\', '/', 'g') - let locationListFileName = substitute(locationListFileName, '\\', '/', 'g') - call assert_equal("test://bar.txt", fileName) - call assert_equal("test://bar.txt", locationListFileName) + " Test A + lrewind + enew + lopen + 4lnext + vert split + wincmd L + lopen + wincmd p + lnext + let fileName = expand("%") + wincmd p + let locationListFileName = substitute(getline(line('.')), '\([^|]*\)|.*', '\1', '') + let fileName = substitute(fileName, '\\', '/', 'g') + let locationListFileName = substitute(locationListFileName, '\\', '/', 'g') + call assert_equal("test://bar.txt", fileName) + call assert_equal("test://bar.txt", locationListFileName) - wincmd n | only + wincmd n | only - " Test B: - lrewind - lopen - 2 - exe "normal \<CR>" - wincmd p - 3 - exe "normal \<CR>" - wincmd p - 4 - exe "normal \<CR>" - call assert_equal(2, winnr('$')) - wincmd n | only + " Test B: + lrewind + lopen + 2 + exe "normal \<CR>" + wincmd p + 3 + exe "normal \<CR>" + wincmd p + 4 + exe "normal \<CR>" + call assert_equal(2, winnr('$')) + wincmd n | only - " Test C: - lrewind - lopen - " Let's move the location list window to the top to check whether it (the - " first window found) will be reused when we try to open new windows: - wincmd K - 2 - exe "normal \<CR>" - wincmd p - 3 - exe "normal \<CR>" - wincmd p - 4 - exe "normal \<CR>" - 1wincmd w - call assert_equal('quickfix', &buftype) - 2wincmd w - let bufferName = expand("%") - let bufferName = substitute(bufferName, '\\', '/', 'g') - call assert_equal('test://quux.txt', bufferName) + " Test C: + lrewind + lopen + " Let's move the location list window to the top to check whether it (the + " first window found) will be reused when we try to open new windows: + wincmd K + 2 + exe "normal \<CR>" + wincmd p + 3 + exe "normal \<CR>" + wincmd p + 4 + exe "normal \<CR>" + 1wincmd w + call assert_equal('quickfix', &buftype) + 2wincmd w + let bufferName = expand("%") + let bufferName = substitute(bufferName, '\\', '/', 'g') + call assert_equal('test://quux.txt', bufferName) - wincmd n | only + wincmd n | only - augroup! testgroup + augroup! testgroup endfunc func Test_locationlist_curwin_was_closed() - augroup testgroup - au! - autocmd BufReadCmd test_curwin.txt call R(expand("<amatch>")) - augroup END + augroup testgroup + au! + autocmd BufReadCmd test_curwin.txt call R(expand("<amatch>")) + augroup END - func! R(n) - quit - endfunc + func! R(n) + quit + endfunc - new - let q = [] - call add(q, {'filename': 'test_curwin.txt' }) - call setloclist(0, q) - call assert_fails('lrewind', 'E924:') + new + let q = [] + call add(q, {'filename': 'test_curwin.txt' }) + call setloclist(0, q) + call assert_fails('lrewind', 'E924:') - augroup! testgroup + augroup! testgroup + delfunc R endfunc func Test_locationlist_cross_tab_jump() @@ -1037,21 +1038,20 @@ func s:dir_stack_tests(cchar) let save_efm=&efm set efm=%DEntering\ dir\ '%f',%f:%l:%m,%XLeaving\ dir\ '%f' - let lines =<< trim [DATA] - Entering dir 'dir1/a' - habits2.txt:1:Nine Healthy Habits - Entering dir 'b' - habits3.txt:2:0 Hours of television - habits2.txt:7:5 Small meals - Entering dir 'dir1/c' - habits4.txt:3:1 Hour of exercise - Leaving dir 'dir1/c' - Leaving dir 'dir1/a' - habits1.txt:4:2 Liters of water - Entering dir 'dir2' - habits5.txt:5:3 Cups of hot green tea - Leaving dir 'dir2 - [DATA] + let lines = ["Entering dir 'dir1/a'", + \ 'habits2.txt:1:Nine Healthy Habits', + \ "Entering dir 'b'", + \ 'habits3.txt:2:0 Hours of television', + \ 'habits2.txt:7:5 Small meals', + \ "Entering dir 'dir1/c'", + \ 'habits4.txt:3:1 Hour of exercise', + \ "Leaving dir 'dir1/c'", + \ "Leaving dir 'dir1/a'", + \ 'habits1.txt:4:2 Liters of water', + \ "Entering dir 'dir2'", + \ 'habits5.txt:5:3 Cups of hot green tea', + \ "Leaving dir 'dir2'" + \] Xexpr "" for l in lines @@ -1085,19 +1085,18 @@ func Test_efm_dirstack() call mkdir('dir1/c') call mkdir('dir2') - let lines =<< trim [DATA] - Nine Healthy Habits, - 0 Hours of television, - 1 Hour of exercise, - 2 Liters of water, - 3 Cups of hot green tea, - 4 Short mental breaks, - 5 Small meals, - 6 AM wake up time, - 7 Minutes of laughter, - 8 Hours of sleep (at least), - 9 PM end of the day and off to bed - [DATA] + let lines = ["Nine Healthy Habits", + \ "0 Hours of television", + \ "1 Hour of exercise", + \ "2 Liters of water", + \ "3 Cups of hot green tea", + \ "4 Short mental breaks", + \ "5 Small meals", + \ "6 AM wake up time", + \ "7 Minutes of laughter", + \ "8 Hours of sleep (at least)", + \ "9 PM end of the day and off to bed" + \ ] call writefile(lines, 'habits1.txt') call writefile(lines, 'dir1/a/habits2.txt') @@ -1219,6 +1218,7 @@ func Test_efm2() (67,3) warning: 's' already defined -- [DATA] + set efm=%+P[%f]%r,(%l\\,%c)%*[\ ]%t%*[^:]:\ %m,%+Q--%r " To exercise the push/pop file functionality in quickfix, the test files " need to be created. @@ -1279,20 +1279,21 @@ func Test_efm2() " Test for %A, %C and other formats let lines =<< trim [DATA] - ============================================================== - FAIL: testGetTypeIdCachesResult (dbfacadeTest.DjsDBFacadeTest) - -------------------------------------------------------------- - Traceback (most recent call last): - File "unittests/dbfacadeTest.py", line 89, in testFoo - self.assertEquals(34, dtid) - File "/usr/lib/python2.2/unittest.py", line 286, in - failUnlessEqual - raise self.failureException, \\ - W:AssertionError: 34 != 33 - - -------------------------------------------------------------- - Ran 27 tests in 0.063s + ============================================================== + FAIL: testGetTypeIdCachesResult (dbfacadeTest.DjsDBFacadeTest) + -------------------------------------------------------------- + Traceback (most recent call last): + File "unittests/dbfacadeTest.py", line 89, in testFoo + self.assertEquals(34, dtid) + File "/usr/lib/python2.2/unittest.py", line 286, in + failUnlessEqual + raise self.failureException, \\ + W:AssertionError: 34 != 33 + + -------------------------------------------------------------- + Ran 27 tests in 0.063s [DATA] + set efm=%C\ %.%#,%A\ \ File\ \"%f\"\\,\ line\ %l%.%#,%Z%[%^\ ]%\\@=%t:%m cgetexpr lines let l = getqflist() @@ -1384,6 +1385,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' @@ -1632,7 +1656,7 @@ func XquickfixSetListWithAct(cchar) \ {'filename': 'fnameD', 'text': 'D'}, \ {'filename': 'fnameE', 'text': 'E'}] - " {action} is unspecified. Same as specifing ' '. + " {action} is unspecified. Same as specifying ' '. new | only silent! Xnewer 99 call g:Xsetlist(list1) @@ -1699,7 +1723,7 @@ endfunc func Test_setqflist_invalid_nr() " The following command used to crash Vim - call setqflist([], ' ', {'nr' : $XXX_DOES_NOT_EXIST}) + eval []->setqflist(' ', {'nr' : $XXX_DOES_NOT_EXIST}) endfunc func Test_setqflist_user_sets_buftype() @@ -1914,6 +1938,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 @@ -1996,6 +2021,7 @@ func s:test_xgrep(cchar) enew set makeef=Temp_File_## silent Xgrepadd GrepAdd_Test_Text: test_quickfix.vim + call assert_true(len(g:Xgetlist()) == 9) " Try with 'grepprg' set to 'internal' set grepprg=internal @@ -2004,12 +2030,12 @@ func s:test_xgrep(cchar) call assert_true(len(g:Xgetlist()) == 9) set grepprg&vim - call writefile(['Vim'], 'XtestTempFile') - set makeef=XtestTempFile - silent Xgrep Grep_Test_Text: test_quickfix.vim - call assert_equal(5, len(g:Xgetlist())) - call assert_false(filereadable('XtestTempFile')) - set makeef&vim + call writefile(['Vim'], 'XtestTempFile') + set makeef=XtestTempFile + silent Xgrep Grep_Test_Text: test_quickfix.vim + call assert_equal(5, len(g:Xgetlist())) + call assert_false(filereadable('XtestTempFile')) + set makeef&vim endfunc func Test_grep() @@ -2682,7 +2708,7 @@ func Test_cwindow_jump() " Open a new window and create a location list " Open the location list window and close the other window " Jump to an entry. - " Should create a new window and jump to the entry. The scrtach buffer + " Should create a new window and jump to the entry. The scratch buffer " should not be used. enew | only set buftype=nofile @@ -2715,7 +2741,26 @@ 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 + " open the quickfix buffer in two windows and jump to an entry. Should open + " the file in the first quickfix window. + enew | only + copen + let bnum = bufnr('') + exe 'sbuffer ' . bnum + wincmd b + cfirst + call assert_equal(2, winnr()) + call assert_equal('F1', bufname('')) + enew | only + exe 'sb' bnum + exe 'botright sb' bnum + wincmd t + clast + call assert_equal(2, winnr()) + call assert_equal('quickfix', getwinvar(1, '&buftype')) + call assert_equal('quickfix', getwinvar(3, '&buftype')) + + " 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') @@ -3524,20 +3569,21 @@ func Xgetlist_empty_tests(cchar) call assert_equal(0, g:Xgetlist({'changedtick' : 0}).changedtick) if a:cchar == 'c' call assert_equal({'context' : '', 'id' : 0, 'idx' : 0, - \ 'items' : [], 'nr' : 0, 'size' : 0, + \ 'items' : [], 'nr' : 0, 'size' : 0, 'qfbufnr' : 0, \ 'title' : '', 'winid' : 0, 'changedtick': 0, \ 'quickfixtextfunc' : ''}, g:Xgetlist({'all' : 0})) else call assert_equal({'context' : '', 'id' : 0, 'idx' : 0, \ 'items' : [], 'nr' : 0, 'size' : 0, 'title' : '', \ 'winid' : 0, 'changedtick': 0, 'filewinid' : 0, - \ 'quickfixtextfunc' : ''}, + \ 'qfbufnr' : 0, 'quickfixtextfunc' : ''}, \ g:Xgetlist({'all' : 0})) endif " Quickfix window with empty stack silent! Xopen let qfwinid = (a:cchar == 'c') ? win_getid() : 0 + let qfbufnr = (a:cchar == 'c') ? bufnr('') : 0 call assert_equal(qfwinid, g:Xgetlist({'winid' : 0}).winid) Xclose @@ -3569,12 +3615,12 @@ func Xgetlist_empty_tests(cchar) if a:cchar == 'c' call assert_equal({'context' : '', 'id' : 0, 'idx' : 0, 'items' : [], \ 'nr' : 0, 'size' : 0, 'title' : '', 'winid' : 0, - \ 'quickfixtextfunc' : '', + \ 'qfbufnr' : qfbufnr, 'quickfixtextfunc' : '', \ 'changedtick' : 0}, g:Xgetlist({'id' : qfid, 'all' : 0})) else call assert_equal({'context' : '', 'id' : 0, 'idx' : 0, 'items' : [], \ 'nr' : 0, 'size' : 0, 'title' : '', 'winid' : 0, - \ 'changedtick' : 0, 'filewinid' : 0, + \ 'changedtick' : 0, 'filewinid' : 0, 'qfbufnr' : 0, \ 'quickfixtextfunc' : ''}, \ g:Xgetlist({'id' : qfid, 'all' : 0})) endif @@ -3592,12 +3638,12 @@ func Xgetlist_empty_tests(cchar) if a:cchar == 'c' call assert_equal({'context' : '', 'id' : 0, 'idx' : 0, 'items' : [], \ 'nr' : 0, 'size' : 0, 'title' : '', 'winid' : 0, - \ 'changedtick' : 0, + \ 'changedtick' : 0, 'qfbufnr' : qfbufnr, \ 'quickfixtextfunc' : ''}, g:Xgetlist({'nr' : 5, 'all' : 0})) else call assert_equal({'context' : '', 'id' : 0, 'idx' : 0, 'items' : [], \ 'nr' : 0, 'size' : 0, 'title' : '', 'winid' : 0, - \ 'changedtick' : 0, 'filewinid' : 0, + \ 'changedtick' : 0, 'filewinid' : 0, 'qfbufnr' : 0, \ 'quickfixtextfunc' : ''}, g:Xgetlist({'nr' : 5, 'all' : 0})) endif endfunc @@ -4336,7 +4382,7 @@ func Test_splitview() new | only " When split opening files from a helpgrep location list window, a new help - " window should be opend with a copy of the location list. + " window should be opened with a copy of the location list. lhelpgrep window let locid = getloclist(0, {'id' : 0}).id lwindow @@ -4432,11 +4478,19 @@ func Xqfbuf_test(cchar) Xclose " Even after the quickfix window is closed, the buffer should be loaded call assert_true(bufloaded(qfbnum)) + call assert_true(qfbnum, g:Xgetlist({'qfbufnr' : 0}).qfbufnr) Xopen " Buffer should be reused when opening the window again call assert_equal(qfbnum, bufnr('')) Xclose + " When quickfix buffer is wiped out, getqflist() should return 0 + %bw! + Xexpr "" + Xopen + bw! + call assert_equal(0, g:Xgetlist({'qfbufnr': 0}).qfbufnr) + if a:cchar == 'l' %bwipe " For a location list, when both the file window and the location list @@ -4450,7 +4504,7 @@ func Xqfbuf_test(cchar) close " When the location list window is closed, the buffer name should not " change to 'Quickfix List' - call assert_match(qfbnum . ' h- "\[Location List]"', execute('ls')) + call assert_match(qfbnum . 'u h- "\[Location List]"', execute('ls!')) call assert_true(bufloaded(qfbnum)) " After deleting a location list buffer using ":bdelete", opening the @@ -4467,6 +4521,7 @@ func Xqfbuf_test(cchar) " removed call setloclist(0, [], 'f') call assert_false(bufexists(qfbnum)) + call assert_equal(0, getloclist(0, {'qfbufnr' : 0}).qfbufnr) " When the location list is freed with the location list window open, the " location list buffer should not be lost. It should be reused when the @@ -4491,11 +4546,36 @@ func Xqfbuf_test(cchar) endfunc func Test_qfbuf() - throw 'skipped: enable after porting patch 8.1.0877' call Xqfbuf_test('c') call Xqfbuf_test('l') endfunc +" If there is an autocmd to use only one window, then opening the location +" list window used to crash Vim. +func Test_winonly_autocmd() + call s:create_test_file('Xtest1') + " Autocmd to show only one Vim window at a time + autocmd WinEnter * only + new + " Load the location list + lexpr "Xtest1:5:Line5\nXtest1:10:Line10\nXtest1:15:Line15" + let loclistid = getloclist(0, {'id' : 0}).id + " Open the location list window. Only this window will be shown and the file + " window is closed. + lopen + call assert_equal(loclistid, getloclist(0, {'id' : 0}).id) + " Jump to an entry in the location list and make sure that the cursor is + " positioned correctly. + ll 3 + call assert_equal(loclistid, getloclist(0, {'id' : 0}).id) + call assert_equal('Xtest1', bufname('')) + call assert_equal(15, line('.')) + " Cleanup + autocmd! WinEnter + new | only + call delete('Xtest1') +endfunc + " Test to make sure that an empty quickfix buffer is not reused for loading " a normal buffer. func Test_empty_qfbuf() @@ -5027,6 +5107,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) @@ -5137,16 +5263,14 @@ func Xtest_qftextfunc(cchar) " Non-existing function set quickfixtextfunc=Tabc - " call assert_fails("Xexpr ['F1:10:2:green', 'F1:20:4:blue']", 'E117:') - Xexpr ['F1:10:2:green', 'F1:20:4:blue']" + call assert_fails("Xexpr ['F1:10:2:green', 'F1:20:4:blue']", 'E117:') call assert_fails("Xwindow", 'E117:') Xclose set quickfixtextfunc& " set option to a non-function set quickfixtextfunc=[10,\ 20] - " call assert_fails("Xexpr ['F1:10:2:green', 'F1:20:4:blue']", 'E117:') - Xexpr ['F1:10:2:green', 'F1:20:4:blue']" + call assert_fails("Xexpr ['F1:10:2:green', 'F1:20:4:blue']", 'E117:') call assert_fails("Xwindow", 'E117:') Xclose set quickfixtextfunc& @@ -5156,8 +5280,7 @@ func Xtest_qftextfunc(cchar) return a:a .. a:b .. a:c endfunc set quickfixtextfunc=Xqftext - " call assert_fails("Xexpr ['F1:10:2:green', 'F1:20:4:blue']", 'E119:') - Xexpr ['F1:10:2:green', 'F1:20:4:blue']" + call assert_fails("Xexpr ['F1:10:2:green', 'F1:20:4:blue']", 'E119:') call assert_fails("Xwindow", 'E119:') Xclose @@ -5166,9 +5289,8 @@ func Xtest_qftextfunc(cchar) return ['one', [], 'two'] endfunc set quickfixtextfunc=Xqftext2 - " call assert_fails("Xexpr ['F1:10:2:green', 'F1:20:4:blue', 'F1:30:6:red']", - " \ 'E730:') - Xexpr ['F1:10:2:green', 'F1:20:4:blue', 'F1:30:6:red'] + call assert_fails("Xexpr ['F1:10:2:green', 'F1:20:4:blue', 'F1:30:6:red']", + \ 'E730:') call assert_fails('Xwindow', 'E730:') call assert_equal(['one', 'F1|20 col 4| blue', 'F1|30 col 6| red'], \ getline(1, '$')) @@ -5332,4 +5454,81 @@ func Test_win_gettype() lclose endfunc +" Test for opening the quickfix window in two tab pages and then closing one +" of the quickfix windows. This should not make the quickfix buffer unlisted. +" (github issue #9300). +func Test_two_qf_windows() + cexpr "F1:1:line1" + copen + tabnew + copen + call assert_true(&buflisted) + cclose + tabfirst + call assert_true(&buflisted) + let bnum = bufnr() + cclose + " if all the quickfix windows are closed, then buffer should be unlisted. + call assert_false(buflisted(bnum)) + %bw! + + " Repeat the test for a location list + lexpr "F2:2:line2" + lopen + let bnum = bufnr() + tabnew + exe "buffer" bnum + tabfirst + lclose + tablast + call assert_true(buflisted(bnum)) + tabclose + lopen + call assert_true(buflisted(bnum)) + lclose + call assert_false(buflisted(bnum)) + %bw! +endfunc + +" Weird sequence of commands that caused entering a wiped-out buffer +func Test_lopen_bwipe() + func R() + silent! tab lopen + e x + silent! lfile + endfunc + + cal R() + cal R() + cal R() + bw! + delfunc R +endfunc + +" Another sequence of commands that caused all buffers to be wiped out +func Test_lopen_bwipe_all() + let lines =<< trim END + func R() + silent! tab lopen + e foo + silent! lfile + endfunc + cal R() + exe "norm \<C-W>\<C-V>0" + cal R() + bwipe + + call writefile(['done'], 'Xresult') + qall! + END + call writefile(lines, 'Xscript') + if RunVim([], [], '--clean -n -S Xscript') + call assert_equal(['done'], readfile('Xresult')) + endif + + call delete('Xscript') + call delete('Xresult') +endfunc + + " vim: shiftwidth=2 sts=2 expandtab 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_regex_char_classes.vim b/src/nvim/testdir/test_regex_char_classes.vim index c1a4202c2b..b0d76a15e2 100644 --- a/src/nvim/testdir/test_regex_char_classes.vim +++ b/src/nvim/testdir/test_regex_char_classes.vim @@ -66,22 +66,22 @@ func Test_regex_char_classes() let save_enc = &encoding set encoding=utf-8 - let input = "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b" + let input = "\t\<C-L>\r !\"#$%&'()#+'-./0123456789:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b" " Format is [cmd_to_run, expected_output] let tests = [ \ [':s/\%#=0\d//g', - \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b"], + \ "\t\<C-L>\r !\"#$%&'()#+'-./:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b"], \ [':s/\%#=1\d//g', - \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b"], + \ "\t\<C-L>\r !\"#$%&'()#+'-./:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b"], \ [':s/\%#=2\d//g', - \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b"], + \ "\t\<C-L>\r !\"#$%&'()#+'-./:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b"], \ [':s/\%#=0[0-9]//g', - \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b"], + \ "\t\<C-L>\r !\"#$%&'()#+'-./:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b"], \ [':s/\%#=1[0-9]//g', - \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b"], + \ "\t\<C-L>\r !\"#$%&'()#+'-./:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b"], \ [':s/\%#=2[0-9]//g', - \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b"], + \ "\t\<C-L>\r !\"#$%&'()#+'-./:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b"], \ [':s/\%#=0\D//g', \ "0123456789"], \ [':s/\%#=1\D//g', @@ -95,17 +95,17 @@ func Test_regex_char_classes() \ [':s/\%#=2[^0-9]//g', \ "0123456789"], \ [':s/\%#=0\o//g', - \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./89:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b"], + \ "\t\<C-L>\r !\"#$%&'()#+'-./89:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b"], \ [':s/\%#=1\o//g', - \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./89:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b"], + \ "\t\<C-L>\r !\"#$%&'()#+'-./89:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b"], \ [':s/\%#=2\o//g', - \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./89:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b"], + \ "\t\<C-L>\r !\"#$%&'()#+'-./89:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b"], \ [':s/\%#=0[0-7]//g', - \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./89:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b"], + \ "\t\<C-L>\r !\"#$%&'()#+'-./89:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b"], \ [':s/\%#=1[0-7]//g', - \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./89:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b"], + \ "\t\<C-L>\r !\"#$%&'()#+'-./89:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b"], \ [':s/\%#=2[0-7]//g', - \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./89:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b"], + \ "\t\<C-L>\r !\"#$%&'()#+'-./89:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b"], \ [':s/\%#=0\O//g', \ "01234567"], \ [':s/\%#=1\O//g', @@ -119,17 +119,17 @@ func Test_regex_char_classes() \ [':s/\%#=2[^0-7]//g', \ "01234567"], \ [':s/\%#=0\x//g', - \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@GHIXYZ[\]^_`ghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b"], + \ "\t\<C-L>\r !\"#$%&'()#+'-./:;<=>?@GHIXYZ[\]^_`ghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b"], \ [':s/\%#=1\x//g', - \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@GHIXYZ[\]^_`ghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b"], + \ "\t\<C-L>\r !\"#$%&'()#+'-./:;<=>?@GHIXYZ[\]^_`ghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b"], \ [':s/\%#=2\x//g', - \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@GHIXYZ[\]^_`ghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b"], + \ "\t\<C-L>\r !\"#$%&'()#+'-./:;<=>?@GHIXYZ[\]^_`ghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b"], \ [':s/\%#=0[0-9A-Fa-f]//g', - \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@GHIXYZ[\]^_`ghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b"], + \ "\t\<C-L>\r !\"#$%&'()#+'-./:;<=>?@GHIXYZ[\]^_`ghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b"], \ [':s/\%#=1[0-9A-Fa-f]//g', - \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@GHIXYZ[\]^_`ghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b"], + \ "\t\<C-L>\r !\"#$%&'()#+'-./:;<=>?@GHIXYZ[\]^_`ghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b"], \ [':s/\%#=2[0-9A-Fa-f]//g', - \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@GHIXYZ[\]^_`ghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b"], + \ "\t\<C-L>\r !\"#$%&'()#+'-./:;<=>?@GHIXYZ[\]^_`ghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b"], \ [':s/\%#=0\X//g', \ "0123456789ABCDEFabcdef"], \ [':s/\%#=1\X//g', @@ -143,17 +143,17 @@ func Test_regex_char_classes() \ [':s/\%#=2[^0-9A-Fa-f]//g', \ "0123456789ABCDEFabcdef"], \ [':s/\%#=0\w//g', - \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@[\]^`{|}~\<C-?>\u0080\u0082\u0090\u009b"], + \ "\t\<C-L>\r !\"#$%&'()#+'-./:;<=>?@[\]^`{|}~\<C-?>\u0080\u0082\u0090\u009b"], \ [':s/\%#=1\w//g', - \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@[\]^`{|}~\<C-?>\u0080\u0082\u0090\u009b"], + \ "\t\<C-L>\r !\"#$%&'()#+'-./:;<=>?@[\]^`{|}~\<C-?>\u0080\u0082\u0090\u009b"], \ [':s/\%#=2\w//g', - \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@[\]^`{|}~\<C-?>\u0080\u0082\u0090\u009b"], + \ "\t\<C-L>\r !\"#$%&'()#+'-./:;<=>?@[\]^`{|}~\<C-?>\u0080\u0082\u0090\u009b"], \ [':s/\%#=0[0-9A-Za-z_]//g', - \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@[\]^`{|}~\<C-?>\u0080\u0082\u0090\u009b"], + \ "\t\<C-L>\r !\"#$%&'()#+'-./:;<=>?@[\]^`{|}~\<C-?>\u0080\u0082\u0090\u009b"], \ [':s/\%#=1[0-9A-Za-z_]//g', - \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@[\]^`{|}~\<C-?>\u0080\u0082\u0090\u009b"], + \ "\t\<C-L>\r !\"#$%&'()#+'-./:;<=>?@[\]^`{|}~\<C-?>\u0080\u0082\u0090\u009b"], \ [':s/\%#=2[0-9A-Za-z_]//g', - \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./:;<=>?@[\]^`{|}~\<C-?>\u0080\u0082\u0090\u009b"], + \ "\t\<C-L>\r !\"#$%&'()#+'-./:;<=>?@[\]^`{|}~\<C-?>\u0080\u0082\u0090\u009b"], \ [':s/\%#=0\W//g', \ "0123456789ABCDEFGHIXYZ_abcdefghiwxyz"], \ [':s/\%#=1\W//g', @@ -167,17 +167,17 @@ func Test_regex_char_classes() \ [':s/\%#=2[^0-9A-Za-z_]//g', \ "0123456789ABCDEFGHIXYZ_abcdefghiwxyz"], \ [':s/\%#=0\h//g', - \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^`{|}~\<C-?>\u0080\u0082\u0090\u009b"], + \ "\t\<C-L>\r !\"#$%&'()#+'-./0123456789:;<=>?@[\]^`{|}~\<C-?>\u0080\u0082\u0090\u009b"], \ [':s/\%#=1\h//g', - \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^`{|}~\<C-?>\u0080\u0082\u0090\u009b"], + \ "\t\<C-L>\r !\"#$%&'()#+'-./0123456789:;<=>?@[\]^`{|}~\<C-?>\u0080\u0082\u0090\u009b"], \ [':s/\%#=2\h//g', - \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^`{|}~\<C-?>\u0080\u0082\u0090\u009b"], + \ "\t\<C-L>\r !\"#$%&'()#+'-./0123456789:;<=>?@[\]^`{|}~\<C-?>\u0080\u0082\u0090\u009b"], \ [':s/\%#=0[A-Za-z_]//g', - \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^`{|}~\<C-?>\u0080\u0082\u0090\u009b"], + \ "\t\<C-L>\r !\"#$%&'()#+'-./0123456789:;<=>?@[\]^`{|}~\<C-?>\u0080\u0082\u0090\u009b"], \ [':s/\%#=1[A-Za-z_]//g', - \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^`{|}~\<C-?>\u0080\u0082\u0090\u009b"], + \ "\t\<C-L>\r !\"#$%&'()#+'-./0123456789:;<=>?@[\]^`{|}~\<C-?>\u0080\u0082\u0090\u009b"], \ [':s/\%#=2[A-Za-z_]//g', - \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^`{|}~\<C-?>\u0080\u0082\u0090\u009b"], + \ "\t\<C-L>\r !\"#$%&'()#+'-./0123456789:;<=>?@[\]^`{|}~\<C-?>\u0080\u0082\u0090\u009b"], \ [':s/\%#=0\H//g', \ "ABCDEFGHIXYZ_abcdefghiwxyz"], \ [':s/\%#=1\H//g', @@ -191,17 +191,17 @@ func Test_regex_char_classes() \ [':s/\%#=2[^A-Za-z_]//g', \ "ABCDEFGHIXYZ_abcdefghiwxyz"], \ [':s/\%#=0\a//g', - \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^_`{|}~\<C-?>\u0080\u0082\u0090\u009b"], + \ "\t\<C-L>\r !\"#$%&'()#+'-./0123456789:;<=>?@[\]^_`{|}~\<C-?>\u0080\u0082\u0090\u009b"], \ [':s/\%#=1\a//g', - \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^_`{|}~\<C-?>\u0080\u0082\u0090\u009b"], + \ "\t\<C-L>\r !\"#$%&'()#+'-./0123456789:;<=>?@[\]^_`{|}~\<C-?>\u0080\u0082\u0090\u009b"], \ [':s/\%#=2\a//g', - \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^_`{|}~\<C-?>\u0080\u0082\u0090\u009b"], + \ "\t\<C-L>\r !\"#$%&'()#+'-./0123456789:;<=>?@[\]^_`{|}~\<C-?>\u0080\u0082\u0090\u009b"], \ [':s/\%#=0[A-Za-z]//g', - \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^_`{|}~\<C-?>\u0080\u0082\u0090\u009b"], + \ "\t\<C-L>\r !\"#$%&'()#+'-./0123456789:;<=>?@[\]^_`{|}~\<C-?>\u0080\u0082\u0090\u009b"], \ [':s/\%#=1[A-Za-z]//g', - \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^_`{|}~\<C-?>\u0080\u0082\u0090\u009b"], + \ "\t\<C-L>\r !\"#$%&'()#+'-./0123456789:;<=>?@[\]^_`{|}~\<C-?>\u0080\u0082\u0090\u009b"], \ [':s/\%#=2[A-Za-z]//g', - \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^_`{|}~\<C-?>\u0080\u0082\u0090\u009b"], + \ "\t\<C-L>\r !\"#$%&'()#+'-./0123456789:;<=>?@[\]^_`{|}~\<C-?>\u0080\u0082\u0090\u009b"], \ [':s/\%#=0\A//g', \ "ABCDEFGHIXYZabcdefghiwxyz"], \ [':s/\%#=1\A//g', @@ -215,17 +215,17 @@ func Test_regex_char_classes() \ [':s/\%#=2[^A-Za-z]//g', \ "ABCDEFGHIXYZabcdefghiwxyz"], \ [':s/\%#=0\l//g', - \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@ABCDEFGHIXYZ[\]^_`{|}~\<C-?>\u0080\u0082\u0090\u009b"], + \ "\t\<C-L>\r !\"#$%&'()#+'-./0123456789:;<=>?@ABCDEFGHIXYZ[\]^_`{|}~\<C-?>\u0080\u0082\u0090\u009b"], \ [':s/\%#=1\l//g', - \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@ABCDEFGHIXYZ[\]^_`{|}~\<C-?>\u0080\u0082\u0090\u009b"], + \ "\t\<C-L>\r !\"#$%&'()#+'-./0123456789:;<=>?@ABCDEFGHIXYZ[\]^_`{|}~\<C-?>\u0080\u0082\u0090\u009b"], \ [':s/\%#=2\l//g', - \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@ABCDEFGHIXYZ[\]^_`{|}~\<C-?>\u0080\u0082\u0090\u009b"], + \ "\t\<C-L>\r !\"#$%&'()#+'-./0123456789:;<=>?@ABCDEFGHIXYZ[\]^_`{|}~\<C-?>\u0080\u0082\u0090\u009b"], \ [':s/\%#=0[a-z]//g', - \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@ABCDEFGHIXYZ[\]^_`{|}~\<C-?>\u0080\u0082\u0090\u009b"], + \ "\t\<C-L>\r !\"#$%&'()#+'-./0123456789:;<=>?@ABCDEFGHIXYZ[\]^_`{|}~\<C-?>\u0080\u0082\u0090\u009b"], \ [':s/\%#=1[a-z]//g', - \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@ABCDEFGHIXYZ[\]^_`{|}~\<C-?>\u0080\u0082\u0090\u009b"], + \ "\t\<C-L>\r !\"#$%&'()#+'-./0123456789:;<=>?@ABCDEFGHIXYZ[\]^_`{|}~\<C-?>\u0080\u0082\u0090\u009b"], \ [':s/\%#=2[a-z]//g', - \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@ABCDEFGHIXYZ[\]^_`{|}~\<C-?>\u0080\u0082\u0090\u009b"], + \ "\t\<C-L>\r !\"#$%&'()#+'-./0123456789:;<=>?@ABCDEFGHIXYZ[\]^_`{|}~\<C-?>\u0080\u0082\u0090\u009b"], \ [':s/\%#=0\L//g', \ "abcdefghiwxyz"], \ [':s/\%#=1\L//g', @@ -239,17 +239,17 @@ func Test_regex_char_classes() \ [':s/\%#=2[^a-z]//g', \ "abcdefghiwxyz"], \ [':s/\%#=0\u//g', - \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b"], + \ "\t\<C-L>\r !\"#$%&'()#+'-./0123456789:;<=>?@[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b"], \ [':s/\%#=1\u//g', - \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b"], + \ "\t\<C-L>\r !\"#$%&'()#+'-./0123456789:;<=>?@[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b"], \ [':s/\%#=2\u//g', - \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b"], + \ "\t\<C-L>\r !\"#$%&'()#+'-./0123456789:;<=>?@[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b"], \ [':s/\%#=0[A-Z]//g', - \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b"], + \ "\t\<C-L>\r !\"#$%&'()#+'-./0123456789:;<=>?@[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b"], \ [':s/\%#=1[A-Z]//g', - \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b"], + \ "\t\<C-L>\r !\"#$%&'()#+'-./0123456789:;<=>?@[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b"], \ [':s/\%#=2[A-Z]//g', - \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./0123456789:;<=>?@[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b"], + \ "\t\<C-L>\r !\"#$%&'()#+'-./0123456789:;<=>?@[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b"], \ [':s/\%#=0\U//g', \ "ABCDEFGHIXYZ"], \ [':s/\%#=1\U//g', @@ -269,11 +269,11 @@ func Test_regex_char_classes() \ [':s/\%#=2\%' . line('.') . 'l^\t...//g', \ "!\"#$%&'()#+'-./0123456789:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz{|}~\<C-?>\u0080\u0082\u0090\u009b"], \ [':s/\%#=0[0-z]//g', - \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./{|}~\<C-?>\u0080\u0082\u0090\u009b"], + \ "\t\<C-L>\r !\"#$%&'()#+'-./{|}~\<C-?>\u0080\u0082\u0090\u009b"], \ [':s/\%#=1[0-z]//g', - \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./{|}~\<C-?>\u0080\u0082\u0090\u009b"], + \ "\t\<C-L>\r !\"#$%&'()#+'-./{|}~\<C-?>\u0080\u0082\u0090\u009b"], \ [':s/\%#=2[0-z]//g', - \ "\t\<C-L>\<C-M> !\"#$%&'()#+'-./{|}~\<C-?>\u0080\u0082\u0090\u009b"], + \ "\t\<C-L>\r !\"#$%&'()#+'-./{|}~\<C-?>\u0080\u0082\u0090\u009b"], \ [':s/\%#=0[^0-z]//g', \ "0123456789:;<=>?@ABCDEFGHIXYZ[\]^_`abcdefghiwxyz"], \ [':s/\%#=1[^0-z]//g', diff --git a/src/nvim/testdir/test_regexp_latin.vim b/src/nvim/testdir/test_regexp_latin.vim index 712f1e6025..13e44b090f 100644 --- a/src/nvim/testdir/test_regexp_latin.vim +++ b/src/nvim/testdir/test_regexp_latin.vim @@ -6,7 +6,10 @@ scriptencoding latin1 source check.vim func s:equivalence_test() - let str = "AÀÁÂÃÄÅ B C D EÈÉÊË F G H IÌÍÎÏ J K L M NÑ OÒÓÔÕÖØ P Q R S T UÙÚÛÜ V W X YÝ Z aàáâãäå b c d eèéêë f g h iìíîï j k l m nñ oòóôõöø p q r s t uùúûü v w x yýÿ z" + let str = 'A B C D E F G H I J K L M N O P Q R S T U V W X Y Z ' + \ .. 'a b c d e f g h i j k l m n o p q r s t u v w x y z ' + \ .. "0 1 2 3 4 5 6 7 8 9 " + \ .. "` ~ ! ? ; : . , / \\ ' \" | < > [ ] { } ( ) @ # $ % ^ & * _ - + \b \e \f \n \r \t" let groups = split(str) for group1 in groups for c in split(group1, '\zs') @@ -337,7 +340,7 @@ func Test_regexp_single_line_pat() call add(tl, [2, '\v((ab)|c*)+', 'abcccaba', 'abcccab', '', 'ab']) call add(tl, [2, '\v(a(c*)+b)+', 'acbababaaa', 'acbabab', 'ab', '']) call add(tl, [2, '\v(a|b*)+', 'aaaa', 'aaaa', '']) - call add(tl, [2, '\p*', 'aá ', 'aá ']) + call add(tl, [2, '\p*', 'a ', 'a ']) " Test greedy-ness and lazy-ness call add(tl, [2, 'a\{-2,7}','aaaaaaaaaaaaa', 'aa']) @@ -787,4 +790,125 @@ func Test_regexp_error() set re& endfunc +" Check patterns matching cursor position. +func s:curpos_test2() + new + call setline(1, ['1', '2 foobar eins zwei drei vier fnf sechse', + \ '3 foobar eins zwei drei vier fnf sechse', + \ '4 foobar eins zwei drei vier fnf sechse', + \ '5 foobar eins zwei drei vier fnf sechse', + \ '6 foobar eins zwei drei vier fnf sechse', + \ '7 foobar eins zwei drei vier fnf sechse']) + call setpos('.', [0, 2, 10, 0]) + s/\%.c.*//g + call setpos('.', [0, 3, 15, 0]) + s/\%.l.*//g + call setpos('.', [0, 5, 3, 0]) + s/\%.v.*/_/g + call assert_equal(['1', + \ '2 foobar ', + \ '', + \ '4 foobar eins zwei drei vier fnf sechse', + \ '5 _', + \ '6 foobar eins zwei drei vier fnf sechse', + \ '7 foobar eins zwei drei vier fnf sechse'], + \ getline(1, '$')) + call assert_fails('call search("\\%.1l")', 'E1204:') + call assert_fails('call search("\\%.1c")', 'E1204:') + call assert_fails('call search("\\%.1v")', 'E1204:') + bwipe! +endfunc + +" Check patterns matching before or after cursor position. +func s:curpos_test3() + new + call setline(1, ['1', '2 foobar eins zwei drei vier fnf sechse', + \ '3 foobar eins zwei drei vier fnf sechse', + \ '4 foobar eins zwei drei vier fnf sechse', + \ '5 foobar eins zwei drei vier fnf sechse', + \ '6 foobar eins zwei drei vier fnf sechse', + \ '7 foobar eins zwei drei vier fnf sechse']) + call setpos('.', [0, 2, 10, 0]) + " Note: This removes all columns, except for the column directly in front of + " the cursor. Bug???? + :s/^.*\%<.c// + call setpos('.', [0, 3, 10, 0]) + :s/\%>.c.*$// + call setpos('.', [0, 5, 4, 0]) + " Note: This removes all columns, except for the column directly in front of + " the cursor. Bug???? + :s/^.*\%<.v/_/ + call setpos('.', [0, 6, 4, 0]) + :s/\%>.v.*$/_/ + call assert_equal(['1', + \ ' eins zwei drei vier fnf sechse', + \ '3 foobar e', + \ '4 foobar eins zwei drei vier fnf sechse', + \ '_foobar eins zwei drei vier fnf sechse', + \ '6 fo_', + \ '7 foobar eins zwei drei vier fnf sechse'], + \ getline(1, '$')) + sil %d + call setline(1, ['1', '2 foobar eins zwei drei vier fnf sechse', + \ '3 foobar eins zwei drei vier fnf sechse', + \ '4 foobar eins zwei drei vier fnf sechse', + \ '5 foobar eins zwei drei vier fnf sechse', + \ '6 foobar eins zwei drei vier fnf sechse', + \ '7 foobar eins zwei drei vier fnf sechse']) + call setpos('.', [0, 4, 4, 0]) + %s/\%<.l.*// + call setpos('.', [0, 5, 4, 0]) + %s/\%>.l.*// + call assert_equal(['', '', '', + \ '4 foobar eins zwei drei vier fnf sechse', + \ '5 foobar eins zwei drei vier fnf sechse', + \ '', ''], + \ getline(1, '$')) + bwipe! +endfunc + +" Test that matching below, at or after the +" cursor position work +func Test_matching_pos() + for val in range(3) + exe "set re=" .. val + " Match at cursor position + call s:curpos_test2() + " Match before or after cursor position + call s:curpos_test3() + endfor + set re& +endfunc + +func Test_using_mark_position() + " this was using freed memory + " new engine + new + norm O0 + call assert_fails("s/\\%')", 'E486:') + bwipe! + + " old engine + new + norm O0 + call assert_fails("s/\\%#=1\\%')", 'E486:') + bwipe! +endfunc + +func Test_using_visual_position() + " this was using freed memory + new + exe "norm 0o\<Esc>\<C-V>k\<C-X>o0" + /\%V + bwipe! +endfunc + +func Test_using_invalid_visual_position() + " this was going beyond the end of the line + new + exe "norm 0o000\<Esc>0\<C-V>$s0" + /\%V + 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..191cd948ac 100644 --- a/src/nvim/testdir/test_regexp_utf8.vim +++ b/src/nvim/testdir/test_regexp_utf8.vim @@ -1,21 +1,21 @@ " Tests for regexp in utf8 encoding func s:equivalence_test() - let str = "AÀÁÂÃÄÅĀĂĄǍǞǠẢ BḂḆ CÇĆĈĊČ DĎĐḊḎḐ EÈÉÊËĒĔĖĘĚẺẼ FḞ GĜĞĠĢǤǦǴḠ HĤĦḢḦḨ IÌÍÎÏĨĪĬĮİǏỈ JĴ KĶǨḰḴ LĹĻĽĿŁḺ MḾṀ NÑŃŅŇṄṈ OÒÓÔÕÖØŌŎŐƠǑǪǬỎ PṔṖ Q RŔŖŘṘṞ SŚŜŞŠṠ TŢŤŦṪṮ UÙÚÛÜŨŪŬŮŰŲƯǓỦ VṼ WŴẀẂẄẆ XẊẌ YÝŶŸẎỲỶỸ ZŹŻŽƵẐẔ aàáâãäåāăąǎǟǡả bḃḇ cçćĉċč dďđḋḏḑ eèéêëēĕėęěẻẽ fḟ gĝğġģǥǧǵḡ hĥħḣḧḩẖ iìíîïĩīĭįǐỉ jĵǰ kķǩḱḵ lĺļľŀłḻ mḿṁ nñńņňʼnṅṉ oòóôõöøōŏőơǒǫǭỏ pṕṗ q rŕŗřṙṟ sśŝşšṡ tţťŧṫṯẗ uùúûüũūŭůűųưǔủ vṽ wŵẁẃẅẇẘ xẋẍ yýÿŷẏẙỳỷỹ zźżžƶẑẕ" + let str = "AÀÁÂÃÄÅĀĂĄǍǞǠǺȂȦȺḀẠẢẤẦẨẪẬẮẰẲẴẶ BƁɃḂḄḆ CÇĆĈĊČƇȻḈꞒ DĎĐƊḊḌḎḐḒ EÈÉÊËĒĔĖĘĚȄȆȨɆḔḖḘḚḜẸẺẼẾỀỂỄỆ FƑḞꞘ GĜĞĠĢƓǤǦǴḠꞠ HĤĦȞḢḤḦḨḪⱧ IÌÍÎÏĨĪĬĮİƗǏȈȊḬḮỈỊ JĴɈ KĶƘǨḰḲḴⱩꝀ LĹĻĽĿŁȽḶḸḺḼⱠ MḾṀṂ NÑŃŅŇǸṄṆṈṊꞤ OÒÓÔÕÖØŌŎŐƟƠǑǪǬǾȌȎȪȬȮȰṌṎṐṒỌỎỐỒỔỖỘỚỜỞỠỢ PƤṔṖⱣ QɊ RŔŖŘȐȒɌṘṚṜṞⱤꞦ SŚŜŞŠȘṠṢṤṦṨⱾꞨ TŢŤŦƬƮȚȾṪṬṮṰ UÙÚÛÜŨŪŬŮŰƯǕǙǛǓǗȔȖɄṲṴṶṸṺỤỦỨỪỬỮỰ VƲṼṾ WŴẀẂẄẆẈ XẊẌ YÝŶŸƳȲɎẎỲỴỶỸ ZŹŻŽƵẐẒẔⱫ aàáâãäåāăąǎǟǡǻȃȧᶏḁẚạảấầẩẫậắằẳẵặⱥ bƀɓᵬᶀḃḅḇ cçćĉċčƈȼḉꞓꞔ dďđɗᵭᶁᶑḋḍḏḑḓ eèéêëēĕėęěȅȇȩɇᶒḕḗḙḛḝẹẻẽếềểễệ fƒᵮᶂḟꞙ gĝğġģǥǧǵɠᶃḡꞡ hĥħȟḣḥḧḩḫẖⱨꞕ iìíîïĩīĭįǐȉȋɨᶖḭḯỉị jĵǰɉ kķƙǩᶄḱḳḵⱪꝁ lĺļľŀłƚḷḹḻḽⱡ mᵯḿṁṃ nñńņňʼnǹᵰᶇṅṇṉṋꞥ oòóôõöøōŏőơǒǫǭǿȍȏȫȭȯȱɵṍṏṑṓọỏốồổỗộớờởỡợ pƥᵱᵽᶈṕṗ qɋʠ rŕŗřȑȓɍɽᵲᵳᶉṛṝṟꞧ sśŝşšșȿᵴᶊṡṣṥṧṩꞩ tţťŧƫƭțʈᵵṫṭṯṱẗⱦ uùúûüũūŭůűųǚǖưǔǘǜȕȗʉᵾᶙṳṵṷṹṻụủứừửữự vʋᶌṽṿ wŵẁẃẅẇẉẘ xẋẍ yýÿŷƴȳɏẏẙỳỵỷỹ zźżžƶᵶᶎẑẓẕⱬ" let groups = split(str) for group1 in groups - for c in split(group1, '\zs') - " next statement confirms that equivalence class matches every - " character in group - call assert_match('^[[=' . c . '=]]*$', group1) - for group2 in groups - if group2 != group1 - " next statement converts that equivalence class doesn't match - " character in any other group - call assert_equal(-1, match(group2, '[[=' . c . '=]]')) - endif + for c in split(group1, '\zs') + " next statement confirms that equivalence class matches every + " character in group + call assert_match('^[[=' .. c .. '=]]*$', group1) + for group2 in groups + if group2 != group1 + " next statement converts that equivalence class doesn't match + " character in any other group + call assert_equal(-1, match(group2, '[[=' .. c .. '=]]'), c) + endif + endfor endfor - endfor endfor endfunc @@ -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 @@ -228,7 +223,7 @@ endfunc func Test_reversed_range() for re in range(0, 2) exe 'set re=' . re - call assert_fails('call match("abc def", "[c-a]")', 'E944:') + call assert_fails('call match("abc def", "[c-a]")', 'E944:', re) endfor set re=0 endfunc @@ -545,7 +540,6 @@ endfunc " Check that [[:upper:]] matches for automatic engine func Test_match_char_class_upper() new - let _engine=®expengine " Test 1: [[:upper:]]\{2,\} set regexpengine=0 @@ -586,8 +580,25 @@ func Test_match_char_class_upper() call assert_equal(4, searchcount().total, 'TEST 3 lower') " clean up - let ®expengine=_engine + set regexpengine=0 + bwipe! +endfunc + +func Test_match_invalid_byte() + call writefile(0z630a.765d30aa0a.2e0a.790a.4030, 'Xinvalid') + new + source Xinvalid bwipe! + call delete('Xinvalid') endfunc +func Test_match_too_complicated() + set regexpengine=1 + exe "noswapfile vsplit \xeb\xdb\x99" + silent! buf \&\zs*\zs*0 + bwipe! + set regexpengine=0 +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..b852cfd22f 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() @@ -62,7 +62,6 @@ func Test_display_registers() call assert_match('^\nType Name Content\n' \ . ' c "" a\n' \ . ' c "0 ba\n' - \ . ' c "1 b\n' \ . ' c "a b\n' \ . '.*' \ . ' c "- a\n' @@ -85,6 +84,90 @@ func Test_display_registers() let g:clipboard = save_clipboard endfunc +func Test_register_one() + " delete a line goes into register one + new + call setline(1, "one") + normal dd + call assert_equal("one\n", @1) + + " delete a word does not change register one, does change "- + call setline(1, "two") + normal de + call assert_equal("one\n", @1) + call assert_equal("two", @-) + + " delete a word with a register does not change register one + call setline(1, "three") + normal "ade + call assert_equal("three", @a) + call assert_equal("one\n", @1) + + " delete a word with register DOES change register one with one of a list of + " operators + " % + call setline(1, ["(12)3"]) + normal "ad% + call assert_equal("(12)", @a) + call assert_equal("(12)", @1) + + " ( + call setline(1, ["first second"]) + normal $"ad( + call assert_equal("first secon", @a) + call assert_equal("first secon", @1) + + " ) + call setline(1, ["First Second."]) + normal gg0"ad) + call assert_equal("First Second.", @a) + call assert_equal("First Second.", @1) + + " ` + call setline(1, ["start here."]) + normal gg0fhmx0"ad`x + call assert_equal("start ", @a) + call assert_equal("start ", @1) + + " / + call setline(1, ["searchX"]) + exe "normal gg0\"ad/X\<CR>" + call assert_equal("search", @a) + call assert_equal("search", @1) + + " ? + call setline(1, ["Ysearch"]) + exe "normal gg$\"ad?Y\<CR>" + call assert_equal("Ysearc", @a) + call assert_equal("Ysearc", @1) + + " n + call setline(1, ["Ynext"]) + normal gg$"adn + call assert_equal("Ynex", @a) + call assert_equal("Ynex", @1) + + " N + call setline(1, ["prevY"]) + normal gg0"adN + call assert_equal("prev", @a) + call assert_equal("prev", @1) + + " } + call setline(1, ["one", ""]) + normal gg0"ad} + call assert_equal("one\n", @a) + call assert_equal("one\n", @1) + + " { + call setline(1, ["", "two"]) + normal 2G$"ad{ + call assert_equal("\ntw", @a) + call assert_equal("\ntw", @1) + + bwipe! +endfunc + func Test_recording_status_in_ex_line() norm qx redraw! @@ -119,6 +202,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 +235,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 +266,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 +302,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') @@ -259,6 +565,82 @@ func Test_v_register() bwipe! endfunc +" Test for executing the contents of a register as an Ex command with line +" continuation. +func Test_execute_reg_as_ex_cmd() + " Line continuation with just two lines + let code =<< trim END + let l = [ + \ 1] + END + let @r = code->join("\n") + let l = [] + @r + call assert_equal([1], l) + + " Line continuation with more than two lines + let code =<< trim END + let l = [ + \ 1, + \ 2, + \ 3] + END + let @r = code->join("\n") + let l = [] + @r + call assert_equal([1, 2, 3], l) + + " use comments interspersed with code + let code =<< trim END + let l = [ + "\ one + \ 1, + "\ two + \ 2, + "\ three + \ 3] + END + let @r = code->join("\n") + let l = [] + @r + call assert_equal([1, 2, 3], l) + + " use line continuation in the middle + let code =<< trim END + let a = "one" + let l = [ + \ 1, + \ 2] + let b = "two" + END + let @r = code->join("\n") + let l = [] + @r + call assert_equal([1, 2], l) + call assert_equal("one", a) + call assert_equal("two", b) + + " only one line with a \ + let @r = "\\let l = 1" + call assert_fails('@r', 'E10:') + + " only one line with a "\ + let @r = ' "\ let i = 1' + @r + call assert_false(exists('i')) + + " first line also begins with a \ + let @r = "\\let l = [\n\\ 1]" + call assert_fails('@r', 'E10:') + + " Test with a large number of lines + let @r = "let str = \n" + let @r ..= repeat(" \\ 'abcdefghijklmnopqrstuvwxyz' ..\n", 312) + let @r ..= ' \ ""' + @r + call assert_equal(repeat('abcdefghijklmnopqrstuvwxyz', 312), str) +endfunc + func Test_ve_blockpaste() new set ve=all @@ -288,84 +670,64 @@ 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! +" mapping that ends macro recording should be removed from recorded macro +func Test_end_record_using_mapping() + call setline(1, 'aaa') + nnoremap s q + call feedkeys('safas', 'tx') + call assert_equal('fa', @a) + nunmap s - 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) + nnoremap xx q + call feedkeys('0xxafaxx', 'tx') + call assert_equal('fa', @a) + nunmap xx - 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) + nnoremap xsx q + call feedkeys('0qafaxsx', 'tx') + call assert_equal('fa', @a) + nunmap xsx - 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_end_reg_executing() + nnoremap s <Nop> + let @a = 's' + call feedkeys("@aqaq\<Esc>", 'tx') + call assert_equal('', @a) + call assert_equal('', getline(1)) + + call setline(1, 'aaa') + nnoremap s qa + let @a = 'fa' + call feedkeys("@asq\<Esc>", 'tx') + call assert_equal('', @a) + call assert_equal('aaa', getline(1)) + + nunmap s 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_ruby.vim b/src/nvim/testdir/test_ruby.vim index 1a274d1fec..1fbf3392d9 100644 --- a/src/nvim/testdir/test_ruby.vim +++ b/src/nvim/testdir/test_ruby.vim @@ -60,7 +60,7 @@ func Test_ruby_set_cursor() " Check that movement after setting cursor position keeps current column. normal j call assert_equal([2, 6], [line('.'), col('.')]) - call assert_equal([2, 5], rubyeval('$curwin.cursor')) + call assert_equal([2, 5], '$curwin.cursor'->rubyeval()) " call assert_fails('ruby $curwin.cursor = [1]', " \ 'ArgumentError: array length must be 2') 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 7570049e7c..8154bd9c4d 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 - let a = searchpair('\[','',']','bW') - call assert_equal(3, a) + 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 - let a = searchpair('\[','',']','bW') - call assert_equal(3, a) + 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 + 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 + 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 @@ -1315,7 +1396,7 @@ func Test_search_match_at_curpos() normal gg - call search('foobar', 'c') + eval 'foobar'->search('c') call assert_equal([1, 1], [line('.'), col('.')]) normal j @@ -1354,6 +1435,41 @@ func Test_search_display_pattern() endif endfunc +func Test_searchdecl() + let lines =<< trim END + int global; + + func() + { + int global; + if (cond) { + int local; + } + int local; + // comment + } + END + new + call setline(1, lines) + 10 + call assert_equal(0, searchdecl('local', 0, 0)) + call assert_equal(7, getcurpos()[1]) + + 10 + call assert_equal(0, 'local'->searchdecl(0, 1)) + call assert_equal(9, getcurpos()[1]) + + 10 + call assert_equal(0, searchdecl('global')) + call assert_equal(5, getcurpos()[1]) + + 10 + call assert_equal(0, searchdecl('global', 1)) + call assert_equal(1, getcurpos()[1]) + + bwipe! +endfunc + func Test_search_special() " this was causing illegal memory access and an endless loop set t_PE= @@ -1411,4 +1527,31 @@ func Test_incsearch_highlighting_newline() bw endfunc +func Test_no_last_search_pattern() + CheckOption incsearch + + let @/ = "" + set incsearch + " these were causing a crash + call feedkeys("//\<C-G>", 'xt') + call feedkeys("//\<C-T>", 'xt') + call feedkeys("??\<C-G>", 'xt') + call feedkeys("??\<C-T>", 'xt') +endfunc + +func Test_search_with_invalid_range() + new + let lines =<< trim END + /\%.v + 5/ + c + END + call writefile(lines, 'Xrangesearch') + source Xrangesearch + + bwipe! + call delete('Xrangesearch') +endfunc + + " vim: shiftwidth=2 sts=2 expandtab 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_searchpos.vim b/src/nvim/testdir/test_searchpos.vim new file mode 100644 index 0000000000..dd13c305c5 --- /dev/null +++ b/src/nvim/testdir/test_searchpos.vim @@ -0,0 +1,30 @@ +" Tests for searchpos() + +func Test_searchpos() + new one + 0put ='1a3' + 1put ='123xyz' + call cursor(1, 1) + call assert_equal([1, 1, 2], searchpos('\%(\([a-z]\)\|\_.\)\{-}xyz', 'pcW')) + call cursor(1, 2) + call assert_equal([2, 1, 1], '\%(\([a-z]\)\|\_.\)\{-}xyz'->searchpos('pcW')) + set cpo-=c + call cursor(1, 2) + call assert_equal([1, 2, 2], searchpos('\%(\([a-z]\)\|\_.\)\{-}xyz', 'pcW')) + call cursor(1, 3) + call assert_equal([1, 3, 1], searchpos('\%(\([a-z]\)\|\_.\)\{-}xyz', 'pcW')) + + " Now with \zs, first match is in column 0, "a" is matched. + call cursor(1, 3) + call assert_equal([2, 4, 2], searchpos('\%(\([a-z]\)\|\_.\)\{-}\zsxyz', 'pcW')) + " With z flag start at cursor column, don't see the "a". + call cursor(1, 3) + call assert_equal([2, 4, 1], searchpos('\%(\([a-z]\)\|\_.\)\{-}\zsxyz', 'pcWz')) + + set cpo+=c + " close the window + q! + +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_set.vim b/src/nvim/testdir/test_set.vim new file mode 100644 index 0000000000..2b1e9eeee0 --- /dev/null +++ b/src/nvim/testdir/test_set.vim @@ -0,0 +1,29 @@ +" Tests for the :set command + +function Test_set_backslash() + let isk_save = &isk + + set isk=a,b,c + set isk+=d + call assert_equal('a,b,c,d', &isk) + set isk+=\\,e + call assert_equal('a,b,c,d,\,e', &isk) + set isk-=e + call assert_equal('a,b,c,d,\', &isk) + set isk-=\\ + call assert_equal('a,b,c,d', &isk) + + let &isk = isk_save +endfunction + +function Test_set_add() + let wig_save = &wig + + set wildignore=*.png, + set wildignore+=*.jpg + call assert_equal('*.png,*.jpg', &wig) + + let &wig = wig_save +endfunction + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_sha256.vim b/src/nvim/testdir/test_sha256.vim index dd4707977e..76d1306836 100644 --- a/src/nvim/testdir/test_sha256.vim +++ b/src/nvim/testdir/test_sha256.vim @@ -6,17 +6,17 @@ endif function Test_sha256() " test for empty string: - call assert_equal(sha256(""), 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855') + call assert_equal('e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855', sha256("")) "'test for 1 char: - call assert_equal(sha256("a"), 'ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb') + call assert_equal('ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb', sha256("a")) " "test for 3 chars: - call assert_equal(sha256("abc"), 'ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad') + call assert_equal('ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad', "abc"->sha256()) " test for contains meta char: - call assert_equal(sha256("foo\nbar"), '807eff6267f3f926a21d234f7b0cf867a86f47e07a532f15e8cc39ed110ca776') + call assert_equal('807eff6267f3f926a21d234f7b0cf867a86f47e07a532f15e8cc39ed110ca776', sha256("foo\nbar")) " test for contains non-ascii char: - call assert_equal(sha256("\xde\xad\xbe\xef"), '5f78c33274e43fa9de5659265c1d917e25c03722dcb0b8d27db8d5feaa813953') + call assert_equal('5f78c33274e43fa9de5659265c1d917e25c03722dcb0b8d27db8d5feaa813953', sha256("\xde\xad\xbe\xef")) endfunction diff --git a/src/nvim/testdir/test_signs.vim b/src/nvim/testdir/test_signs.vim index 9753100375..ff9ba3d8ed 100644 --- a/src/nvim/testdir/test_signs.vim +++ b/src/nvim/testdir/test_signs.vim @@ -15,13 +15,13 @@ func Test_sign() " the icon name when listing signs. sign define Sign1 text=x - call Sign_command_ignore_error('sign define Sign2 text=xy texthl=Title linehl=Error icon=../../pixmaps/stock_vim_find_help.png') + call Sign_command_ignore_error('sign define Sign2 text=xy texthl=Title linehl=Error culhl=Search icon=../../pixmaps/stock_vim_find_help.png') " Test listing signs. let a=execute('sign list') call assert_match('^\nsign Sign1 text=x \nsign Sign2 ' . \ 'icon=../../pixmaps/stock_vim_find_help.png .*text=xy ' . - \ 'linehl=Error texthl=Title$', a) + \ 'linehl=Error texthl=Title culhl=Search$', a) let a=execute('sign list Sign1') call assert_equal("\nsign Sign1 text=x ", a) @@ -126,6 +126,30 @@ func Test_sign() " call assert_fails("sign define Sign4 text= linehl=Comment", 'E239:') call assert_fails("sign define Sign4 text=\\ ab linehl=Comment", 'E239:') + " an empty highlight argument for an existing sign clears it + sign define SignY texthl=TextHl culhl=CulHl linehl=LineHl + let sl = sign_getdefined('SignY')[0] + call assert_equal('TextHl', sl.texthl) + call assert_equal('CulHl', sl.culhl) + call assert_equal('LineHl', sl.linehl) + + sign define SignY texthl= culhl=CulHl linehl=LineHl + let sl = sign_getdefined('SignY')[0] + call assert_false(has_key(sl, 'texthl')) + call assert_equal('CulHl', sl.culhl) + call assert_equal('LineHl', sl.linehl) + + sign define SignY linehl= + let sl = sign_getdefined('SignY')[0] + call assert_false(has_key(sl, 'linehl')) + call assert_equal('CulHl', sl.culhl) + + sign define SignY culhl= + let sl = sign_getdefined('SignY')[0] + call assert_false(has_key(sl, 'culhl')) + + sign undefine SignY + " define sign with whitespace sign define Sign4 text=\ X linehl=Comment sign undefine Sign4 @@ -194,15 +218,13 @@ func Test_sign_completion() call assert_equal('"sign define jump list place undefine unplace', @:) call feedkeys(":sign define Sign \<C-A>\<C-B>\"\<CR>", 'tx') - call assert_equal('"sign define Sign icon= linehl= numhl= text= texthl=', @:) + call assert_equal('"sign define Sign culhl= icon= linehl= numhl= text= texthl=', @:) - call feedkeys(":sign define Sign linehl=Spell\<C-A>\<C-B>\"\<CR>", 'tx') - call assert_equal('"sign define Sign linehl=SpellBad SpellCap ' . - \ 'SpellLocal SpellRare', @:) - - call feedkeys(":sign define Sign texthl=Spell\<C-A>\<C-B>\"\<CR>", 'tx') - call assert_equal('"sign define Sign texthl=SpellBad SpellCap ' . - \ 'SpellLocal SpellRare', @:) + for hl in ['culhl', 'linehl', 'numhl', 'texthl'] + call feedkeys(":sign define Sign "..hl.."=Spell\<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal('"sign define Sign '..hl..'=SpellBad SpellCap ' . + \ 'SpellLocal SpellRare', @:) + endfor call writefile(repeat(["Sun is shining"], 30), "XsignOne") call writefile(repeat(["Sky is blue"], 30), "XsignTwo") @@ -392,25 +414,28 @@ func Test_sign_funcs() call sign_undefine() " Tests for sign_define() - let attr = {'text' : '=>', 'linehl' : 'Search', 'texthl' : 'Error'} - call assert_equal(0, sign_define("sign1", attr)) - call assert_equal([{'name' : 'sign1', 'texthl' : 'Error', - \ 'linehl' : 'Search', 'text' : '=>'}], sign_getdefined()) + let attr = {'text' : '=>', 'linehl' : 'Search', 'texthl' : 'Error', + \ 'culhl': 'Visual', 'numhl': 'Number'} + call assert_equal(0, "sign1"->sign_define(attr)) + call assert_equal([{'name' : 'sign1', 'texthl' : 'Error', 'linehl': 'Search', + \ 'culhl': 'Visual', 'numhl': 'Number', 'text' : '=>'}], + \ sign_getdefined()) " Define a new sign without attributes and then update it call sign_define("sign2") let attr = {'text' : '!!', 'linehl' : 'DiffAdd', 'texthl' : 'DiffChange', - \ 'icon' : 'sign2.ico'} + \ 'culhl': 'DiffDelete', 'numhl': 'Number', 'icon' : 'sign2.ico'} call Sign_define_ignore_error("sign2", attr) call assert_equal([{'name' : 'sign2', 'texthl' : 'DiffChange', - \ 'linehl' : 'DiffAdd', 'text' : '!!', 'icon' : 'sign2.ico'}], - \ sign_getdefined("sign2")) + \ 'linehl' : 'DiffAdd', 'culhl' : 'DiffDelete', 'text' : '!!', + \ 'numhl': 'Number', 'icon' : 'sign2.ico'}], + \ "sign2"->sign_getdefined()) " Test for a sign name with digits call assert_equal(0, sign_define(0002, {'linehl' : 'StatusLine'})) call assert_equal([{'name' : '2', 'linehl' : 'StatusLine'}], \ sign_getdefined(0002)) - call sign_undefine(0002) + eval 0002->sign_undefine() " Tests for invalid arguments to sign_define() call assert_fails('call sign_define("sign4", {"text" : "===>"})', 'E239:') @@ -434,7 +459,7 @@ func Test_sign_funcs() call assert_equal([{'bufnr' : bufnr(''), 'signs' : \ [{'id' : 10, 'group' : '', 'lnum' : 20, 'name' : 'sign1', \ 'priority' : 10}]}], - \ sign_getplaced('%', {'lnum' : 20})) + \ '%'->sign_getplaced({'lnum' : 20})) call assert_equal([{'bufnr' : bufnr(''), 'signs' : \ [{'id' : 10, 'group' : '', 'lnum' : 20, 'name' : 'sign1', \ 'priority' : 10}]}], @@ -490,10 +515,10 @@ func Test_sign_funcs() \ 'E745:') " Tests for sign_unplace() - call sign_place(20, '', 'sign2', 'Xsign', {"lnum" : 30}) + eval 20->sign_place('', 'sign2', 'Xsign', {"lnum" : 30}) call assert_equal(0, sign_unplace('', \ {'id' : 20, 'buffer' : 'Xsign'})) - call assert_equal(-1, sign_unplace('', + call assert_equal(-1, ''->sign_unplace( \ {'id' : 30, 'buffer' : 'Xsign'})) call sign_place(20, '', 'sign2', 'Xsign', {"lnum" : 30}) call assert_fails("call sign_unplace('', @@ -1693,7 +1718,7 @@ func Test_sign_jump_func() let r = sign_jump(5, '', 'foo') call assert_equal(2, r) call assert_equal(2, line('.')) - let r = sign_jump(6, 'g1', 'foo') + let r = 6->sign_jump('g1', 'foo') call assert_equal(5, r) call assert_equal(5, line('.')) let r = sign_jump(5, '', 'bar') @@ -1921,8 +1946,7 @@ func Test_sign_funcs_multi() \ 'group' : 'g1', 'priority' : 10}], s[0].signs) " Change an existing sign without specifying the group - call assert_equal([5], sign_placelist([ - \ {'id' : 5, 'name' : 'sign1', 'buffer' : 'Xsign'}])) + call assert_equal([5], [{'id' : 5, 'name' : 'sign1', 'buffer' : 'Xsign'}]->sign_placelist()) let s = sign_getplaced('Xsign', {'id' : 5, 'group' : ''}) call assert_equal([{'id' : 5, 'name' : 'sign1', 'lnum' : 11, \ 'group' : '', 'priority' : 10}], s[0].signs) @@ -1955,7 +1979,7 @@ func Test_sign_funcs_multi() \ {'id' : 1, 'group' : 'g1'}, {'id' : 1, 'group' : 'g2'}])) " Invalid arguments - call assert_equal([], sign_unplacelist([])) + call assert_equal([], []->sign_unplacelist()) call assert_fails('call sign_unplacelist({})', "E714:") call assert_fails('call sign_unplacelist([[]])', "E715:") call assert_fails('call sign_unplacelist(["abc"])', "E715:") diff --git a/src/nvim/testdir/test_sort.vim b/src/nvim/testdir/test_sort.vim index 570c4415c6..5d7dd7bfff 100644 --- a/src/nvim/testdir/test_sort.vim +++ b/src/nvim/testdir/test_sort.vim @@ -12,6 +12,7 @@ func Compare2(a, b) abort endfunc func Test_sort_strings() + CheckNotMSWindows " FIXME: Why does this fail with MSVC? " numbers compared as strings call assert_equal([1, 2, 3], sort([3, 2, 1])) call assert_equal([13, 28, 3], sort([3, 28, 13])) @@ -58,6 +59,7 @@ endfunc func Test_sort_numbers() call assert_equal([3, 13, 28], sort([13, 28, 3], 'N')) call assert_equal(['3', '13', '28'], sort(['13', '28', '3'], 'N')) + call assert_equal([3997, 4996], sort([4996, 3997], 'Compare1')) endfunc func Test_sort_float() 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 e525d06ea2..56ed97cdd9 100644 --- a/src/nvim/testdir/test_spell.vim +++ b/src/nvim/testdir/test_spell.vim @@ -77,7 +77,7 @@ func Test_spellbadword() set spell call assert_equal(['bycycle', 'bad'], spellbadword('My bycycle.')) - call assert_equal(['another', 'caps'], spellbadword('A sentence. another sentence')) + call assert_equal(['another', 'caps'], 'A sentence. another sentence'->spellbadword()) call assert_equal(['TheCamelWord', 'bad'], spellbadword('TheCamelWord asdf')) set spelloptions=camel @@ -407,7 +407,7 @@ func Test_zz_basic() \ ) call assert_equal("gebletegek", soundfold('goobledygoook')) - call assert_equal("kepereneven", soundfold('kóopërÿnôven')) + call assert_equal("kepereneven", 'koprnven'->soundfold()) call assert_equal("everles gesvets etele", soundfold('oeverloos gezwets edale')) endfunc @@ -588,7 +588,7 @@ func Test_zz_sal_and_addition() mkspell! Xtest Xtest set spl=Xtest.latin1.spl spell call assert_equal('kbltykk', soundfold('goobledygoook')) - call assert_equal('kprnfn', soundfold('kóopërÿnôven')) + call assert_equal('kprnfn', soundfold('koprnven')) call assert_equal('*fls kswts tl', soundfold('oeverloos gezwets edale')) "also use an addition file @@ -681,6 +681,14 @@ func Test_spell_long_word() set nospell endfunc +func Test_spellsuggest_too_deep() + " This was incrementing "depth" over MAXWLEN. + new + norm s000G00000000000000 + sil norm ..vzG................vvzG0 v z= + bwipe! +endfunc + func LoadAffAndDic(aff_contents, dic_contents) throw 'skipped: Nvim does not support enc=latin1' set enc=latin1 @@ -711,7 +719,7 @@ func TestGoodBadBase() break endif let prevbad = bad - let lst = spellsuggest(bad, 3) + let lst = bad->spellsuggest(3) normal mm call add(result, [bad, lst]) @@ -768,6 +776,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_spell_utf8.vim b/src/nvim/testdir/test_spell_utf8.vim index cafdb97f28..3d159f3352 100644 --- a/src/nvim/testdir/test_spell_utf8.vim +++ b/src/nvim/testdir/test_spell_utf8.vim @@ -512,8 +512,7 @@ func TestGoodBadBase() break endif let prevbad = bad - " let lst = bad->spellsuggest(3) - let lst = spellsuggest(bad, 3) + let lst = bad->spellsuggest(3) normal mm call add(result, [bad, lst]) @@ -552,8 +551,7 @@ func Test_spell_basic() \ ) call assert_equal("gebletegek", soundfold('goobledygoook')) - " call assert_equal("kepereneven", 'kóopërÿnôven'->soundfold()) - call assert_equal("kepereneven", soundfold('kóopërÿnôven')) + call assert_equal("kepereneven", 'kóopërÿnôven'->soundfold()) call assert_equal("everles gesvets etele", soundfold('oeverloos gezwets edale')) endfunc diff --git a/src/nvim/testdir/test_startup.vim b/src/nvim/testdir/test_startup.vim index b140077111..d830f5216d 100644 --- a/src/nvim/testdir/test_startup.vim +++ b/src/nvim/testdir/test_startup.vim @@ -905,15 +905,13 @@ func Test_not_a_term() " This will take 2 seconds because of the missing --not-a-term let cmd = GetVimProg() .. ' --cmd quit ' .. redir exe "silent !" . cmd - " call assert_match("\<Esc>", readfile('Xvimout')->join()) - call assert_match("\<Esc>", join(readfile('Xvimout'))) + call assert_match("\<Esc>", readfile('Xvimout')->join()) call delete('Xvimout') " With --not-a-term there are no escape sequences. let cmd = GetVimProg() .. ' --not-a-term --cmd quit ' .. redir exe "silent !" . cmd - " call assert_notmatch("\<Esc>", readfile('Xvimout')->join()) - call assert_notmatch("\<Esc>", join(readfile('Xvimout'))) + call assert_notmatch("\<Esc>", readfile('Xvimout')->join()) call delete('Xvimout') endfunc 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 e7f9bb76f2..86fd0147a5 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() @@ -137,7 +140,7 @@ func Test_substitute_repeat() " This caused an invalid memory access. split Xfile s/^/x - call feedkeys("Qsc\<CR>y", 'tx') + call feedkeys("gQsc\<CR>y", 'tx') bwipe! endfunc @@ -174,9 +177,9 @@ func Test_sub_cmd_1() \ ['I', 's/I/\lII/', ['iI']], \ ['J', 's/J/\LJ\EJ/', ['jJ']], \ ['K', 's/K/\Uk\ek/', ['Kk']], - \ ['lLl', "s/L/\<C-V>\<C-M>/", ["l\<C-V>", 'l']], + \ ['lLl', "s/L/\<C-V>\r/", ["l\<C-V>", 'l']], \ ['mMm', 's/M/\r/', ['m', 'm']], - \ ['nNn', "s/N/\\\<C-V>\<C-M>/", ["n\<C-V>", 'n']], + \ ['nNn', "s/N/\\\<C-V>\r/", ["n\<C-V>", 'n']], \ ['oOo', 's/O/\n/', ["o\no"]], \ ['pPp', 's/P/\b/', ["p\<C-H>p"]], \ ['qQq', 's/Q/\t/', ["q\tq"]], @@ -205,9 +208,9 @@ func Test_sub_cmd_2() \ ['I', 's/I/\lII/', ['iI']], \ ['J', 's/J/\LJ\EJ/', ['jJ']], \ ['K', 's/K/\Uk\ek/', ['Kk']], - \ ['lLl', "s/L/\<C-V>\<C-M>/", ["l\<C-V>", 'l']], + \ ['lLl', "s/L/\<C-V>\r/", ["l\<C-V>", 'l']], \ ['mMm', 's/M/\r/', ['m', 'm']], - \ ['nNn', "s/N/\\\<C-V>\<C-M>/", ["n\<C-V>", 'n']], + \ ['nNn', "s/N/\\\<C-V>\r/", ["n\<C-V>", 'n']], \ ['oOo', 's/O/\n/', ["o\no"]], \ ['pPp', 's/P/\b/', ["p\<C-H>p"]], \ ['qQq', 's/Q/\t/', ["q\tq"]], @@ -227,9 +230,9 @@ func Test_sub_cmd_3() " List entry format: [input, cmd, output] let tests = [['aAa', "s/A/\\='\\'/", ['a\a']], \ ['bBb', "s/B/\\='\\\\'/", ['b\\b']], - \ ['cCc', "s/C/\\='\<C-V>\<C-M>'/", ["c\<C-V>", 'c']], - \ ['dDd', "s/D/\\='\\\<C-V>\<C-M>'/", ["d\\\<C-V>", 'd']], - \ ['eEe', "s/E/\\='\\\\\<C-V>\<C-M>'/", ["e\\\\\<C-V>", 'e']], + \ ['cCc', "s/C/\\='\<C-V>\r'/", ["c\<C-V>", 'c']], + \ ['dDd', "s/D/\\='\\\<C-V>\r'/", ["d\\\<C-V>", 'd']], + \ ['eEe', "s/E/\\='\\\\\<C-V>\r'/", ["e\\\\\<C-V>", 'e']], \ ['fFf', "s/F/\\='\r'/", ['f', 'f']], \ ['gGg', "s/G/\\='\<C-V>\<C-J>'/", ["g\<C-V>", 'g']], \ ['hHh', "s/H/\\='\\\<C-V>\<C-J>'/", ["h\\\<C-V>", 'h']], @@ -251,11 +254,11 @@ func Test_sub_cmd_4() \ ['a\a']], \ ['bBb', "s/B/\\=substitute(submatch(0), '.', '\\', '')/", \ ['b\b']], - \ ['cCc', "s/C/\\=substitute(submatch(0), '.', '\<C-V>\<C-M>', '')/", + \ ['cCc', "s/C/\\=substitute(submatch(0), '.', '\<C-V>\r', '')/", \ ["c\<C-V>", 'c']], - \ ['dDd', "s/D/\\=substitute(submatch(0), '.', '\\\<C-V>\<C-M>', '')/", + \ ['dDd', "s/D/\\=substitute(submatch(0), '.', '\\\<C-V>\r', '')/", \ ["d\<C-V>", 'd']], - \ ['eEe', "s/E/\\=substitute(submatch(0), '.', '\\\\\<C-V>\<C-M>', '')/", + \ ['eEe', "s/E/\\=substitute(submatch(0), '.', '\\\\\<C-V>\r', '')/", \ ["e\\\<C-V>", 'e']], \ ['fFf', "s/F/\\=substitute(submatch(0), '.', '\\r', '')/", \ ['f', 'f']], @@ -313,7 +316,7 @@ func Test_sub_cmd_7() set cpo& " List entry format: [input, cmd, output] - let tests = [ ["A\<C-V>\<C-M>A", 's/A./\=submatch(0)/', ['A', 'A']], + let tests = [ ["A\<C-V>\rA", 's/A./\=submatch(0)/', ['A', 'A']], \ ["B\<C-V>\<C-J>B", 's/B./\=submatch(0)/', ['B', 'B']], \ ["C\<C-V>\<C-J>C", 's/C./\=strtrans(string(submatch(0, 1)))/', [strtrans("['C\<C-J>']C")]], \ ["D\<C-V>\<C-J>\nD", 's/D.\nD/\=strtrans(string(submatch(0, 1)))/', [strtrans("['D\<C-J>', 'D']")]], @@ -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 @@ -451,11 +467,11 @@ func Test_sub_replace_1() call assert_equal('iI', substitute('I', 'I', '\lII', '')) call assert_equal('jJ', substitute('J', 'J', '\LJ\EJ', '')) call assert_equal('Kk', substitute('K', 'K', '\Uk\ek', '')) - call assert_equal("l\<C-V>\<C-M>l", - \ substitute('lLl', 'L', "\<C-V>\<C-M>", '')) - call assert_equal("m\<C-M>m", substitute('mMm', 'M', '\r', '')) - call assert_equal("n\<C-V>\<C-M>n", - \ substitute('nNn', 'N', "\\\<C-V>\<C-M>", '')) + call assert_equal("l\<C-V>\rl", + \ substitute('lLl', 'L', "\<C-V>\r", '')) + call assert_equal("m\rm", substitute('mMm', 'M', '\r', '')) + call assert_equal("n\<C-V>\rn", + \ substitute('nNn', 'N', "\\\<C-V>\r", '')) call assert_equal("o\no", substitute('oOo', 'O', '\n', '')) call assert_equal("p\<C-H>p", substitute('pPp', 'P', '\b', '')) call assert_equal("q\tq", substitute('qQq', 'Q', '\t', '')) @@ -464,7 +480,7 @@ func Test_sub_replace_1() call assert_equal("u\nu", substitute('uUu', 'U', "\n", '')) call assert_equal("v\<C-H>v", substitute('vVv', 'V', "\b", '')) call assert_equal("w\\w", substitute('wWw', 'W', "\\", '')) - call assert_equal("x\<C-M>x", substitute('xXx', 'X', "\r", '')) + call assert_equal("x\rx", substitute('xXx', 'X', "\r", '')) call assert_equal("YyyY", substitute('Y', 'Y', '\L\uyYy\l\EY', '')) call assert_equal("zZZz", substitute('Z', 'Z', '\U\lZzZ\u\Ez', '')) endfunc @@ -484,17 +500,17 @@ func Test_sub_replace_2() call assert_equal('iI', substitute('I', 'I', '\lII', '')) call assert_equal('jJ', substitute('J', 'J', '\LJ\EJ', '')) call assert_equal('Kk', substitute('K', 'K', '\Uk\ek', '')) - call assert_equal("l\<C-V>\<C-M>l", - \ substitute('lLl', 'L', "\<C-V>\<C-M>", '')) - call assert_equal("m\<C-M>m", substitute('mMm', 'M', '\r', '')) - call assert_equal("n\<C-V>\<C-M>n", - \ substitute('nNn', 'N', "\\\<C-V>\<C-M>", '')) + call assert_equal("l\<C-V>\rl", + \ substitute('lLl', 'L', "\<C-V>\r", '')) + call assert_equal("m\rm", substitute('mMm', 'M', '\r', '')) + call assert_equal("n\<C-V>\rn", + \ substitute('nNn', 'N', "\\\<C-V>\r", '')) call assert_equal("o\no", substitute('oOo', 'O', '\n', '')) call assert_equal("p\<C-H>p", substitute('pPp', 'P', '\b', '')) call assert_equal("q\tq", substitute('qQq', 'Q', '\t', '')) call assert_equal('r\r', substitute('rRr', 'R', '\\', '')) call assert_equal('scs', substitute('sSs', 'S', '\c', '')) - call assert_equal("t\<C-M>t", substitute('tTt', 'T', "\r", '')) + call assert_equal("t\rt", substitute('tTt', 'T', "\r", '')) call assert_equal("u\nu", substitute('uUu', 'U', "\n", '')) call assert_equal("v\<C-H>v", substitute('vVv', 'V', "\b", '')) call assert_equal('w\w', substitute('wWw', 'W', "\\", '')) @@ -512,7 +528,7 @@ func Test_sub_replace_3() call assert_equal("e\\\\\re", substitute('eEe', 'E', "\\=\"\\\\\\\\\r\"", '')) call assert_equal('f\rf', substitute('fFf', 'F', '\="\\r"', '')) call assert_equal('j\nj', substitute('jJj', 'J', '\="\\n"', '')) - call assert_equal("k\<C-M>k", substitute('kKk', 'K', '\="\r"', '')) + call assert_equal("k\rk", substitute('kKk', 'K', '\="\r"', '')) call assert_equal("l\nl", substitute('lLl', 'L', '\="\n"', '')) endfunc @@ -524,10 +540,10 @@ func Test_sub_replace_4() \ '\=substitute(submatch(0), ".", "\\", "")', '')) call assert_equal('b\b', substitute('bBb', 'B', \ '\=substitute(submatch(0), ".", "\\\\", "")', '')) - call assert_equal("c\<C-V>\<C-M>c", substitute('cCc', 'C', '\=substitute(submatch(0), ".", "\<C-V>\<C-M>", "")', '')) - call assert_equal("d\<C-V>\<C-M>d", substitute('dDd', 'D', '\=substitute(submatch(0), ".", "\\\<C-V>\<C-M>", "")', '')) - call assert_equal("e\\\<C-V>\<C-M>e", substitute('eEe', 'E', '\=substitute(submatch(0), ".", "\\\\\<C-V>\<C-M>", "")', '')) - call assert_equal("f\<C-M>f", substitute('fFf', 'F', '\=substitute(submatch(0), ".", "\\r", "")', '')) + call assert_equal("c\<C-V>\rc", substitute('cCc', 'C', '\=substitute(submatch(0), ".", "\<C-V>\r", "")', '')) + call assert_equal("d\<C-V>\rd", substitute('dDd', 'D', '\=substitute(submatch(0), ".", "\\\<C-V>\r", "")', '')) + call assert_equal("e\\\<C-V>\re", substitute('eEe', 'E', '\=substitute(submatch(0), ".", "\\\\\<C-V>\r", "")', '')) + call assert_equal("f\rf", substitute('fFf', 'F', '\=substitute(submatch(0), ".", "\\r", "")', '')) call assert_equal("j\nj", substitute('jJj', 'J', '\=substitute(submatch(0), ".", "\\n", "")', '')) call assert_equal("k\rk", substitute('kKk', 'K', '\=substitute(submatch(0), ".", "\r", "")', '')) call assert_equal("l\nl", substitute('lLl', 'L', '\=substitute(submatch(0), ".", "\n", "")', '')) @@ -547,7 +563,7 @@ func Test_sub_replace_5() \ substitute('A123456789', \ 'A\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)', \ '\=string([submatch(0, 1), submatch(9, 1), ' . - \ 'submatch(8, 1), submatch(7, 1), submatch(6, 1), ' . + \ 'submatch(8, 1), 7->submatch(1), submatch(6, 1), ' . \ 'submatch(5, 1), submatch(4, 1), submatch(3, 1), ' . \ 'submatch(2, 1), submatch(1, 1)])', \ '')) @@ -749,11 +765,49 @@ 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)]])} - " call substitute('A1', pat, Rep, '')->assert_equal("[['A1'], ['1']]") - call assert_equal(substitute('A1', pat, Rep, ''), "[['A1'], ['1']]") + call substitute('A1', pat, Rep, '')->assert_equal("[['A1'], ['1']]") endfunc func Test_substitute_skipped_range() @@ -765,4 +819,22 @@ func Test_substitute_skipped_range() bwipe! endfunc +" This was using "old_sub" after it was freed. +func Test_using_old_sub() + " set compatible maxfuncdepth=10 + set maxfuncdepth=10 + new + call setline(1, 'some text.') + func Repl() + ~ + s/ + endfunc + silent! s/\%')/\=Repl() + + delfunc Repl + bwipe! + set nocompatible +endfunc + + " vim: shiftwidth=2 sts=2 expandtab 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 e3101d4e44..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')) @@ -113,7 +114,7 @@ func Test_swapinfo() w let fname = s:swapname() call assert_match('Xswapinfo', fname) - let info = swapinfo(fname) + let info = fname->swapinfo() let ver = printf('VIM %d.%d', v:version / 100, v:version % 100) call assert_equal(ver, info.version) @@ -155,7 +156,7 @@ func Test_swapname() let buf = bufnr('%') let expected = s:swapname() wincmd p - call assert_equal(expected, swapname(buf)) + call assert_equal(expected, buf->swapname()) new Xtest3 setlocal noswapfile @@ -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 914d9c2782..b047b53b6f 100644 --- a/src/nvim/testdir/test_syntax.vim +++ b/src/nvim/testdir/test_syntax.vim @@ -30,23 +30,17 @@ func AssertHighlightGroups(lnum, startcol, expected, trans = 1, msg = "") " If groups are provided as a string, each character is assumed to be a " group and spaces represent no group, useful for visually describing tests. let l:expectedGroups = type(a:expected) == v:t_string - "\ ? a:expected->split('\zs')->map({_, v -> trim(v)}) - \ ? map(split(a:expected, '\zs'), {_, v -> trim(v)}) + \ ? a:expected->split('\zs')->map({_, v -> trim(v)}) \ : a:expected let l:errors = 0 - " let l:msg = (a:msg->empty() ? "" : a:msg .. ": ") - let l:msg = (empty(a:msg) ? "" : a:msg .. ": ") + let l:msg = (a:msg->empty() ? "" : a:msg .. ": ") \ .. "Wrong highlight group at " .. a:lnum .. "," - " for l:i in range(a:startcol, a:startcol + l:expectedGroups->len() - 1) - " let l:errors += synID(a:lnum, l:i, a:trans) - " \ ->synIDattr("name") - " \ ->assert_equal(l:expectedGroups[l:i - 1], - for l:i in range(a:startcol, a:startcol + len(l:expectedGroups) - 1) - let l:errors += - \ assert_equal(synIDattr(synID(a:lnum, l:i, a:trans), "name"), - \ l:expectedGroups[l:i - 1], - \ l:msg .. l:i) + for l:i in range(a:startcol, a:startcol + l:expectedGroups->len() - 1) + let l:errors += synID(a:lnum, l:i, a:trans) + \ ->synIDattr("name") + \ ->assert_equal(l:expectedGroups[l:i - 1], + \ l:msg .. l:i) endfor endfunc @@ -734,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 7b8ee778cc..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 @@ -121,8 +121,7 @@ func Test_system_with_shell_quote() let msg = printf('shell=%s shellxquote=%s', &shell, &shellxquote) try - " let out = 'echo 123'->system() - let out = system('echo 123') + let out = 'echo 123'->system() catch call assert_report(printf('%s: %s', msg, v:exception)) continue 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_tabpage.vim b/src/nvim/testdir/test_tabpage.vim index b261b96c3b..51ab5c1022 100644 --- a/src/nvim/testdir/test_tabpage.vim +++ b/src/nvim/testdir/test_tabpage.vim @@ -35,7 +35,7 @@ function Test_tabpage() tabnew tabfirst call settabvar(2, 'val_num', 100) - call settabvar(2, 'val_str', 'SetTabVar test') + eval 'SetTabVar test'->settabvar(2, 'val_str') call settabvar(2, 'val_list', ['red', 'blue', 'green']) " call assert_true(gettabvar(2, 'val_num') == 100 && gettabvar(2, 'val_str') == 'SetTabVar test' && gettabvar(2, 'val_list') == ['red', 'blue', 'green']) @@ -128,6 +128,8 @@ function Test_tabpage() 1tabmove call assert_equal(2, tabpagenr()) + call assert_fails('let t = tabpagenr("@")', 'E15:') + call assert_equal(0, tabpagewinnr(-1)) call assert_fails("99tabmove", 'E16:') call assert_fails("+99tabmove", 'E16:') call assert_fails("-99tabmove", 'E16:') @@ -184,7 +186,7 @@ function Test_tabpage_with_autocmd() let s:li = split(join(map(copy(winr), 'gettabwinvar('.tabn.', v:val, "a")')), '\s\+') call assert_equal(['a', 'a'], s:li) let s:li = [] - C call map(copy(winr), 'settabwinvar('.tabn.', v:val, ''a'', v:val*2)') + C call map(copy(winr), '(v:val*2)->settabwinvar(' .. tabn .. ', v:val, ''a'')') let s:li = split(join(map(copy(winr), 'gettabwinvar('.tabn.', v:val, "a")')), '\s\+') call assert_equal(['2', '4'], s:li) @@ -683,4 +685,73 @@ func Test_tabline_tabmenu() %bw! endfunc +" Test for jumping to last accessed tabpage +func Test_lastused_tabpage() + tabonly! + call assert_equal(0, tabpagenr('#')) + call assert_beeps('call feedkeys("g\<Tab>", "xt")') + call assert_beeps('call feedkeys("\<C-Tab>", "xt")') + call assert_beeps('call feedkeys("\<C-W>g\<Tab>", "xt")') + call assert_fails('tabnext #', 'E475:') + + " open four tab pages + tabnew + tabnew + tabnew + + 2tabnext + + " Test for g<Tab> + call assert_equal(4, tabpagenr('#')) + call feedkeys("g\<Tab>", "xt") + call assert_equal(4, tabpagenr()) + call assert_equal(2, tabpagenr('#')) + + " Test for <C-Tab> + call feedkeys("\<C-Tab>", "xt") + call assert_equal(2, tabpagenr()) + call assert_equal(4, tabpagenr('#')) + + " Test for <C-W>g<Tab> + call feedkeys("\<C-W>g\<Tab>", "xt") + call assert_equal(4, tabpagenr()) + call assert_equal(2, tabpagenr('#')) + + " Test for :tabnext # + tabnext # + call assert_equal(2, tabpagenr()) + call assert_equal(4, tabpagenr('#')) + + " Try to jump to a closed tab page + tabclose # + call assert_equal(0, tabpagenr('#')) + call feedkeys("g\<Tab>", "xt") + call assert_equal(2, tabpagenr()) + call feedkeys("\<C-Tab>", "xt") + call assert_equal(2, tabpagenr()) + call feedkeys("\<C-W>g\<Tab>", "xt") + call assert_equal(2, tabpagenr()) + call assert_fails('tabnext #', 'E475:') + call assert_equal(2, tabpagenr()) + + " Test for :tabonly # + let wnum = win_getid() + $tabnew + tabonly # + call assert_equal(wnum, win_getid()) + call assert_equal(1, tabpagenr('$')) + + " Test for :tabmove # + tabnew + let wnum = win_getid() + tabnew + tabnew + tabnext 2 + tabmove # + call assert_equal(4, tabpagenr()) + call assert_equal(wnum, win_getid()) + + tabonly! +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_tagjump.vim b/src/nvim/testdir/test_tagjump.vim index 15182893e9..e0b05edf15 100644 --- a/src/nvim/testdir/test_tagjump.vim +++ b/src/nvim/testdir/test_tagjump.vim @@ -351,7 +351,7 @@ func Test_getsettagstack() " Try to set current index to invalid values call settagstack(1, {'curidx' : -1}) call assert_equal(1, gettagstack().curidx) - call settagstack(1, {'curidx' : 50}) + eval {'curidx' : 50}->settagstack(1) call assert_equal(4, gettagstack().curidx) " Try pushing invalid items onto the stack @@ -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_taglist.vim b/src/nvim/testdir/test_taglist.vim index e830813081..e11815ff33 100644 --- a/src/nvim/testdir/test_taglist.vim +++ b/src/nvim/testdir/test_taglist.vim @@ -14,7 +14,7 @@ func Test_taglist() split Xtext call assert_equal(['FFoo', 'BFoo'], map(taglist("Foo"), {i, v -> v.name})) - call assert_equal(['FFoo', 'BFoo'], map(taglist("Foo", "Xtext"), {i, v -> v.name})) + call assert_equal(['FFoo', 'BFoo'], map("Foo"->taglist("Xtext"), {i, v -> v.name})) call assert_equal(['FFoo', 'BFoo'], map(taglist("Foo", "Xfoo"), {i, v -> v.name})) call assert_equal(['BFoo', 'FFoo'], map(taglist("Foo", "Xbar"), {i, v -> v.name})) diff --git a/src/nvim/testdir/test_textformat.vim b/src/nvim/testdir/test_textformat.vim index bf0706a0c2..b6be3d8861 100644 --- a/src/nvim/testdir/test_textformat.vim +++ b/src/nvim/testdir/test_textformat.vim @@ -196,6 +196,143 @@ 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, '$')) + + " using 'indentexpr' instead of 'cindent' does not repeat a comment + setl nocindent indentexpr=2 + 3delete + normal 2Gox + let expected =<< trim END + nop; + val = val; // This is a comment + x + END + call assert_equal(expected, getline(1, '$')) + setl cindent indentexpr= + 3delete + + 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 +570,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_textobjects.vim b/src/nvim/testdir/test_textobjects.vim index c259453b5e..2b6bb8b302 100644 --- a/src/nvim/testdir/test_textobjects.vim +++ b/src/nvim/testdir/test_textobjects.vim @@ -421,4 +421,36 @@ func Test_textobj_quote() close! endfunc +" Test for i(, i<, etc. when cursor is in front of a block +func Test_textobj_find_paren_forward() + new + + " i< and a> when cursor is in front of a block + call setline(1, '#include <foo.h>') + normal 0yi< + call assert_equal('foo.h', @") + normal 0ya> + call assert_equal('<foo.h>', @") + + " 2i(, 3i( in front of a block enters second/third nested '(' + call setline(1, 'foo (bar (baz (quux)))') + normal 0yi) + call assert_equal('bar (baz (quux))', @") + normal 02yi) + call assert_equal('baz (quux)', @") + normal 03yi) + call assert_equal('quux', @") + + " 3i( in front of a block doesn't enter third but un-nested '(' + call setline(1, 'foo (bar (baz) (quux))') + normal 03di) + call assert_equal('foo (bar (baz) (quux))', getline(1)) + normal 02di) + call assert_equal('foo (bar () (quux))', getline(1)) + normal 0di) + call assert_equal('foo ()', getline(1)) + + close! +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_timers.vim b/src/nvim/testdir/test_timers.vim index ceaa5de92b..09eed4e10d 100644 --- a/src/nvim/testdir/test_timers.vim +++ b/src/nvim/testdir/test_timers.vim @@ -77,7 +77,7 @@ endfunc func Test_info() let id = timer_start(1000, 'MyHandler') - let info = timer_info(id) + let info = id->timer_info() call assert_equal(id, info[0]['id']) call assert_equal(1000, info[0]['time']) call assert_equal("function('MyHandler')", string(info[0]['callback'])) @@ -113,7 +113,7 @@ func Test_paused() let info = timer_info(id) call assert_equal(0, info[0]['paused']) - call timer_pause(id, 1) + eval id->timer_pause(1) let info = timer_info(id) call assert_equal(1, info[0]['paused']) sleep 200m @@ -148,7 +148,7 @@ func Test_delete_myself() endfunc func StopTimer1(timer) - let g:timer2 = timer_start(10, 'StopTimer2') + let g:timer2 = 10->timer_start('StopTimer2') " avoid maxfuncdepth error call timer_pause(g:timer1, 1) sleep 40m @@ -239,7 +239,7 @@ func FeedAndPeek(timer) endfunc func Interrupt(timer) - " call test_feedinput("\<C-C>") + " eval "\<C-C>"->test_feedinput() call nvim_input("\<C-C>") endfunc @@ -251,7 +251,7 @@ func Test_peek_and_get_char() let intr = timer_start(100, 'Interrupt') let c = getchar() call assert_equal(char2nr('a'), c) - call timer_stop(intr) + eval intr->timer_stop() endfunc func Test_getchar_zero() @@ -279,7 +279,7 @@ func Test_ex_mode() endfunc let timer = timer_start(40, function('g:Foo'), {'repeat':-1}) " This used to throw error E749. - exe "normal Qsleep 100m\rvi\r" + exe "normal gQsleep 100m\rvi\r" call timer_stop(timer) endfunc @@ -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_trycatch.vim b/src/nvim/testdir/test_trycatch.vim index 7e513180a3..adc1745b39 100644 --- a/src/nvim/testdir/test_trycatch.vim +++ b/src/nvim/testdir/test_trycatch.vim @@ -1972,6 +1972,29 @@ func Test_builtin_func_error() call assert_equal('jlmnpqrtueghivyzACD', g:Xpath) endfunc -" Modelines {{{1 +func Test_reload_in_try_catch() + call writefile(['x'], 'Xreload') + set autoread + edit Xreload + tabnew + call writefile(['xx'], 'Xreload') + augroup ReLoad + au FileReadPost Xreload let x = doesnotexist + au BufReadPost Xreload let x = doesnotexist + augroup END + try + edit Xreload + catch + endtry + tabnew + + tabclose + tabclose + autocmd! ReLoad + set noautoread + bwipe! Xreload + call delete('Xreload') +endfunc + +" Modeline {{{1 " vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker -"------------------------------------------------------------------------------- diff --git a/src/nvim/testdir/test_undo.vim b/src/nvim/testdir/test_undo.vim index c7dcaa0f36..30e00e7ad4 100644 --- a/src/nvim/testdir/test_undo.vim +++ b/src/nvim/testdir/test_undo.vim @@ -490,7 +490,7 @@ funct Test_undofile() call delete('Xundodir', 'd') " Test undofile() with 'undodir' set to a non-existing directory. - " call assert_equal('', undofile('Xundofoo')) + " call assert_equal('', 'Xundofoo'->undofile()) if isdirectory('/tmp') set undodir=/tmp diff --git a/src/nvim/testdir/test_usercommands.vim b/src/nvim/testdir/test_usercommands.vim index 29e578ac6d..e96e3d94e5 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,46 @@ 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 + +func DefCmd(name) + if len(a:name) > 30 + return + endif + exe 'command ' .. a:name .. ' call DefCmd("' .. a:name .. 'x")' + echo a:name + exe a:name +endfunc + +func Test_recursive_define() + call DefCmd('Command') + + let name = 'Command' + while len(name) < 30 + exe 'delcommand ' .. name + let name ..= 'x' + endwhile +endfunc + + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_utf8.vim b/src/nvim/testdir/test_utf8.vim index da72da087f..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 @@ -17,7 +18,7 @@ func Test_strchars() let exp = [[1, 1, 1], [3, 3, 3], [2, 2, 1], [3, 3, 1], [1, 1, 1]] for i in range(len(inp)) call assert_equal(exp[i][0], strchars(inp[i])) - call assert_equal(exp[i][1], strchars(inp[i], 0)) + call assert_equal(exp[i][1], inp[i]->strchars(0)) call assert_equal(exp[i][2], strchars(inp[i], 1)) endfor endfunc @@ -69,7 +70,7 @@ func Test_screenchar_utf8() call setline(1, ["ABC\u0308"]) redraw call assert_equal([0x0041], screenchars(1, 1)) - call assert_equal([0x0042], screenchars(1, 2)) + call assert_equal([0x0042], 1->screenchars(2)) call assert_equal([0x0043, 0x0308], screenchars(1, 3)) call assert_equal("A", screenstring(1, 1)) call assert_equal("B", screenstring(1, 2)) @@ -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 2fbf130345..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 @@ -330,7 +340,7 @@ func Test_vartabs_shiftwidth() let lines = ScreenLines([1, 2], winwidth(0)) call s:compare_lines(expect2, lines) call assert_equal(20, shiftwidth(virtcol('.')-2)) - call assert_equal(30, shiftwidth(virtcol('.'))) + call assert_equal(30, virtcol('.')->shiftwidth()) norm! $>> let expect3 = [' ', ' x ', '~ '] let lines = ScreenLines([1, 3], winwidth(0)) 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 dbabdcf427..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,31 +162,35 @@ 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. func Test_virtual_replace() - throw 'skipped: TODO: ' if exists('&t_kD') let save_t_kD = &t_kD endif @@ -166,7 +212,6 @@ func Test_virtual_replace() \ ], getline(1, 6)) normal G mark a - inoremap <C-D> <Del> exe "normal o0\<C-D>\nabcdefghi\njk\tlmn\n opq\trst\n\<C-D>uvwxyz\n" exe "normal 'ajgR0\<C-D> 1\nA\nBCDEFGHIJ\n\tKL\nMNO\nPQR" . repeat("\<BS>", 29) call assert_equal([' 1', @@ -244,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.']) @@ -351,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.', @@ -411,6 +416,17 @@ func Test_Visual_paragraph_textobject() bwipe! endfunc +func Test_curswant_not_changed() + new + call setline(1, ['one', 'two']) + au InsertLeave * call getcurpos() + call feedkeys("gg0\<C-V>jI123 \<Esc>j", 'xt') + call assert_equal([0, 2, 1, 0, 1], getcurpos()) + + bwipe! + au! InsertLeave +endfunc + " Tests for "vaBiB", end could be wrong. func Test_Visual_Block() new @@ -435,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 @@ -612,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 @@ -647,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> @@ -746,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 @@ -766,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 @@ -997,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 @@ -1092,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 @@ -1106,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 039de0c623..ef6dec580f 100644 --- a/src/nvim/testdir/test_window_cmd.vim +++ b/src/nvim/testdir/test_window_cmd.vim @@ -72,7 +72,7 @@ endfunc func Test_window_quit() e Xa split Xb - call assert_equal(2, winnr('$')) + call assert_equal(2, '$'->winnr()) call assert_equal('Xb', bufname(winbufnr(1))) call assert_equal('Xa', bufname(winbufnr(2))) @@ -88,7 +88,7 @@ func Test_window_horizontal_split() 3wincmd s call assert_equal(2, winnr('$')) call assert_equal(3, winheight(0)) - call assert_equal(winwidth(1), winwidth(2)) + call assert_equal(winwidth(1), 2->winwidth()) call assert_fails('botright topleft wincmd s', 'E442:') bw @@ -267,7 +267,7 @@ func Test_window_height() wincmd + call assert_equal(wh1, winheight(1)) - call assert_equal(wh2, winheight(2)) + call assert_equal(wh2, 2->winheight()) 2wincmd _ call assert_equal(2, winheight(1)) @@ -452,7 +452,7 @@ func Test_window_newtab() wincmd T call assert_equal(2, tabpagenr('$')) call assert_equal(['Xb', 'Xa'], map(tabpagebuflist(1), 'bufname(v:val)')) - call assert_equal(['Xc' ], map(tabpagebuflist(2), 'bufname(v:val)')) + call assert_equal(['Xc' ], map(2->tabpagebuflist(), 'bufname(v:val)')) %bw! endfunc @@ -575,14 +575,17 @@ func Test_winrestcmd() only endfunc -function! Fun_RenewFile() +func Fun_RenewFile() " Need to wait a bit for the timestamp to be older. - sleep 2 - silent execute '!echo "1" > tmp.txt' + let old_ftime = getftime("tmp.txt") + while getftime("tmp.txt") == old_ftime + sleep 100m + silent execute '!echo "1" > tmp.txt' + endwhile sp wincmd p edit! tmp.txt -endfunction +endfunc func Test_window_prevwin() " Can we make this work on MS-Windows? @@ -814,13 +817,25 @@ func Test_winnr() tabnew call assert_equal(8, tabpagewinnr(1, 'j')) - call assert_equal(2, tabpagewinnr(1, 'k')) + call assert_equal(2, 1->tabpagewinnr('k')) call assert_equal(4, tabpagewinnr(1, 'h')) call assert_equal(6, tabpagewinnr(1, 'l')) only | tabonly endfunc +func Test_winrestview() + split runtest.vim + normal 50% + let view = winsaveview() + close + split runtest.vim + eval view->winrestview() + call assert_equal(view, winsaveview()) + + bwipe! +endfunc + func Test_win_splitmove() edit a leftabove split b @@ -924,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_window_id.vim b/src/nvim/testdir/test_window_id.vim index d10d831650..8bf4ede350 100644 --- a/src/nvim/testdir/test_window_id.vim +++ b/src/nvim/testdir/test_window_id.vim @@ -67,7 +67,7 @@ func Test_win_getid() call win_gotoid(id2) call assert_equal("two", expand("%")) - call win_gotoid(id4) + eval id4->win_gotoid() call assert_equal("four", expand("%")) call win_gotoid(id1) call assert_equal("one", expand("%")) @@ -75,17 +75,17 @@ func Test_win_getid() call assert_equal("five", expand("%")) call assert_equal(0, win_id2win(9999)) - call assert_equal(nr5, win_id2win(id5)) + call assert_equal(nr5, id5->win_id2win()) call assert_equal(0, win_id2win(id1)) tabnext call assert_equal(nr1, win_id2win(id1)) call assert_equal([0, 0], win_id2tabwin(9999)) - call assert_equal([1, nr2], win_id2tabwin(id2)) + call assert_equal([1, nr2], id2->win_id2tabwin()) call assert_equal([2, nr4], win_id2tabwin(id4)) call assert_equal([], win_findbuf(9999)) - call assert_equal([id2], win_findbuf(bufnr2)) + call assert_equal([id2], bufnr2->win_findbuf()) call win_gotoid(id5) split call assert_equal(sort([id5, win_getid()]), sort(win_findbuf(bufnr5))) @@ -98,7 +98,7 @@ func Test_win_getid_curtab() tabfirst copen only - call assert_equal(win_getid(1), win_getid(1, 1)) + call assert_equal(win_getid(1), 1->win_getid( 1)) tabclose! endfunc @@ -120,4 +120,11 @@ func Test_winlayout() call assert_equal(['col', [['leaf', w3], ['row', [['leaf', w4], ['leaf', w2]]], ['leaf', w1]]], winlayout()) only! + + let w1 = win_getid() + call assert_equal(['leaf', w1], winlayout(1)) + tabnew + let w2 = win_getid() + call assert_equal(['leaf', w2], 2->winlayout()) + tabclose endfunc 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/testing.c b/src/nvim/testing.c new file mode 100644 index 0000000000..5771ec4445 --- /dev/null +++ b/src/nvim/testing.c @@ -0,0 +1,562 @@ +// 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 + +// testing.c: Support for tests + +#include "nvim/eval/encode.h" +#include "nvim/ex_docmd.h" +#include "nvim/eval.h" +#include "nvim/os/os.h" +#include "nvim/testing.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "testing.c.generated.h" +#endif + +/// Prepare "gap" for an assert error and add the sourcing position. +static void prepare_assert_error(garray_T *gap) +{ + char buf[NUMBUFLEN]; + + ga_init(gap, 1, 100); + if (sourcing_name != NULL) { + ga_concat(gap, (char *)sourcing_name); + if (sourcing_lnum > 0) { + ga_concat(gap, " "); + } + } + if (sourcing_lnum > 0) { + vim_snprintf(buf, ARRAY_SIZE(buf), "line %" PRId64, (int64_t)sourcing_lnum); + ga_concat(gap, buf); + } + if (sourcing_name != NULL || sourcing_lnum > 0) { + ga_concat(gap, ": "); + } +} + +/// Append "p[clen]" to "gap", escaping unprintable characters. +/// Changes NL to \n, CR to \r, etc. +static void ga_concat_esc(garray_T *gap, const char_u *p, int clen) + FUNC_ATTR_NONNULL_ALL +{ + char_u buf[NUMBUFLEN]; + + if (clen > 1) { + memmove(buf, p, (size_t)clen); + buf[clen] = NUL; + ga_concat(gap, (char *)buf); + } else { + switch (*p) { + case BS: + ga_concat(gap, "\\b"); break; + case ESC: + ga_concat(gap, "\\e"); break; + case FF: + ga_concat(gap, "\\f"); break; + case NL: + ga_concat(gap, "\\n"); break; + case TAB: + ga_concat(gap, "\\t"); break; + case CAR: + ga_concat(gap, "\\r"); break; + case '\\': + ga_concat(gap, "\\\\"); break; + default: + if (*p < ' ') { + vim_snprintf((char *)buf, NUMBUFLEN, "\\x%02x", *p); + ga_concat(gap, (char *)buf); + } else { + ga_append(gap, (char)(*p)); + } + break; + } + } +} + +/// Append "str" to "gap", escaping unprintable characters. +/// Changes NL to \n, CR to \r, etc. +static void ga_concat_shorten_esc(garray_T *gap, const char_u *str) + FUNC_ATTR_NONNULL_ARG(1) +{ + char_u buf[NUMBUFLEN]; + + if (str == NULL) { + ga_concat(gap, "NULL"); + return; + } + + for (const char_u *p = str; *p != NUL; p++) { + int same_len = 1; + const char_u *s = p; + const int c = mb_ptr2char_adv(&s); + const int clen = (int)(s - p); + while (*s != NUL && c == utf_ptr2char(s)) { + same_len++; + s += clen; + } + if (same_len > 20) { + ga_concat(gap, "\\["); + ga_concat_esc(gap, p, clen); + ga_concat(gap, " occurs "); + vim_snprintf((char *)buf, NUMBUFLEN, "%d", same_len); + ga_concat(gap, (char *)buf); + ga_concat(gap, " times]"); + p = s - 1; + } else { + ga_concat_esc(gap, p, clen); + } + } +} + +/// Fill "gap" with information about an assert error. +static void fill_assert_error(garray_T *gap, typval_T *opt_msg_tv, char_u *exp_str, + typval_T *exp_tv, typval_T *got_tv, assert_type_T atype) +{ + char_u *tofree; + + if (opt_msg_tv->v_type != VAR_UNKNOWN) { + tofree = (char_u *)encode_tv2echo(opt_msg_tv, NULL); + ga_concat(gap, (char *)tofree); + xfree(tofree); + ga_concat(gap, ": "); + } + + if (atype == ASSERT_MATCH || atype == ASSERT_NOTMATCH) { + ga_concat(gap, "Pattern "); + } else if (atype == ASSERT_NOTEQUAL) { + ga_concat(gap, "Expected not equal to "); + } else { + ga_concat(gap, "Expected "); + } + + if (exp_str == NULL) { + tofree = (char_u *)encode_tv2string(exp_tv, NULL); + ga_concat_shorten_esc(gap, tofree); + xfree(tofree); + } else { + ga_concat_shorten_esc(gap, exp_str); + } + + if (atype != ASSERT_NOTEQUAL) { + if (atype == ASSERT_MATCH) { + ga_concat(gap, " does not match "); + } else if (atype == ASSERT_NOTMATCH) { + ga_concat(gap, " does match "); + } else { + ga_concat(gap, " but got "); + } + tofree = (char_u *)encode_tv2string(got_tv, NULL); + ga_concat_shorten_esc(gap, tofree); + xfree(tofree); + } +} + +static int assert_equal_common(typval_T *argvars, assert_type_T atype) + FUNC_ATTR_NONNULL_ALL +{ + garray_T ga; + + if (tv_equal(&argvars[0], &argvars[1], false, false) + != (atype == ASSERT_EQUAL)) { + prepare_assert_error(&ga); + fill_assert_error(&ga, &argvars[2], NULL, + &argvars[0], &argvars[1], atype); + assert_error(&ga); + ga_clear(&ga); + return 1; + } + return 0; +} + +static int assert_match_common(typval_T *argvars, assert_type_T atype) + FUNC_ATTR_NONNULL_ALL +{ + char buf1[NUMBUFLEN]; + char buf2[NUMBUFLEN]; + const char *const pat = tv_get_string_buf_chk(&argvars[0], buf1); + const char *const text = tv_get_string_buf_chk(&argvars[1], buf2); + + if (pat == NULL || text == NULL) { + emsg(_(e_invarg)); + } else if (pattern_match((char_u *)pat, (char_u *)text, false) + != (atype == ASSERT_MATCH)) { + garray_T ga; + prepare_assert_error(&ga); + fill_assert_error(&ga, &argvars[2], NULL, &argvars[0], &argvars[1], atype); + assert_error(&ga); + ga_clear(&ga); + return 1; + } + return 0; +} + +/// Common for assert_true() and assert_false(). +static int assert_bool(typval_T *argvars, bool is_true) + FUNC_ATTR_NONNULL_ALL +{ + bool error = false; + garray_T ga; + + if ((argvars[0].v_type != VAR_NUMBER + || (tv_get_number_chk(&argvars[0], &error) == 0) == is_true + || error) + && (argvars[0].v_type != VAR_BOOL + || (argvars[0].vval.v_bool + != (BoolVarValue)(is_true + ? kBoolVarTrue + : kBoolVarFalse)))) { + prepare_assert_error(&ga); + fill_assert_error(&ga, &argvars[1], + (char_u *)(is_true ? "True" : "False"), + NULL, &argvars[0], ASSERT_OTHER); + assert_error(&ga); + ga_clear(&ga); + return 1; + } + return 0; +} + +static void assert_append_cmd_or_arg(garray_T *gap, typval_T *argvars, const char *cmd) + FUNC_ATTR_NONNULL_ALL +{ + if (argvars[1].v_type != VAR_UNKNOWN && argvars[2].v_type != VAR_UNKNOWN) { + char *const tofree = encode_tv2echo(&argvars[2], NULL); + ga_concat(gap, tofree); + xfree(tofree); + } else { + ga_concat(gap, cmd); + } +} + +static int assert_beeps(typval_T *argvars, bool no_beep) + FUNC_ATTR_NONNULL_ALL +{ + const char *const cmd = tv_get_string_chk(&argvars[0]); + int ret = 0; + + called_vim_beep = false; + suppress_errthrow = true; + emsg_silent = false; + do_cmdline_cmd(cmd); + if (no_beep ? called_vim_beep : !called_vim_beep) { + garray_T ga; + prepare_assert_error(&ga); + if (no_beep) { + ga_concat(&ga, "command did beep: "); + } else { + ga_concat(&ga, "command did not beep: "); + } + ga_concat(&ga, cmd); + assert_error(&ga); + ga_clear(&ga); + ret = 1; + } + + suppress_errthrow = false; + emsg_on_display = false; + return ret; +} + +/// "assert_beeps(cmd [, error])" function +void f_assert_beeps(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->vval.v_number = assert_beeps(argvars, false); +} + +/// "assert_nobeep(cmd [, error])" function +void f_assert_nobeep(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->vval.v_number = assert_beeps(argvars, true); +} + +/// "assert_equal(expected, actual[, msg])" function +void f_assert_equal(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->vval.v_number = assert_equal_common(argvars, ASSERT_EQUAL); +} + +static int assert_equalfile(typval_T *argvars) + FUNC_ATTR_NONNULL_ALL +{ + char buf1[NUMBUFLEN]; + char buf2[NUMBUFLEN]; + const char *const fname1 = tv_get_string_buf_chk(&argvars[0], buf1); + const char *const fname2 = tv_get_string_buf_chk(&argvars[1], buf2); + garray_T ga; + + if (fname1 == NULL || fname2 == NULL) { + return 0; + } + + IObuff[0] = NUL; + FILE *const fd1 = os_fopen(fname1, READBIN); + char line1[200]; + char line2[200]; + ptrdiff_t lineidx = 0; + if (fd1 == NULL) { + snprintf((char *)IObuff, IOSIZE, (char *)e_notread, fname1); + } else { + FILE *const fd2 = os_fopen(fname2, READBIN); + if (fd2 == NULL) { + fclose(fd1); + snprintf((char *)IObuff, IOSIZE, (char *)e_notread, fname2); + } else { + int64_t linecount = 1; + for (int64_t count = 0;; count++) { + const int c1 = fgetc(fd1); + const int c2 = fgetc(fd2); + if (c1 == EOF) { + if (c2 != EOF) { + STRCPY(IObuff, "first file is shorter"); + } + break; + } else if (c2 == EOF) { + STRCPY(IObuff, "second file is shorter"); + break; + } else { + line1[lineidx] = (char)c1; + line2[lineidx] = (char)c2; + lineidx++; + if (c1 != c2) { + snprintf((char *)IObuff, IOSIZE, + "difference at byte %" PRId64 ", line %" PRId64, + count, linecount); + break; + } + } + if (c1 == NL) { + linecount++; + lineidx = 0; + } else if (lineidx + 2 == (ptrdiff_t)sizeof(line1)) { + memmove(line1, line1 + 100, (size_t)(lineidx - 100)); + memmove(line2, line2 + 100, (size_t)(lineidx - 100)); + lineidx -= 100; + } + } + fclose(fd1); + fclose(fd2); + } + } + if (IObuff[0] != NUL) { + prepare_assert_error(&ga); + if (argvars[2].v_type != VAR_UNKNOWN) { + char *const tofree = encode_tv2echo(&argvars[2], NULL); + ga_concat(&ga, tofree); + xfree(tofree); + ga_concat(&ga, ": "); + } + ga_concat(&ga, (char *)IObuff); + if (lineidx > 0) { + line1[lineidx] = NUL; + line2[lineidx] = NUL; + ga_concat(&ga, " after \""); + ga_concat(&ga, line1); + if (STRCMP(line1, line2) != 0) { + ga_concat(&ga, "\" vs \""); + ga_concat(&ga, line2); + } + ga_concat(&ga, "\""); + } + assert_error(&ga); + ga_clear(&ga); + return 1; + } + return 0; +} + +/// "assert_equalfile(fname-one, fname-two[, msg])" function +void f_assert_equalfile(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->vval.v_number = assert_equalfile(argvars); +} + +/// "assert_notequal(expected, actual[, msg])" function +void f_assert_notequal(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->vval.v_number = assert_equal_common(argvars, ASSERT_NOTEQUAL); +} + +/// "assert_exception(string[, msg])" function +void f_assert_exception(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + garray_T ga; + + const char *const error = tv_get_string_chk(&argvars[0]); + if (*get_vim_var_str(VV_EXCEPTION) == NUL) { + prepare_assert_error(&ga); + ga_concat(&ga, "v:exception is not set"); + assert_error(&ga); + ga_clear(&ga); + rettv->vval.v_number = 1; + } else if (error != NULL + && strstr((char *)get_vim_var_str(VV_EXCEPTION), error) == NULL) { + prepare_assert_error(&ga); + fill_assert_error(&ga, &argvars[1], NULL, &argvars[0], + get_vim_var_tv(VV_EXCEPTION), ASSERT_OTHER); + assert_error(&ga); + ga_clear(&ga); + rettv->vval.v_number = 1; + } +} + +/// "assert_fails(cmd [, error [, msg]])" function +void f_assert_fails(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + const char *const cmd = tv_get_string_chk(&argvars[0]); + garray_T ga; + int save_trylevel = trylevel; + + // trylevel must be zero for a ":throw" command to be considered failed + trylevel = 0; + called_emsg = false; + suppress_errthrow = true; + emsg_silent = true; + + do_cmdline_cmd(cmd); + if (!called_emsg) { + prepare_assert_error(&ga); + ga_concat(&ga, "command did not fail: "); + assert_append_cmd_or_arg(&ga, argvars, cmd); + assert_error(&ga); + ga_clear(&ga); + rettv->vval.v_number = 1; + } else if (argvars[1].v_type != VAR_UNKNOWN) { + char buf[NUMBUFLEN]; + const char *const error = tv_get_string_buf_chk(&argvars[1], buf); + + if (error == NULL + || strstr((char *)get_vim_var_str(VV_ERRMSG), error) == NULL) { + prepare_assert_error(&ga); + fill_assert_error(&ga, &argvars[2], NULL, &argvars[1], + get_vim_var_tv(VV_ERRMSG), ASSERT_OTHER); + ga_concat(&ga, ": "); + assert_append_cmd_or_arg(&ga, argvars, cmd); + assert_error(&ga); + ga_clear(&ga); + rettv->vval.v_number = 1; + } + } + + trylevel = save_trylevel; + called_emsg = false; + suppress_errthrow = false; + emsg_silent = false; + emsg_on_display = false; + set_vim_var_string(VV_ERRMSG, NULL, 0); +} + +// "assert_false(actual[, msg])" function +void f_assert_false(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->vval.v_number = assert_bool(argvars, false); +} + +static int assert_inrange(typval_T *argvars) + FUNC_ATTR_NONNULL_ALL +{ + bool error = false; + + if (argvars[0].v_type == VAR_FLOAT + || argvars[1].v_type == VAR_FLOAT + || argvars[2].v_type == VAR_FLOAT) { + const float_T flower = tv_get_float(&argvars[0]); + const float_T fupper = tv_get_float(&argvars[1]); + const float_T factual = tv_get_float(&argvars[2]); + + if (factual < flower || factual > fupper) { + garray_T ga; + prepare_assert_error(&ga); + if (argvars[3].v_type != VAR_UNKNOWN) { + char_u *const tofree = (char_u *)encode_tv2string(&argvars[3], NULL); + ga_concat(&ga, (char *)tofree); + xfree(tofree); + } else { + char msg[80]; + vim_snprintf(msg, sizeof(msg), "Expected range %g - %g, but got %g", + flower, fupper, factual); + ga_concat(&ga, msg); + } + assert_error(&ga); + ga_clear(&ga); + return 1; + } + } else { + const varnumber_T lower = tv_get_number_chk(&argvars[0], &error); + const varnumber_T upper = tv_get_number_chk(&argvars[1], &error); + const varnumber_T actual = tv_get_number_chk(&argvars[2], &error); + + if (error) { + return 0; + } + if (actual < lower || actual > upper) { + garray_T ga; + prepare_assert_error(&ga); + + char msg[55]; + vim_snprintf(msg, sizeof(msg), + "range %" PRIdVARNUMBER " - %" PRIdVARNUMBER ",", + lower, upper); // -V576 + fill_assert_error(&ga, &argvars[3], (char_u *)msg, NULL, &argvars[2], + ASSERT_INRANGE); + assert_error(&ga); + ga_clear(&ga); + return 1; + } + } + return 0; +} + +/// "assert_inrange(lower, upper[, msg])" function +void f_assert_inrange(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->vval.v_number = assert_inrange(argvars); +} + +/// "assert_match(pattern, actual[, msg])" function +void f_assert_match(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->vval.v_number = assert_match_common(argvars, ASSERT_MATCH); +} + +/// "assert_notmatch(pattern, actual[, msg])" function +void f_assert_notmatch(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->vval.v_number = assert_match_common(argvars, ASSERT_NOTMATCH); +} + +/// "assert_report(msg)" function +void f_assert_report(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + garray_T ga; + + prepare_assert_error(&ga); + ga_concat(&ga, tv_get_string(&argvars[0])); + assert_error(&ga); + ga_clear(&ga); + rettv->vval.v_number = 1; +} + +/// "assert_true(actual[, msg])" function +void f_assert_true(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->vval.v_number = assert_bool(argvars, true); +} + +/// "test_garbagecollect_now()" function +void f_test_garbagecollect_now(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + // This is dangerous, any Lists and Dicts used internally may be freed + // while still in use. + garbage_collect(true); +} + +/// "test_write_list_log()" function +void f_test_write_list_log(typval_T *const argvars, typval_T *const rettv, FunPtr fptr) +{ + const char *const fname = tv_get_string_chk(&argvars[0]); + if (fname == NULL) { + return; + } + list_write_log(fname); +} + diff --git a/src/nvim/testing.h b/src/nvim/testing.h new file mode 100644 index 0000000000..34ee1d10df --- /dev/null +++ b/src/nvim/testing.h @@ -0,0 +1,10 @@ +#ifndef NVIM_TESTING_H +#define NVIM_TESTING_H + +#include "nvim/eval/typval.h" +#include "nvim/eval/funcs.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "testing.h.generated.h" +#endif +#endif // NVIM_TESTING_H diff --git a/src/nvim/tui/input.c b/src/nvim/tui/input.c index 5bb6059fa7..17656c5ddc 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" @@ -19,6 +19,7 @@ # include "nvim/os/os_win_console.h" #endif #include "nvim/event/rstream.h" +#include "nvim/msgpack_rpc/channel.h" #define KEY_BUFFER_SIZE 0xfff @@ -114,20 +115,39 @@ static void tinput_done_event(void **argv) static void tinput_wait_enqueue(void **argv) { TermInput *input = argv[0]; - RBUFFER_UNTIL_EMPTY(input->key_buffer, buf, len) { - const String keys = { .data = buf, .size = len }; - if (input->paste) { - String copy = copy_string(keys); + if (input->paste) { // produce exactly one paste event + const size_t len = rbuffer_size(input->key_buffer); + String keys = { .data = xmallocz(len), .size = len }; + rbuffer_read(input->key_buffer, keys.data, len); + if (ui_client_channel_id) { + Array args = ARRAY_DICT_INIT; + ADD(args, STRING_OBJ(keys)); // 'data' + ADD(args, BOOLEAN_OBJ(true)); // 'crlf' + ADD(args, INTEGER_OBJ(input->paste)); // 'phase' + rpc_send_event(ui_client_channel_id, "nvim_paste", args); + } else { multiqueue_put(main_loop.events, tinput_paste_event, 3, - copy.data, copy.size, (intptr_t)input->paste); - if (input->paste == 1) { - // Paste phase: "continue" - input->paste = 2; + keys.data, keys.size, (intptr_t)input->paste); + } + if (input->paste == 1) { + // Paste phase: "continue" + input->paste = 2; + } + rbuffer_reset(input->key_buffer); + } else { // enqueue input for the main thread or Nvim server + RBUFFER_UNTIL_EMPTY(input->key_buffer, buf, len) { + const String keys = { .data = buf, .size = len }; + size_t consumed; + if (ui_client_channel_id) { + Array args = ARRAY_DICT_INIT; + Error err = ERROR_INIT; + ADD(args, STRING_OBJ(copy_string(keys))); + // TODO(bfredl): could be non-blocking now with paste? + Object result = rpc_send_call(ui_client_channel_id, "nvim_input", args, &err); + consumed = result.type == kObjectTypeInteger ? (size_t)result.data.integer : 0; + } else { + consumed = input_enqueue(keys); } - rbuffer_consumed(input->key_buffer, len); - rbuffer_reset(input->key_buffer); - } else { - const size_t consumed = input_enqueue(keys); if (consumed) { rbuffer_consumed(input->key_buffer, consumed); } @@ -221,7 +241,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] = '-'; @@ -309,7 +329,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) { @@ -373,7 +392,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; @@ -420,22 +439,6 @@ static HandleState handle_bracketed_paste(TermInput *input) return kNotApplicable; } -// ESC NUL => <Esc> -static bool handle_forced_escape(TermInput *input) -{ - if (rbuffer_size(input->read_stream.buffer) > 1 - && !rbuffer_cmp(input->read_stream.buffer, "\x1b\x00", 2)) { - // skip the ESC and NUL and push one <esc> to the input buffer - size_t rcnt; - termkey_push_bytes(input->tk, rbuffer_read_ptr(input->read_stream.buffer, - &rcnt), 1); - rbuffer_consumed(input->read_stream.buffer, 2); - tk_getkeys(input, true); - return true; - } - return false; -} - static void set_bg_deferred(void **argv) { char *bgvalue = argv[0]; @@ -564,7 +567,6 @@ static void handle_raw_buffer(TermInput *input, bool force) if (!force && (handle_focus_event(input) || (is_paste = handle_bracketed_paste(input)) != kNotApplicable - || handle_forced_escape(input) || (is_bc = handle_background_color(input)) != kNotApplicable)) { if (is_paste == kIncomplete || is_bc == kIncomplete) { // Wait for the next input, leaving it in the raw buffer due to an diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index bb75286369..a67bcf98dc 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -131,6 +131,7 @@ typedef struct { int get_bg; int set_underline_style; int set_underline_color; + int enable_extended_keys, disable_extended_keys; } unibi_ext; char *space_buf; } TUIData; @@ -168,7 +169,7 @@ UI *tui_start(void) ui->set_title = tui_set_title; ui->set_icon = tui_set_icon; ui->screenshot = tui_screenshot; - ui->option_set= tui_option_set; + ui->option_set = tui_option_set; ui->raw_line = tui_raw_line; memset(ui->ui_ext, 0, sizeof(ui->ui_ext)); @@ -225,6 +226,8 @@ static void terminfo_start(UI *ui) data->unibi_ext.reset_cursor_style = -1; data->unibi_ext.get_bg = -1; data->unibi_ext.set_underline_color = -1; + data->unibi_ext.enable_extended_keys = -1; + data->unibi_ext.disable_extended_keys = -1; data->out_fd = STDOUT_FILENO; data->out_isatty = os_isatty(data->out_fd); @@ -308,23 +311,43 @@ static void terminfo_start(UI *ui) // Enable bracketed paste unibi_out_ext(ui, data->unibi_ext.enable_bracketed_paste); + // Enable extended keys (also known as 'modifyOtherKeys' or CSI u). On terminals that don't + // support this, this sequence is ignored. + unibi_out_ext(ui, data->unibi_ext.enable_extended_keys); + + int ret; uv_loop_init(&data->write_loop); if (data->out_isatty) { - uv_tty_init(&data->write_loop, &data->output_handle.tty, data->out_fd, 0); + ret = uv_tty_init(&data->write_loop, &data->output_handle.tty, data->out_fd, 0); + if (ret) { + ELOG("uv_tty_init failed: %s", uv_strerror(ret)); + } #ifdef WIN32 - uv_tty_set_mode(&data->output_handle.tty, UV_TTY_MODE_RAW); + ret = uv_tty_set_mode(&data->output_handle.tty, UV_TTY_MODE_RAW); + if (ret) { + ELOG("uv_tty_set_mode failed: %s", uv_strerror(ret)); + } #else int retry_count = 10; // A signal may cause uv_tty_set_mode() to fail (e.g., SIGCONT). Retry a // few times. #12322 - while (uv_tty_set_mode(&data->output_handle.tty, UV_TTY_MODE_IO) == UV_EINTR + while ((ret = uv_tty_set_mode(&data->output_handle.tty, UV_TTY_MODE_IO)) == UV_EINTR && retry_count > 0) { retry_count--; } + if (ret) { + ELOG("uv_tty_set_mode failed: %s", uv_strerror(ret)); + } #endif } else { - uv_pipe_init(&data->write_loop, &data->output_handle.pipe, 0); - uv_pipe_open(&data->output_handle.pipe, data->out_fd); + ret = uv_pipe_init(&data->write_loop, &data->output_handle.pipe, 0); + if (ret) { + ELOG("uv_pipe_init failed: %s", uv_strerror(ret)); + } + ret = uv_pipe_open(&data->output_handle.pipe, data->out_fd); + if (ret) { + ELOG("uv_pipe_open failed: %s", uv_strerror(ret)); + } } flush_buf(ui); } @@ -349,6 +372,8 @@ static void terminfo_stop(UI *ui) unibi_out_ext(ui, data->unibi_ext.disable_bracketed_paste); // Disable focus reporting unibi_out_ext(ui, data->unibi_ext.disable_focus_reporting); + // Disable extended keys + unibi_out_ext(ui, data->unibi_ext.disable_extended_keys); flush_buf(ui); uv_tty_reset_mode(); uv_close((uv_handle_t *)&data->output_handle, NULL); @@ -512,7 +537,7 @@ static bool attrs_differ(UI *ui, int id1, int id2, bool rgb) return a1.cterm_fg_color != a2.cterm_fg_color || a1.cterm_bg_color != a2.cterm_bg_color || a1.cterm_ae_attr != a2.cterm_ae_attr - || (a1.cterm_ae_attr & (HL_UNDERLINE|HL_UNDERCURL) + || (a1.cterm_ae_attr & HL_ANY_UNDERLINE && a1.rgb_sp_color != a2.rgb_sp_color); } } @@ -536,15 +561,27 @@ static void update_attrs(UI *ui, int attr_id) bool strikethrough = attr & HL_STRIKETHROUGH; bool underline; + bool underlineline; bool undercurl; + bool underdot; + bool underdash; if (data->unibi_ext.set_underline_style != -1) { underline = attr & HL_UNDERLINE; + underlineline = attr & HL_UNDERLINELINE; undercurl = attr & HL_UNDERCURL; + underdash = attr & HL_UNDERDASH; + underdot = attr & HL_UNDERDOT; } else { - underline = (attr & HL_UNDERLINE) || (attr & HL_UNDERCURL); + underline = attr & HL_ANY_UNDERLINE; + underlineline = false; undercurl = false; + underdot = false; + underdash = false; } + bool has_any_underline = undercurl || underline + || underdot || underdash || underlineline; + if (unibi_get_str(data->ut, unibi_set_attributes)) { if (bold || reverse || underline || standout) { UNIBI_SET_NUM_VAR(data->params[0], standout); @@ -583,11 +620,24 @@ static void update_attrs(UI *ui, int attr_id) if (strikethrough && data->unibi_ext.enter_strikethrough_mode != -1) { unibi_out_ext(ui, data->unibi_ext.enter_strikethrough_mode); } + if (underlineline && data->unibi_ext.set_underline_style != -1) { + UNIBI_SET_NUM_VAR(data->params[0], 2); + unibi_out_ext(ui, data->unibi_ext.set_underline_style); + } if (undercurl && data->unibi_ext.set_underline_style != -1) { UNIBI_SET_NUM_VAR(data->params[0], 3); unibi_out_ext(ui, data->unibi_ext.set_underline_style); } - if ((undercurl || underline) && data->unibi_ext.set_underline_color != -1) { + if (underdot && data->unibi_ext.set_underline_style != -1) { + UNIBI_SET_NUM_VAR(data->params[0], 4); + unibi_out_ext(ui, data->unibi_ext.set_underline_style); + } + if (underdash && data->unibi_ext.set_underline_style != -1) { + UNIBI_SET_NUM_VAR(data->params[0], 5); + unibi_out_ext(ui, data->unibi_ext.set_underline_style); + } + + if (has_any_underline && data->unibi_ext.set_underline_color != -1) { int color = attrs.rgb_sp_color; if (color != -1) { UNIBI_SET_NUM_VAR(data->params[0], (color >> 16) & 0xff); // red @@ -636,13 +686,13 @@ static void update_attrs(UI *ui, int attr_id) data->default_attr = fg == -1 && bg == -1 - && !bold && !italic && !underline && !undercurl && !reverse && !standout + && !bold && !italic && !has_any_underline && !reverse && !standout && !strikethrough; // Non-BCE terminals can't clear with non-default background color. Some BCE // terminals don't support attributes either, so don't rely on it. But assume // italic and bold has no effect if there is no text. - data->can_clear_attr = !reverse && !standout && !underline && !undercurl + data->can_clear_attr = !reverse && !standout && !has_any_underline && !strikethrough && (data->bce || bg == -1); } @@ -1086,8 +1136,14 @@ static void tui_mode_change(UI *ui, String mode, Integer mode_idx) // after calling uv_tty_set_mode. So, set the mode of the TTY again here. // #13073 if (data->is_starting && data->input.in_fd == STDERR_FILENO) { - uv_tty_set_mode(&data->output_handle.tty, UV_TTY_MODE_NORMAL); - uv_tty_set_mode(&data->output_handle.tty, UV_TTY_MODE_IO); + int ret = uv_tty_set_mode(&data->output_handle.tty, UV_TTY_MODE_NORMAL); + if (ret) { + ELOG("uv_tty_set_mode failed: %s", uv_strerror(ret)); + } + ret = uv_tty_set_mode(&data->output_handle.tty, UV_TTY_MODE_IO); + if (ret) { + ELOG("uv_tty_set_mode failed: %s", uv_strerror(ret)); + } } #endif tui_set_mode(ui, (ModeShape)mode_idx); @@ -1331,7 +1387,6 @@ static void tui_screenshot(UI *ui, String path) fclose(f); } - static void tui_option_set(UI *ui, String name, Object value) { TUIData *data = ui->data; @@ -1340,11 +1395,9 @@ static void tui_option_set(UI *ui, String name, Object value) data->print_attr_id = -1; invalidate(ui, 0, data->grid.height, 0, data->grid.width); - } - if (strequal(name.data, "ttimeout")) { + } else if (strequal(name.data, "ttimeout")) { data->input.ttimeout = value.data.boolean; - } - if (strequal(name.data, "ttimeoutlen")) { + } else if (strequal(name.data, "ttimeoutlen")) { data->input.ttimeoutlen = (long)value.data.integer; } } @@ -1428,8 +1481,8 @@ static void tui_guess_size(UI *ui) // 1 - look for non-default 'columns' and 'lines' options during startup if (data->is_starting && (Columns != DFLT_COLS || Rows != DFLT_ROWS)) { did_user_set_dimensions = true; - assert(Columns >= INT_MIN && Columns <= INT_MAX); - assert(Rows >= INT_MIN && Rows <= INT_MAX); + assert(Columns >= 0); + assert(Rows >= 0); width = Columns; height = Rows; goto end; @@ -1855,7 +1908,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=%?" @@ -1897,6 +1950,7 @@ static void augment_terminfo(TUIData *data, const char *term, long vte_version, || terminfo_is_term_family(term, "iTerm.app") || terminfo_is_term_family(term, "iTerm2.app"); bool alacritty = terminfo_is_term_family(term, "alacritty"); + bool kitty = terminfo_is_term_family(term, "xterm-kitty"); // None of the following work over SSH; see :help TERM . bool iterm_pretending_xterm = xterm && iterm_env; @@ -2020,6 +2074,15 @@ static void augment_terminfo(TUIData *data, const char *term, long vte_version, data->unibi_ext.set_underline_color = (int)unibi_add_ext_str(ut, "ext.set_underline_color", "\x1b[58:2::%p1%d:%p2%d:%p3%dm"); } + + if (!kitty) { + // Kitty does not support these sequences; it only supports it's own CSI > 1 u which enables the + // Kitty keyboard protocol + data->unibi_ext.enable_extended_keys = (int)unibi_add_ext_str(ut, "ext.enable_extended_keys", + "\x1b[>4;2m"); + data->unibi_ext.disable_extended_keys = (int)unibi_add_ext_str(ut, "ext.disable_extended_keys", + "\x1b[>4;0m"); + } } static void flush_buf(UI *ui) @@ -2081,8 +2144,11 @@ static void flush_buf(UI *ui) fwrite(bufs[i].base, bufs[i].len, 1, data->screenshot); } } else { - uv_write(&req, STRUCT_CAST(uv_stream_t, &data->output_handle), - bufs, (unsigned)(bufp - bufs), NULL); + int ret = uv_write(&req, STRUCT_CAST(uv_stream_t, &data->output_handle), + bufs, (unsigned)(bufp - bufs), NULL); + if (ret) { + ELOG("uv_write failed: %s", uv_strerror(ret)); + } uv_run(&data->write_loop, UV_RUN_DEFAULT); } data->bufpos = 0; 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 aad72af025..4fe3e1157c 100644 --- a/src/nvim/ui.c +++ b/src/nvim/ui.c @@ -8,10 +8,11 @@ #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" +#include "nvim/msgpack_rpc/channel.h" #include "nvim/diff.h" #include "nvim/event/loop.h" #include "nvim/ex_cmds2.h" @@ -23,7 +24,6 @@ #include "nvim/main.h" #include "nvim/mbyte.h" #include "nvim/memory.h" -#include "nvim/misc1.h" #include "nvim/move.h" #include "nvim/normal.h" #include "nvim/option.h" @@ -223,10 +223,23 @@ void ui_refresh(void) ui_default_colors_set(); - int save_p_lz = p_lz; - p_lz = false; // convince redrawing() to return true ... - screen_resize(width, height); - p_lz = save_p_lz; + if (!ui_client_channel_id) { + int save_p_lz = p_lz; + p_lz = false; // convince redrawing() to return true ... + screen_resize(width, height); + p_lz = save_p_lz; + } else { + Array args = ARRAY_DICT_INIT; + Error err = ERROR_INIT; + ADD(args, INTEGER_OBJ((int)width)); + ADD(args, INTEGER_OBJ((int)height)); + rpc_send_call(ui_client_channel_id, "nvim_ui_try_resize", args, &err); + + if (ERROR_SET(&err)) { + ELOG("ui_client resize: %s", err.msg); + } + api_clear_error(&err); + } if (ext_widgets[kUIMessages]) { p_ch = 0; @@ -300,6 +313,44 @@ void ui_busy_stop(void) } } +/// Emit a bell or visualbell as a warning +/// +/// val is one of the BO_ values, e.g., BO_OPER +void vim_beep(unsigned val) +{ + called_vim_beep = true; + + if (emsg_silent == 0) { + if (!((bo_flags & val) || (bo_flags & BO_ALL))) { + static int beeps = 0; + static uint64_t start_time = 0; + + // Only beep up to three times per half a second, + // otherwise a sequence of beeps would freeze Vim. + if (start_time == 0 || os_hrtime() - start_time > 500000000u) { + beeps = 0; + start_time = os_hrtime(); + } + beeps++; + if (beeps <= 3) { + if (p_vb) { + ui_call_visual_bell(); + } else { + ui_call_bell(); + } + } + } + + // When 'debug' contains "beep" produce a message. If we are sourcing + // a script or executing a function give the user a hint where the beep + // comes from. + if (vim_strchr(p_debug, 'e') != NULL) { + msg_source(HL_ATTR(HLF_W)); + msg_attr(_("Beep!"), HL_ATTR(HLF_W)); + } + } +} + void ui_attach_impl(UI *ui, uint64_t chanid) { if (ui_count == MAX_UI_COUNT) { @@ -602,8 +653,8 @@ void ui_grid_resize(handle_T grid_handle, int width, int height, Error *error) } } else { // non-positive indicates no request - wp->w_height_request = (int)MAX(height, 0); - wp->w_width_request = (int)MAX(width, 0); + wp->w_height_request = MAX(height, 0); + wp->w_width_request = MAX(width, 0); win_set_inner_size(wp); } } diff --git a/src/nvim/ui_client.c b/src/nvim/ui_client.c new file mode 100644 index 0000000000..6a7695bf72 --- /dev/null +++ b/src/nvim/ui_client.c @@ -0,0 +1,214 @@ +// 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 <stdint.h> +#include <assert.h> + +#include "nvim/vim.h" +#include "nvim/log.h" +#include "nvim/map.h" +#include "nvim/ui_client.h" +#include "nvim/api/private/helpers.h" +#include "nvim/msgpack_rpc/channel.h" +#include "nvim/api/private/dispatch.h" +#include "nvim/ui.h" +#include "nvim/highlight.h" +#include "nvim/screen.h" + +static Map(String, UIClientHandler) ui_client_handlers = MAP_INIT; + +// Temporary buffer for converting a single grid_line event +static size_t buf_size = 0; +static schar_T *buf_char = NULL; +static sattr_T *buf_attr = NULL; + +static void add_ui_client_event_handler(String method, UIClientHandler handler) +{ + map_put(String, UIClientHandler)(&ui_client_handlers, method, handler); +} + +void ui_client_init(uint64_t chan) +{ + Array args = ARRAY_DICT_INIT; + int width = Columns; + int height = Rows; + Dictionary opts = ARRAY_DICT_INIT; + + PUT(opts, "rgb", BOOLEAN_OBJ(true)); + PUT(opts, "ext_linegrid", BOOLEAN_OBJ(true)); + PUT(opts, "ext_termcolors", BOOLEAN_OBJ(true)); + + ADD(args, INTEGER_OBJ((int)width)); + ADD(args, INTEGER_OBJ((int)height)); + ADD(args, DICTIONARY_OBJ(opts)); + + rpc_send_event(chan, "nvim_ui_attach", args); + msgpack_rpc_add_redraw(); // GAME! + // TODO(bfredl): use a keyset instead + ui_client_methods_table_init(); + ui_client_channel_id = chan; +} + +/// Handler for "redraw" events sent by the NVIM server +/// +/// This function will be called by handle_request (in msgpack_rpc/channel.c) +/// The individual ui_events sent by the server are individually handled +/// by their respective handlers defined in ui_events_client.generated.h +/// +/// @note The "flush" event is called only once and only after handling all +/// the other events +/// @param channel_id: The id of the rpc channel +/// @param uidata: The dense array containing the ui_events sent by the server +/// @param[out] err Error details, if any +Object ui_client_handle_redraw(uint64_t channel_id, Array args, Error *error) +{ + for (size_t i = 0; i < args.size; i++) { + Array call = args.items[i].data.array; + String name = call.items[0].data.string; + + UIClientHandler handler = map_get(String, UIClientHandler)(&ui_client_handlers, name); + if (!handler) { + ELOG("No ui client handler for %s", name.size ? name.data : "<empty>"); + continue; + } + + // fprintf(stderr, "%s: %zu\n", name.data, call.size-1); + DLOG("Invoke ui client handler for %s", name.data); + for (size_t j = 1; j < call.size; j++) { + handler(call.items[j].data.array); + } + } + + return NIL; +} + +/// run the main thread in ui client mode +/// +/// This is just a stub. the full version will handle input, resizing, etc +void ui_client_execute(uint64_t chan) + FUNC_ATTR_NORETURN +{ + while (true) { + loop_poll_events(&main_loop, -1); + multiqueue_process_events(resize_events); + } + + getout(0); +} + +static HlAttrs ui_client_dict2hlattrs(Dictionary d, bool rgb) +{ + Error err = ERROR_INIT; + Dict(highlight) dict = { 0 }; + if (!api_dict_to_keydict(&dict, KeyDict_highlight_get_field, d, &err)) { + // TODO(bfredl): log "err" + return HLATTRS_INIT; + } + return dict2hlattrs(&dict, true, NULL, &err); +} + +#ifdef INCLUDE_GENERATED_DECLARATIONS +#include "ui_events_client.generated.h" +#endif + +void ui_client_event_grid_resize(Array args) +{ + if (args.size < 3 + || args.items[0].type != kObjectTypeInteger + || args.items[1].type != kObjectTypeInteger + || args.items[2].type != kObjectTypeInteger) { + ELOG("Error handling ui event 'grid_resize'"); + return; + } + + Integer grid = args.items[0].data.integer; + Integer width = args.items[1].data.integer; + Integer height = args.items[2].data.integer; + ui_call_grid_resize(grid, width, height); + + if (buf_size < (size_t)width) { + xfree(buf_char); + xfree(buf_attr); + buf_size = (size_t)width; + buf_char = xmalloc(buf_size * sizeof(schar_T)); + buf_attr = xmalloc(buf_size * sizeof(sattr_T)); + } +} + +void ui_client_event_grid_line(Array args) +{ + if (args.size < 4 + || args.items[0].type != kObjectTypeInteger + || args.items[1].type != kObjectTypeInteger + || args.items[2].type != kObjectTypeInteger + || args.items[3].type != kObjectTypeArray) { + goto error; + } + + Integer grid = args.items[0].data.integer; + Integer row = args.items[1].data.integer; + Integer startcol = args.items[2].data.integer; + Array cells = args.items[3].data.array; + + // TODO(hlpr98): Accommodate other LineFlags when included in grid_line + LineFlags lineflags = 0; + + size_t j = 0; + int cur_attr = 0; + int clear_attr = 0; + int clear_width = 0; + for (size_t i = 0; i < cells.size; i++) { + if (cells.items[i].type != kObjectTypeArray) { + goto error; + } + Array cell = cells.items[i].data.array; + + if (cell.size < 1 || cell.items[0].type != kObjectTypeString) { + goto error; + } + String sstring = cell.items[0].data.string; + + char *schar = sstring.data; + int repeat = 1; + if (cell.size >= 2) { + if (cell.items[1].type != kObjectTypeInteger + || cell.items[1].data.integer < 0) { + goto error; + } + cur_attr = (int)cell.items[1].data.integer; + } + + if (cell.size >= 3) { + if (cell.items[2].type != kObjectTypeInteger + || cell.items[2].data.integer < 0) { + goto error; + } + repeat = (int)cell.items[2].data.integer; + } + + if (i == cells.size - 1 && sstring.size == 1 && sstring.data[0] == ' ' && repeat > 1) { + clear_width = repeat; + break; + } + + for (int r = 0; r < repeat; r++) { + if (j >= buf_size) { + goto error; // _YIKES_ + } + STRLCPY(buf_char[j], schar, sizeof(schar_T)); + buf_attr[j++] = cur_attr; + } + } + + Integer endcol = startcol + (int)j; + Integer clearcol = endcol + clear_width; + clear_attr = cur_attr; + + ui_call_raw_line(grid, row, startcol, endcol, clearcol, clear_attr, lineflags, + (const schar_T *)buf_char, (const sattr_T *)buf_attr); + return; + +error: + ELOG("Error handling ui event 'grid_line'"); +} diff --git a/src/nvim/ui_client.h b/src/nvim/ui_client.h new file mode 100644 index 0000000000..253deecc52 --- /dev/null +++ b/src/nvim/ui_client.h @@ -0,0 +1,13 @@ +#ifndef NVIM_UI_CLIENT_H +#define NVIM_UI_CLIENT_H + +#include "nvim/api/private/defs.h" + +typedef void (*UIClientHandler)(Array args); + +#ifdef INCLUDE_GENERATED_DECLARATIONS +#include "ui_client.h.generated.h" +#include "ui_events_client.h.generated.h" +#endif + +#endif // NVIM_UI_CLIENT_H diff --git a/src/nvim/ui_compositor.c b/src/nvim/ui_compositor.c index d7becb4fd4..e356960cc8 100644 --- a/src/nvim/ui_compositor.c +++ b/src/nvim/ui_compositor.c @@ -14,6 +14,7 @@ #include "nvim/api/private/helpers.h" #include "nvim/ascii.h" #include "nvim/highlight.h" +#include "nvim/highlight_group.h" #include "nvim/lib/kvec.h" #include "nvim/log.h" #include "nvim/lua/executor.h" @@ -23,7 +24,6 @@ #include "nvim/os/os.h" #include "nvim/popupmnu.h" #include "nvim/screen.h" -#include "nvim/syntax.h" #include "nvim/ugrid.h" #include "nvim/ui.h" #include "nvim/ui_compositor.h" diff --git a/src/nvim/undo.c b/src/nvim/undo.c index 8161fce9f4..2d8df4cad8 100644 --- a/src/nvim/undo.c +++ b/src/nvim/undo.c @@ -93,13 +93,14 @@ #include "nvim/fileio.h" #include "nvim/fold.h" #include "nvim/garray.h" +#include "nvim/getchar.h" #include "nvim/lib/kvec.h" #include "nvim/mark.h" #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/message.h" -#include "nvim/misc1.h" #include "nvim/option.h" +#include "nvim/os/input.h" #include "nvim/os/os.h" #include "nvim/os/time.h" #include "nvim/os_unix.h" @@ -2621,7 +2622,7 @@ static void u_undo_end(bool did_undo, bool absolute, bool quiet) if (uhp == NULL) { *msgbuf = NUL; } else { - add_time(msgbuf, sizeof(msgbuf), uhp->uh_time); + undo_fmt_time(msgbuf, sizeof(msgbuf), uhp->uh_time); } { @@ -2632,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, @@ -2641,6 +2646,29 @@ static void u_undo_end(bool did_undo, bool absolute, bool quiet) msgbuf); } +/// Put the timestamp of an undo header in "buf[buflen]" in a nice format. +void undo_fmt_time(char_u *buf, size_t buflen, time_t tt) +{ + struct tm curtime; + + if (time(NULL) - tt >= 100) { + os_localtime_r(&tt, &curtime); + if (time(NULL) - tt < (60L * 60L * 12L)) { + // within 12 hours + (void)strftime((char *)buf, buflen, "%H:%M:%S", &curtime); + } else { + // longer ago + (void)strftime((char *)buf, buflen, "%Y/%m/%d %H:%M:%S", &curtime); + } + } else { + int64_t seconds = time(NULL) - tt; + vim_snprintf((char *)buf, buflen, + NGETTEXT("%" PRId64 " second ago", + "%" PRId64 " seconds ago", (uint32_t)seconds), + seconds); + } +} + /// u_sync: stop adding to the current entry list /// /// @param force if true, also sync when no_u_sync is set. @@ -2683,16 +2711,13 @@ void ex_undolist(exarg_T *eap) while (uhp != NULL) { if (uhp->uh_prev.ptr == NULL && uhp->uh_walk != nomark && uhp->uh_walk != mark) { - vim_snprintf((char *)IObuff, IOSIZE, "%6ld %7d ", - uhp->uh_seq, changes); - add_time(IObuff + STRLEN(IObuff), IOSIZE - STRLEN(IObuff), - uhp->uh_time); + vim_snprintf((char *)IObuff, IOSIZE, "%6ld %7d ", uhp->uh_seq, changes); + undo_fmt_time(IObuff + STRLEN(IObuff), IOSIZE - STRLEN(IObuff), uhp->uh_time); if (uhp->uh_save_nr > 0) { while (STRLEN(IObuff) < 33) { STRCAT(IObuff, " "); } - vim_snprintf_add((char *)IObuff, IOSIZE, - " %3ld", uhp->uh_save_nr); + vim_snprintf_add((char *)IObuff, IOSIZE, " %3ld", uhp->uh_save_nr); } GA_APPEND(char_u *, &ga, vim_strsave(IObuff)); } diff --git a/src/nvim/undo_defs.h b/src/nvim/undo_defs.h index 3267b2f71e..d8470b07b1 100644 --- a/src/nvim/undo_defs.h +++ b/src/nvim/undo_defs.h @@ -33,8 +33,8 @@ struct u_entry { }; struct u_header { - /* The following have a pointer and a number. The number is used when - * reading the undo file in u_read_undo() */ + // The following have a pointer and a number. The number is used when reading + // the undo file in u_read_undo() union { u_header_T *ptr; // pointer to next undo header in list long seq; @@ -80,4 +80,4 @@ typedef struct { FILE *bi_fp; } bufinfo_T; -#endif // NVIM_UNDO_DEFS_H +#endif // NVIM_UNDO_DEFS_H 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 726670f082..64333e9c3d 100644 --- a/src/nvim/vim.h +++ b/src/nvim/vim.h @@ -141,6 +141,7 @@ enum { EXPAND_COMPILER, EXPAND_USER_DEFINED, EXPAND_USER_LIST, + EXPAND_USER_LUA, EXPAND_SHELLCMD, EXPAND_CSCOPE, EXPAND_SIGN, @@ -213,6 +214,11 @@ enum { FOLD_TEXT_LEN = 51, }; //!< buffer size for get_foldtext() // (vim_strchr() is now in strings.c) #define STRLEN(s) strlen((char *)(s)) +#ifdef HAVE_STRNLEN +# define STRNLEN(s, n) strnlen((char *)(s), (size_t)(n)) +#else +# define STRNLEN(s, n) xstrnlen((char *)(s), (size_t)(n)) +#endif #define STRCPY(d, s) strcpy((char *)(d), (char *)(s)) #define STRNCPY(d, s, n) strncpy((char *)(d), (char *)(s), (size_t)(n)) #define STRLCPY(d, s, n) xstrlcpy((char *)(d), (char *)(s), (size_t)(n)) @@ -256,7 +262,7 @@ enum { FOLD_TEXT_LEN = 51, }; //!< buffer size for get_foldtext() // Prefer using semsg(), because perror() may send the output to the wrong // destination and mess up the screen. -#define PERROR(msg) (void)semsg("%s: %s", msg, strerror(errno)) +#define PERROR(msg) (void)semsg("%s: %s", (msg), strerror(errno)) #define SHOWCMD_COLS 10 // columns needed by shown command @@ -311,7 +317,7 @@ enum { FOLD_TEXT_LEN = 51, }; //!< buffer size for get_foldtext() #endif // Replacement for nchar used by nv_replace(). -#define REPLACE_CR_NCHAR -1 -#define REPLACE_NL_NCHAR -2 +#define REPLACE_CR_NCHAR (-1) +#define REPLACE_NL_NCHAR (-2) #endif // NVIM_VIM_H diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c index 800ecf10db..9d1318724e 100644 --- a/src/nvim/viml/parser/expressions.c +++ b/src/nvim/viml/parser/expressions.c @@ -204,40 +204,40 @@ LexExprToken viml_pexpr_next_token(ParserState *const pstate, const int flags) } \ } while (0) switch (schar) { - // Paired brackets. + // Paired brackets. #define BRACKET(typ, opning, clsing) \ -case opning: \ -case clsing: { \ - ret.type = typ; \ - ret.data.brc.closing = (schar == clsing); \ - break; \ -} - BRACKET(kExprLexParenthesis, '(', ')') - BRACKET(kExprLexBracket, '[', ']') - BRACKET(kExprLexFigureBrace, '{', '}') + case opning: \ + case clsing: { \ + ret.type = typ; \ + ret.data.brc.closing = (schar == clsing); \ + break; \ + } + BRACKET(kExprLexParenthesis, '(', ')') + BRACKET(kExprLexBracket, '[', ']') + BRACKET(kExprLexFigureBrace, '{', '}') #undef BRACKET - // Single character tokens without data. + // Single character tokens without data. #define CHAR(typ, ch) \ -case ch: { \ - ret.type = typ; \ - break; \ -} - CHAR(kExprLexQuestion, '?') - CHAR(kExprLexColon, ':') - CHAR(kExprLexComma, ',') + case ch: { \ + ret.type = typ; \ + break; \ + } + CHAR(kExprLexQuestion, '?') + CHAR(kExprLexColon, ':') + CHAR(kExprLexComma, ',') #undef CHAR - // Multiplication/division/modulo. + // Multiplication/division/modulo. #define MUL(mul_type, ch) \ -case ch: { \ - ret.type = kExprLexMultiplication; \ - ret.data.mul.type = mul_type; \ - break; \ -} - MUL(kExprLexMulMul, '*') - MUL(kExprLexMulDiv, '/') - MUL(kExprLexMulMod, '%') + case ch: { \ + ret.type = kExprLexMultiplication; \ + ret.data.mul.type = mul_type; \ + break; \ + } + MUL(kExprLexMulMul, '*') + MUL(kExprLexMulDiv, '/') + MUL(kExprLexMulMod, '%') #undef MUL #define CHARREG(typ, cond) \ @@ -653,16 +653,16 @@ case ch: { \ // Sign or augmented assignment. #define CHAR_OR_ASSIGN(ch, ch_type, ass_type) \ -case ch: { \ - if (pline.size > 1 && pline.data[1] == '=') { \ - ret.len++; \ - ret.type = kExprLexAssignment; \ - ret.data.ass.type = ass_type; \ - } else { \ - ret.type = ch_type; \ - } \ - break; \ -} + case ch: { \ + if (pline.size > 1 && pline.data[1] == '=') { \ + ret.len++; \ + ret.type = kExprLexAssignment; \ + ret.data.ass.type = ass_type; \ + } else { \ + ret.type = ch_type; \ + } \ + break; \ + } CHAR_OR_ASSIGN('+', kExprLexPlus, kExprAsgnAdd) CHAR_OR_ASSIGN('.', kExprLexDot, kExprAsgnConcat) #undef CHAR_OR_ASSIGN @@ -811,19 +811,19 @@ const char *viml_pexpr_repr_token(const ParserState *const pstate, const LexExpr eltkn_type_tab[token.type]); switch (token.type) { #define TKNARGS(tkn_type, ...) \ -case tkn_type: { \ - ADDSTR(__VA_ARGS__); \ - break; \ -} - TKNARGS(kExprLexComparison, "(type=%s,ccs=%s,inv=%i)", - eltkn_cmp_type_tab[token.data.cmp.type], - ccs_tab[token.data.cmp.ccs], - (int)token.data.cmp.inv) - TKNARGS(kExprLexMultiplication, "(type=%s)", - eltkn_mul_type_tab[token.data.mul.type]) - TKNARGS(kExprLexAssignment, "(type=%s)", - expr_asgn_type_tab[token.data.ass.type]) - TKNARGS(kExprLexRegister, "(name=%s)", intchar2str(token.data.reg.name)) + case tkn_type: { \ + ADDSTR(__VA_ARGS__); \ + break; \ + } + TKNARGS(kExprLexComparison, "(type=%s,ccs=%s,inv=%i)", + eltkn_cmp_type_tab[token.data.cmp.type], + ccs_tab[token.data.cmp.ccs], + (int)token.data.cmp.inv) + TKNARGS(kExprLexMultiplication, "(type=%s)", + eltkn_mul_type_tab[token.data.mul.type]) + TKNARGS(kExprLexAssignment, "(type=%s)", + expr_asgn_type_tab[token.data.ass.type]) + TKNARGS(kExprLexRegister, "(name=%s)", intchar2str(token.data.reg.name)) case kExprLexDoubleQuotedString: TKNARGS(kExprLexSingleQuotedString, "(closed=%i)", (int)token.data.str.closed) @@ -1536,25 +1536,25 @@ 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: { \ - NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeComplexIdentifier); \ - cur_node->len = 0; \ - cur_node->children = *top_node_p; \ - *top_node_p = cur_node; \ - kvi_push(ast_stack, &cur_node->children->next); \ - ExprASTNode **const new_top_node_p = kv_last(ast_stack); \ - assert(*new_top_node_p == NULL); \ - new_ident_node_code; \ - *new_top_node_p = cur_node; \ - HL_CUR_TOKEN(hl); \ - break; \ + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeComplexIdentifier); \ + cur_node->len = 0; \ + cur_node->children = *top_node_p; \ + *top_node_p = cur_node; \ + kvi_push(ast_stack, &cur_node->children->next); \ + ExprASTNode **const new_top_node_p = kv_last(ast_stack); \ + assert(*new_top_node_p == NULL); \ + new_ident_node_code; \ + *new_top_node_p = cur_node; \ + HL_CUR_TOKEN(hl); \ + break; \ } \ default: { \ - OP_MISSING; \ - break; \ + OP_MISSING; \ + break; \ } \ } \ } while (0) @@ -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 { @@ -1747,19 +1747,19 @@ static void parse_quoted_string(ParserState *const pstate, ExprASTNode *const no const char *const v_p_start = v_p; switch (*p) { #define SINGLE_CHAR_ESC(ch, real_ch) \ -case ch: { \ - *v_p++ = real_ch; \ - p++; \ - break; \ -} - SINGLE_CHAR_ESC('b', BS) - SINGLE_CHAR_ESC('e', ESC) - SINGLE_CHAR_ESC('f', FF) - SINGLE_CHAR_ESC('n', NL) - SINGLE_CHAR_ESC('r', CAR) - SINGLE_CHAR_ESC('t', TAB) - SINGLE_CHAR_ESC('"', '"') - SINGLE_CHAR_ESC('\\', '\\') + case ch: { \ + *v_p++ = real_ch; \ + p++; \ + break; \ + } + SINGLE_CHAR_ESC('b', BS) + SINGLE_CHAR_ESC('e', ESC) + SINGLE_CHAR_ESC('f', FF) + SINGLE_CHAR_ESC('n', NL) + SINGLE_CHAR_ESC('r', CAR) + SINGLE_CHAR_ESC('t', TAB) + SINGLE_CHAR_ESC('"', '"') + SINGLE_CHAR_ESC('\\', '\\') #undef SINGLE_CHAR_ESC // Hexadecimal or unicode. @@ -2141,32 +2141,32 @@ viml_pexpr_parse_process_token: break; } #define SIMPLE_UB_OP(op) \ -case kExprLex##op: { \ - if (want_node == kENodeValue) { \ - /* Value level: assume unary operator. */ \ - NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeUnary##op); \ - *top_node_p = cur_node; \ - kvi_push(ast_stack, &cur_node->children); \ - HL_CUR_TOKEN(Unary##op); \ - } else { \ - NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeBinary##op); \ - ADD_OP_NODE(cur_node); \ - HL_CUR_TOKEN(Binary##op); \ - } \ - want_node = kENodeValue; \ - break; \ -} + case kExprLex##op: { \ + if (want_node == kENodeValue) { \ + /* Value level: assume unary operator. */ \ + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeUnary##op); \ + *top_node_p = cur_node; \ + kvi_push(ast_stack, &cur_node->children); \ + HL_CUR_TOKEN(Unary##op); \ + } else { \ + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeBinary##op); \ + ADD_OP_NODE(cur_node); \ + HL_CUR_TOKEN(Binary##op); \ + } \ + want_node = kENodeValue; \ + break; \ + } SIMPLE_UB_OP(Plus) SIMPLE_UB_OP(Minus) #undef SIMPLE_UB_OP #define SIMPLE_B_OP(op, msg) \ -case kExprLex##op: { \ - ADD_VALUE_IF_MISSING(_("E15: Unexpected " msg ": %.*s")); \ - NEW_NODE_WITH_CUR_POS(cur_node, kExprNode##op); \ - HL_CUR_TOKEN(op); \ - ADD_OP_NODE(cur_node); \ - break; \ -} + case kExprLex##op: { \ + ADD_VALUE_IF_MISSING(_("E15: Unexpected " msg ": %.*s")); \ + NEW_NODE_WITH_CUR_POS(cur_node, kExprNode##op); \ + HL_CUR_TOKEN(op); \ + ADD_OP_NODE(cur_node); \ + break; \ + } SIMPLE_B_OP(Or, "or operator") SIMPLE_B_OP(And, "and operator") #undef SIMPLE_B_OP @@ -2174,14 +2174,14 @@ case kExprLex##op: { \ ADD_VALUE_IF_MISSING(_("E15: Unexpected multiplication-like operator: %.*s")); switch (cur_token.data.mul.type) { #define MUL_OP(lex_op_tail, node_op_tail) \ -case kExprLexMul##lex_op_tail: { \ - NEW_NODE_WITH_CUR_POS(cur_node, kExprNode##node_op_tail); \ - HL_CUR_TOKEN(node_op_tail); \ - break; \ -} - MUL_OP(Mul, Multiplication) - MUL_OP(Div, Division) - MUL_OP(Mod, Mod) + case kExprLexMul##lex_op_tail: { \ + NEW_NODE_WITH_CUR_POS(cur_node, kExprNode##node_op_tail); \ + HL_CUR_TOKEN(node_op_tail); \ + break; \ + } + MUL_OP(Mul, Multiplication) + MUL_OP(Div, Division) + MUL_OP(Mod, Mod) #undef MUL_OP } ADD_OP_NODE(cur_node); @@ -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; } @@ -2929,11 +2929,11 @@ viml_pexpr_parse_no_paren_closing_error: {} cur_node->data.ass.type = cur_token.data.ass.type; switch (cur_token.data.ass.type) { #define HL_ASGN(asgn, hl) \ -case kExprAsgn##asgn: { HL_CUR_TOKEN(hl); break; } - HL_ASGN(Plain, PlainAssignment) - HL_ASGN(Add, AssignmentWithAddition) - HL_ASGN(Subtract, AssignmentWithSubtraction) - HL_ASGN(Concat, AssignmentWithConcatenation) + case kExprAsgn##asgn: { HL_CUR_TOKEN(hl); break; } + HL_ASGN(Plain, PlainAssignment) + HL_ASGN(Add, AssignmentWithAddition) + HL_ASGN(Subtract, AssignmentWithSubtraction) + HL_ASGN(Concat, AssignmentWithConcatenation) #undef HL_ASGN } ADD_OP_NODE(cur_node); 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/viml/parser/parser.h b/src/nvim/viml/parser/parser.h index b2933c3781..b8835127e7 100644 --- a/src/nvim/viml/parser/parser.h +++ b/src/nvim/viml/parser/parser.h @@ -81,10 +81,8 @@ typedef struct { bool can_continuate; } ParserState; -static inline void viml_parser_init( - ParserState *const ret_pstate, - const ParserLineGetter get_line, void *const cookie, - ParserHighlight *const colors) +static inline void viml_parser_init(ParserState *const ret_pstate, const ParserLineGetter get_line, + void *const cookie, ParserHighlight *const colors) REAL_FATTR_ALWAYS_INLINE REAL_FATTR_NONNULL_ARG(1, 2); /// Initialize a new parser state instance diff --git a/src/nvim/window.c b/src/nvim/window.c index 9c13264684..2ca5128445 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -27,10 +27,10 @@ #include "nvim/hashtab.h" #include "nvim/main.h" #include "nvim/mark.h" +#include "nvim/match.h" #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/message.h" -#include "nvim/misc1.h" #include "nvim/mouse.h" #include "nvim/move.h" #include "nvim/normal.h" @@ -59,9 +59,9 @@ #endif -#define NOWIN (win_T *)-1 // non-existing window +#define NOWIN ((win_T *)-1) // non-existing window -#define ROWS_AVAIL (Rows - p_ch - tabline_height()) +#define ROWS_AVAIL (Rows - p_ch - tabline_height() - global_stl_height()) /// flags for win_enter_ext() typedef enum { @@ -74,6 +74,15 @@ typedef enum { static char *m_onlyone = N_("Already only one window"); +/// @return the current window, unless in the cmdline window and "prevwin" is +/// set, then return "prevwin". +win_T *prevwin_curwin(void) + FUNC_ATTR_WARN_UNUSED_RESULT +{ + // In cmdwin, the alternative buffer should be used. + return is_in_cmdwin() && prevwin != NULL ? prevwin : curwin; +} + /// all CTRL-W window commands are handled here, called from normal_cmd(). /// /// @param xchar extra char from ":wincmd gx" or NUL @@ -291,7 +300,7 @@ newwindow: // move window to new tab page case 'T': - if (one_window()) { + if (one_window(curwin)) { msg(_(m_onlyone)); } else { tabpage_T *oldtab = curtab; @@ -305,7 +314,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); @@ -450,7 +459,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; @@ -522,10 +531,6 @@ wingotofile: do_nv_ident('g', xchar); break; - case TAB: - goto_tabpage_lastused(); - break; - case 'f': // CTRL-W gf: "gf" in a new tab page case 'F': // CTRL-W gF: "gF" in a new tab page cmdmod.tab = tabpage_index(curtab) + 1; @@ -539,6 +544,12 @@ wingotofile: goto_tabpage(-(int)Prenum1); break; + case TAB: // CTRL-W g<Tab>: go to last used tab page + if (!goto_tabpage_lastused()) { + beep_flush(); + } + break; + case 'e': if (curwin->w_floating || !ui_has(kUIMultigrid)) { beep_flush(); @@ -549,7 +560,7 @@ wingotofile: config.height = curwin->w_height; config.external = true; Error err = ERROR_INIT; - if (!win_new_float(curwin, config, &err)) { + if (!win_new_float(curwin, false, config, &err)) { emsg(err.msg); api_clear_error(&err); beep_flush(); @@ -578,18 +589,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", @@ -609,7 +621,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(); } @@ -617,16 +629,18 @@ void win_set_buf(Window window, Buffer buffer, bool noautocmd, Error *err) /// Create a new float. /// -/// if wp == NULL allocate a new window, otherwise turn existing window into a -/// float. It must then already belong to the current tabpage! -/// -/// config must already have been validated! -win_T *win_new_float(win_T *wp, FloatConfig fconfig, Error *err) +/// @param wp if NULL, allocate a new window, otherwise turn existing window into a float. +/// It must then already belong to the current tabpage! +/// @param last make the window the last one in the window list. +/// Only used when allocating the autocommand window. +/// @param config must already have been validated! +win_T *win_new_float(win_T *wp, bool last, FloatConfig fconfig, Error *err) { if (wp == NULL) { - wp = win_alloc(lastwin_nofloating(), false); + wp = win_alloc(last ? lastwin : lastwin_nofloating(), false); win_init(wp, curwin, 0); } else { + assert(!last); assert(!wp->w_floating); if (firstwin == wp && lastwin_nofloating() == wp) { // last non-float @@ -647,6 +661,7 @@ win_T *win_new_float(win_T *wp, FloatConfig fconfig, Error *err) } wp->w_floating = 1; wp->w_status_height = 0; + wp->w_hsep_height = 0; wp->w_vsep_width = 0; win_config_float(wp, fconfig); @@ -821,7 +836,7 @@ void ui_ext_win_position(win_T *wp) FloatConfig c = wp->w_float_config; if (!c.external) { ScreenGrid *grid = &default_grid; - float row = c.row, col = c.col; + Float row = c.row, col = c.col; if (c.relative == kFloatRelativeWindow) { Error dummy = ERROR_INIT; win_T *win = find_window_by_handle(c.window, &dummy); @@ -958,6 +973,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) { @@ -1151,8 +1171,7 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir) while (frp != NULL) { if (frp->fr_win != oldwin && frp->fr_win != NULL && (frp->fr_win->w_height > new_size - || frp->fr_win->w_height > oldwin_height - new_size - - STATUS_HEIGHT)) { + || frp->fr_win->w_height > oldwin_height - new_size - STATUS_HEIGHT)) { do_equal = true; break; } @@ -1278,13 +1297,15 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir) if (flags & (WSP_TOP | WSP_BOT)) { // set height and row of new window to full height wp->w_winrow = tabline_height(); - win_new_height(wp, curfrp->fr_height - (p_ls > 0)); - wp->w_status_height = (p_ls > 0); + win_new_height(wp, curfrp->fr_height - (p_ls == 1 || p_ls == 2)); + wp->w_status_height = (p_ls == 1 || p_ls == 2); + wp->w_hsep_height = 0; } else { // height and row of new window is same as current window wp->w_winrow = oldwin->w_winrow; win_new_height(wp, oldwin->w_height); wp->w_status_height = oldwin->w_status_height; + wp->w_hsep_height = oldwin->w_hsep_height; } frp->fr_height = curfrp->fr_height; @@ -1317,6 +1338,7 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir) frame_fix_width(oldwin); frame_fix_width(wp); } else { + bool is_stl_global = global_stl_height() > 0; // width and column of new window is same as current window if (flags & (WSP_TOP | WSP_BOT)) { wp->w_wincol = 0; @@ -1332,28 +1354,52 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir) // "new_size" of the current window goes to the new window, use // one row for the status line win_new_height(wp, new_size); + if (before) { + wp->w_hsep_height = is_stl_global ? 1 : 0; + } else { + wp->w_hsep_height = oldwin->w_hsep_height; + oldwin->w_hsep_height = is_stl_global ? 1 : 0; + } if (flags & (WSP_TOP | WSP_BOT)) { int new_fr_height = curfrp->fr_height - new_size; - if (!((flags & WSP_BOT) && p_ls == 0)) { + if (!((flags & WSP_BOT) && p_ls == 0) && global_stl_height() == 0) { new_fr_height -= STATUS_HEIGHT; + } else if (global_stl_height() > 0) { + if (flags & WSP_BOT) { + frame_add_hsep(curfrp); + } else { + new_fr_height -= 1; + } } frame_new_height(curfrp, new_fr_height, flags & WSP_TOP, false); } else { win_new_height(oldwin, oldwin_height - (new_size + STATUS_HEIGHT)); } + if (before) { // new window above current one wp->w_winrow = oldwin->w_winrow; - wp->w_status_height = STATUS_HEIGHT; - oldwin->w_winrow += wp->w_height + STATUS_HEIGHT; + + if (is_stl_global) { + wp->w_status_height = 0; + oldwin->w_winrow += wp->w_height + 1; + } else { + wp->w_status_height = STATUS_HEIGHT; + oldwin->w_winrow += wp->w_height + STATUS_HEIGHT; + } } else { // new window below current one - wp->w_winrow = oldwin->w_winrow + oldwin->w_height + STATUS_HEIGHT; - wp->w_status_height = oldwin->w_status_height; - if (!(flags & WSP_BOT)) { - oldwin->w_status_height = STATUS_HEIGHT; + if (is_stl_global) { + wp->w_winrow = oldwin->w_winrow + oldwin->w_height + 1; + wp->w_status_height = 0; + } else { + wp->w_winrow = oldwin->w_winrow + oldwin->w_height + STATUS_HEIGHT; + wp->w_status_height = oldwin->w_status_height; + if (!(flags & WSP_BOT)) { + oldwin->w_status_height = STATUS_HEIGHT; + } } } - if (flags & WSP_BOT) { + if ((flags & WSP_BOT) && global_stl_height() == 0) { frame_add_statusline(curfrp); } frame_fix_height(wp); @@ -1415,13 +1461,11 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir) p_wh = i; } - if (!win_valid(oldwin)) { - return FAIL; + if (win_valid(oldwin)) { + // Send the window positions to the UI + oldwin->w_pos_changed = true; } - // Send the window positions to the UI - oldwin->w_pos_changed = true; - return OK; } @@ -1482,8 +1526,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); } /* @@ -1592,14 +1634,14 @@ 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); } else { - // Each window needs at least 'winminheight' lines and a status line. - maxcount = (curwin->w_height - + curwin->w_status_height + // Each window needs at least 'winminheight' lines + // If statusline isn't global, each window also needs a statusline + maxcount = (curwin->w_height + curwin->w_hsep_height + curwin->w_status_height - (p_wh - p_wmh)) / (p_wmh + STATUS_HEIGHT); } @@ -1695,7 +1737,7 @@ static void win_exchange(long Prenum) * if wp != wp2 * 3. remove wp from the list * 4. insert wp after wp2 - * 5. exchange the status line height and vsep width. + * 5. exchange the status line height, hsep height and vsep width. */ wp2 = curwin->w_prev; frp2 = curwin->w_frame->fr_prev; @@ -1721,6 +1763,9 @@ static void win_exchange(long Prenum) temp = curwin->w_vsep_width; curwin->w_vsep_width = wp->w_vsep_width; wp->w_vsep_width = temp; + temp = curwin->w_hsep_height; + curwin->w_hsep_height = wp->w_hsep_height; + wp->w_hsep_height = temp; frame_fix_height(curwin); frame_fix_height(wp); @@ -1729,6 +1774,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); @@ -1795,10 +1846,13 @@ static void win_rotate(bool upwards, int count) frame_insert(frp->fr_parent->fr_child, frp); } - // exchange status height and vsep width of old and new last window + // exchange status height, hsep height and vsep width of old and new last window n = wp2->w_status_height; wp2->w_status_height = wp1->w_status_height; wp1->w_status_height = n; + n = wp2->w_hsep_height; + wp2->w_hsep_height = wp1->w_hsep_height; + wp1->w_hsep_height = n; frame_fix_height(wp1); frame_fix_height(wp2); n = wp2->w_vsep_width; @@ -1829,6 +1883,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); @@ -1872,11 +1929,16 @@ void win_move_after(win_T *win1, win_T *win2) // check if there is something to do if (win2->w_next != win1) { - // may need move the status line/vertical separator of the last window + // may need move the status line, horizontal or vertical separator of the last window if (win1 == lastwin) { height = win1->w_prev->w_status_height; win1->w_prev->w_status_height = win1->w_status_height; win1->w_status_height = height; + + height = win1->w_prev->w_hsep_height; + win1->w_prev->w_hsep_height = win1->w_hsep_height; + win1->w_hsep_height = height; + if (win1->w_prev->w_vsep_width == 1) { // Remove the vertical separator from the last-but-one window, // add it to the last window. Adjust the frame widths. @@ -1889,6 +1951,11 @@ void win_move_after(win_T *win1, win_T *win2) height = win1->w_status_height; win1->w_status_height = win2->w_status_height; win2->w_status_height = height; + + height = win1->w_hsep_height; + win1->w_hsep_height = win2->w_hsep_height; + win2->w_hsep_height = height; + if (win1->w_vsep_width == 1) { // Remove the vertical separator from win1, add it to the last // window, win2. Adjust the frame widths. @@ -2103,13 +2170,15 @@ static void win_equal_rec(win_T *next_curwin, bool current, frame_T *topfr, int if (dir != 'h') { // equalize frame heights // Compute maximum number of windows vertically in this frame. n = frame_minheight(topfr, NOWIN); - // add one for the bottom window if it doesn't have a statusline + // add one for the bottom window if it doesn't have a statusline or separator if (row + height == cmdline_row && p_ls == 0) { + extra_sep = STATUS_HEIGHT; + } else if (global_stl_height() > 0) { extra_sep = 1; } else { extra_sep = 0; } - totwincount = (n + extra_sep) / (p_wmh + 1); + totwincount = (n + extra_sep) / (p_wmh + STATUS_HEIGHT); has_next_curwin = frame_has_win(topfr, next_curwin); /* @@ -2120,7 +2189,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; @@ -2144,7 +2213,7 @@ static void win_equal_rec(win_T *next_curwin, bool current, frame_T *topfr, int } else { // These windows don't use up room. totwincount -= (n + (fr->fr_next == NULL - ? extra_sep : 0)) / (p_wmh + 1); + ? extra_sep : 0)) / (p_wmh + STATUS_HEIGHT); } room -= new_size - n; if (room < 0) { @@ -2190,7 +2259,7 @@ static void win_equal_rec(win_T *next_curwin, bool current, frame_T *topfr, int // Compute the maximum number of windows vert. in "fr". n = frame_minheight(fr, NOWIN); wincount = (n + (fr->fr_next == NULL ? extra_sep : 0)) - / (p_wmh + 1); + / (p_wmh + STATUS_HEIGHT); m = frame_minheight(fr, next_curwin); if (has_next_curwin) { hnc = frame_has_win(fr, next_curwin); @@ -2229,28 +2298,102 @@ static void win_equal_rec(win_T *next_curwin, bool current, frame_T *topfr, int } } -/// Closes all windows for buffer `buf`. +static void leaving_window(win_T *const win) + FUNC_ATTR_NONNULL_ALL +{ + // Only matters for a prompt window. + if (!bt_prompt(win->w_buffer)) { + return; + } + + // When leaving a prompt window stop Insert mode and perhaps restart + // it when entering that window again. + win->w_buffer->b_prompt_insert = restart_edit; + if (restart_edit != NUL && mode_displayed) { + clear_cmdline = true; // unshow mode later + } + restart_edit = NUL; + + // When leaving the window (or closing the window) was done from a + // callback we need to break out of the Insert mode loop and restart Insert + // mode when entering the window again. + if (State & INSERT) { + stop_insert_mode = true; + if (win->w_buffer->b_prompt_insert == NUL) { + win->w_buffer->b_prompt_insert = 'A'; + } + } +} + +void entering_window(win_T *const win) + FUNC_ATTR_NONNULL_ALL +{ + // Only matters for a prompt window. + if (!bt_prompt(win->w_buffer)) { + return; + } + + // When switching to a prompt buffer that was in Insert mode, don't stop + // Insert mode, it may have been set in leaving_window(). + if (win->w_buffer->b_prompt_insert != NUL) { + stop_insert_mode = false; + } + + // When entering the prompt window restart Insert mode if we were in Insert + // mode when we left it and not already in Insert mode. + if ((State & INSERT) == 0) { + restart_edit = win->w_buffer->b_prompt_insert; + } +} + +void win_init_empty(win_T *wp) +{ + redraw_later(wp, NOT_VALID); + wp->w_lines_valid = 0; + wp->w_cursor.lnum = 1; + wp->w_curswant = wp->w_cursor.col = 0; + wp->w_cursor.coladd = 0; + wp->w_pcmark.lnum = 1; // pcmark not cleared but set to line 1 + wp->w_pcmark.col = 0; + wp->w_prev_pcmark.lnum = 0; + wp->w_prev_pcmark.col = 0; + wp->w_topline = 1; + wp->w_topfill = 0; + wp->w_botline = 2; + wp->w_s = &wp->w_buffer->b_s; +} + +/// Init the current window "curwin". +/// Called when a new file is being edited. +void curwin_init(void) +{ + win_init_empty(curwin); +} + +/// Closes all windows for buffer `buf` unless there is only one non-floating window. /// -/// @param keep_curwin don't close `curwin` -void close_windows(buf_T *buf, int keep_curwin) +/// @param keep_curwin don't close `curwin` +void close_windows(buf_T *buf, bool keep_curwin) { tabpage_T *tp, *nexttp; int h = tabline_height(); ++RedrawingDisabled; - for (win_T *wp = firstwin; wp != NULL && !ONE_WINDOW;) { + // Start from lastwin to close floating windows with the same buffer first. + // When the autocommand window is involved win_close() may need to print an error message. + for (win_T *wp = lastwin; wp != NULL && (lastwin == aucmd_win || !one_window(wp));) { 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; } // Start all over, autocommands may change the window layout. - wp = firstwin; + wp = lastwin; } else { - wp = wp->w_next; + wp = wp->w_prev; } } @@ -2280,23 +2423,24 @@ void close_windows(buf_T *buf, int keep_curwin) } } -/// Check that current window is the last one. +/// Check that the specified window is the last one. +/// @param win counted even if floating /// -/// @return true if the current window is the only window that exists, false if -/// there is another, possibly in another tab page. -static bool last_window(void) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT +/// @return true if the specified window is the only window that exists, +/// false if there is another, possibly in another tab page. +bool last_window(win_T *win) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { - return one_window() && first_tabpage->tp_next == NULL; + return one_window(win) && first_tabpage->tp_next == NULL; } -/// Check that current tab page contains no more then one window other than -/// "aucmd_win". Only counts floating window if it is current. -bool one_window(void) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT +/// Check that current tab page contains no more then one window other than `aucmd_win`. +/// @param counted_float counted even if floating, but not if it is `aucmd_win` +bool one_window(win_T *counted_float) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { bool seen_one = false; FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - if (wp != aucmd_win && (!wp->w_floating || wp == curwin)) { + if (wp != aucmd_win && (!wp->w_floating || wp == counted_float)) { if (seen_one) { return false; } @@ -2320,6 +2464,24 @@ 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 in the current tab can be closed. +/// Do not call this when the autocommand window is in use! +/// +/// @return true if all floating windows can be closed +static bool can_close_floating_windows(void) +{ + assert(lastwin != aucmd_win); + for (win_T *wp = lastwin; wp->w_floating; wp = wp->w_prev) { + 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 @@ -2367,6 +2529,7 @@ static bool close_last_window_tabpage(win_T *win, bool free_buf, tabpage_T *prev shell_new_rows(); } } + entering_window(curwin); // Since goto_tabpage_tp above did not trigger *Enter autocommands, do // that now. @@ -2378,12 +2541,47 @@ static bool close_last_window_tabpage(win_T *win, bool free_buf, tabpage_T *prev return true; } +/// Close the buffer of "win" and unload it if "free_buf" is true. +/// "abort_if_last" is passed to close_buffer(): abort closing if all other +/// windows are closed. +static void win_close_buffer(win_T *win, bool free_buf, bool abort_if_last) +{ + // Free independent synblock before the buffer is freed. + if (win->w_buffer != NULL) { + reset_synblock(win); + } + + // When a quickfix/location list window is closed and the buffer is + // displayed in only one window, then unlist the buffer. + if (win->w_buffer != NULL && bt_quickfix(win->w_buffer) + && win->w_buffer->b_nwindows == 1) { + win->w_buffer->b_p_bl = false; + } + + // Close the link to the buffer. + if (win->w_buffer != NULL) { + bufref_T bufref; + set_bufref(&bufref, curbuf); + win->w_closing = true; + close_buffer(win, win->w_buffer, free_buf ? DOBUF_UNLOAD : 0, abort_if_last, true); + if (win_valid_any_tab(win)) { + win->w_closing = false; + } + + // Make sure curbuf is valid. It can become invalid if 'bufhidden' is + // "wipe". + if (!bufref_valid(&bufref)) { + curbuf = firstbuf; + } + } +} + // Close window "win". Only works for the current tab page. // If "free_buf" is true related buffer may be unloaded. // // 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; @@ -2394,7 +2592,7 @@ int win_close(win_T *win, bool free_buf) frame_T *win_frame = win->w_floating ? NULL : win->w_frame->fr_parent; const bool had_diffmode = win->w_p_diff; - if (last_window() && !win->w_floating) { + if (last_window(win)) { emsg(_("E444: Cannot close last window")); return FAIL; } @@ -2407,15 +2605,24 @@ int win_close(win_T *win, bool free_buf) emsg(_(e_autocmd_close)); return FAIL; } - if ((firstwin == aucmd_win || lastwin == aucmd_win) && one_window()) { - emsg(_("E814: Cannot close window, only autocmd window would remain")); - return FAIL; - } - if ((firstwin == win && lastwin_nofloating() == win) - && lastwin->w_floating) { - // TODO(bfredl): we might close the float also instead - emsg(e_floatonly); - return FAIL; + if (lastwin->w_floating && one_window(win)) { + if (lastwin == aucmd_win) { + emsg(_("E814: Cannot close window, only autocmd window would remain")); + return FAIL; + } + if (force || can_close_floating_windows()) { + // close the last window until the there are no floating windows + while (lastwin->w_floating) { + // `force` flag isn't actually used when closing a floating window. + if (win_close(lastwin, free_buf, true) == FAIL) { + // If closing the window fails give up, to avoid looping forever. + return FAIL; + } + } + } else { + emsg(e_floatonly); + return FAIL; + } } // When closing the last window in a tab page first go to another tab page @@ -2434,10 +2641,10 @@ int win_close(win_T *win, bool free_buf) } if (win == curwin) { - /* - * Guess which window is going to be the new current window. - * This may change because of the autocommands (sigh). - */ + leaving_window(curwin); + + // Guess which window is going to be the new current window. + // This may change because of the autocommands (sigh). if (!win->w_floating) { wp = frame2win(win_altframe(win, NULL)); } else { @@ -2460,7 +2667,7 @@ int win_close(win_T *win, bool free_buf) return FAIL; } win->w_closing = false; - if (last_window()) { + if (last_window(win)) { return FAIL; } } @@ -2470,7 +2677,7 @@ int win_close(win_T *win, bool free_buf) return FAIL; } win->w_closing = false; - if (last_window()) { + if (last_window(win)) { return FAIL; } // autocmds may abort script processing @@ -2507,32 +2714,10 @@ int win_close(win_T *win, bool free_buf) return OK; } - // Free independent synblock before the buffer is freed. - if (win->w_buffer != NULL) { - reset_synblock(win); - } - - /* - * Close the link to the buffer. - */ - if (win->w_buffer != NULL) { - bufref_T bufref; - set_bufref(&bufref, curbuf); - win->w_closing = true; - close_buffer(win, win->w_buffer, free_buf ? DOBUF_UNLOAD : 0, true); - if (win_valid_any_tab(win)) { - win->w_closing = false; - } - - // Make sure curbuf is valid. It can become invalid if 'bufhidden' is - // "wipe". - if (!bufref_valid(&bufref)) { - curbuf = firstbuf; - } - } + win_close_buffer(win, free_buf, true); if (only_one_window() && win_valid(win) && win->w_buffer == NULL - && (last_window() || curtab != prev_curtab + && (last_window(win) || curtab != prev_curtab || close_last_window_tabpage(win, free_buf, prev_curtab)) && !win->w_floating) { // Autocommands have closed all windows, quit now. Restore @@ -2552,7 +2737,7 @@ int win_close(win_T *win, bool free_buf) // Autocommands may have closed the window already, or closed the only // other window or moved to another tab page. - if (!win_valid(win) || (!win->w_floating && last_window()) + if (!win_valid(win) || (!win->w_floating && last_window(win)) || close_last_window_tabpage(win, free_buf, prev_curtab)) { return FAIL; } @@ -2666,7 +2851,7 @@ static void do_autocmd_winclosed(win_T *win) } recursive = true; char_u winid[NUMBUFLEN]; - vim_snprintf((char *)winid, sizeof(winid), "%i", win->handle); + vim_snprintf((char *)winid, sizeof(winid), "%d", win->handle); apply_autocmds(EVENT_WINCLOSED, winid, winid, false, win->w_buffer); recursive = false; } @@ -2700,7 +2885,7 @@ void win_close_othertab(win_T *win, int free_buf, tabpage_T *tp) if (win->w_buffer != NULL) { // Close the link to the buffer. - close_buffer(win, win->w_buffer, free_buf ? DOBUF_UNLOAD : 0, false); + close_buffer(win, win->w_buffer, free_buf ? DOBUF_UNLOAD : 0, false, true); } // Careful: Autocommands may have closed the tab page or made it the @@ -2708,6 +2893,13 @@ void win_close_othertab(win_T *win, int free_buf, tabpage_T *tp) for (ptp = first_tabpage; ptp != NULL && ptp != tp; ptp = ptp->tp_next) { } if (ptp == NULL || tp == curtab) { + // If the buffer was removed from the window we have to give it any + // buffer. + if (win_valid_any_tab(win) && win->w_buffer == NULL) { + win->w_buffer = firstbuf; + firstbuf->b_nwindows++; + win_init_empty(win); + } return; } @@ -2804,6 +2996,9 @@ void win_free_all(void) { int dummy; + // avoid an error for switching tabpage with the cmdline window open + cmdwin_type = 0; + while (first_tabpage->tp_next != NULL) { tabpage_close(TRUE); } @@ -3007,9 +3202,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; } @@ -3050,7 +3257,7 @@ static tabpage_T *alt_tabpage(void) /* * Find the left-upper window in frame "frp". */ -static win_T *frame2win(frame_T *frp) +win_T *frame2win(frame_T *frp) { while (frp->fr_win == NULL) { frp = frp->fr_child; @@ -3077,23 +3284,40 @@ static bool frame_has_win(const frame_T *frp, const win_T *wp) return false; } +/// Check if current window is at the bottom +/// Returns true if there are no windows below current window +static bool is_bottom_win(win_T *wp) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL +{ + frame_T *frp; + for (frp = wp->w_frame; frp->fr_parent != NULL; frp = frp->fr_parent) { + if (frp->fr_parent->fr_layout == FR_COL && frp->fr_next != NULL) { + return false; + } + } + return true; +} /// Set a new height for a frame. Recursively sets the height for contained /// frames and windows. Caller must take care of positions. /// /// @param topfirst resize topmost contained frame first. /// @param wfh obey 'winfixheight' when there is a choice; /// may cause the height not to be set. -static void frame_new_height(frame_T *topfrp, int height, bool topfirst, bool wfh) +void frame_new_height(frame_T *topfrp, int height, bool topfirst, bool wfh) FUNC_ATTR_NONNULL_ALL { frame_T *frp; int extra_lines; int h; + win_T *wp; if (topfrp->fr_win != NULL) { // Simple case: just one window. - win_new_height(topfrp->fr_win, - height - topfrp->fr_win->w_status_height); + wp = topfrp->fr_win; + if (is_bottom_win(wp)) { + wp->w_hsep_height = 0; + } + win_new_height(wp, height - wp->w_hsep_height - wp->w_status_height); } else if (topfrp->fr_layout == FR_ROW) { do { // All frames in this row get the same new height. @@ -3249,8 +3473,8 @@ static void frame_add_statusline(frame_T *frp) if (frp->fr_layout == FR_LEAF) { wp = frp->fr_win; if (wp->w_status_height == 0) { - if (wp->w_height > 0) { // don't make it negative - --wp->w_height; + if (wp->w_height - STATUS_HEIGHT >= 0) { // don't make it negative + wp->w_height -= STATUS_HEIGHT; } wp->w_status_height = STATUS_HEIGHT; } @@ -3370,10 +3594,8 @@ static void frame_new_width(frame_T *topfrp, int width, bool leftfirst, bool wfw topfrp->fr_width = width; } -/* - * Add the vertical separator to windows at the right side of "frp". - * Note: Does not check if there is room! - */ +/// Add the vertical separator to windows at the right side of "frp". +/// Note: Does not check if there is room! static void frame_add_vsep(const frame_T *frp) FUNC_ATTR_NONNULL_ARG(1) { @@ -3403,6 +3625,37 @@ static void frame_add_vsep(const frame_T *frp) } } +/// Add the horizontal separator to windows at the bottom of "frp". +/// Note: Does not check if there is room or whether the windows have a statusline! +static void frame_add_hsep(const frame_T *frp) + FUNC_ATTR_NONNULL_ARG(1) +{ + win_T *wp; + + if (frp->fr_layout == FR_LEAF) { + wp = frp->fr_win; + if (wp->w_hsep_height == 0) { + if (wp->w_height > 0) { // don't make it negative + wp->w_height++; + } + wp->w_hsep_height = 1; + } + } else if (frp->fr_layout == FR_ROW) { + // Handle all the frames in the row. + FOR_ALL_FRAMES(frp, frp->fr_child) { + frame_add_hsep(frp); + } + } else { + assert(frp->fr_layout == FR_COL); + // Only need to handle the last frame in the column. + frp = frp->fr_child; + while (frp->fr_next != NULL) { + frp = frp->fr_next; + } + frame_add_hsep(frp); + } +} + /* * Set frame width from the window it contains. */ @@ -3417,7 +3670,7 @@ static void frame_fix_width(win_T *wp) static void frame_fix_height(win_T *wp) FUNC_ATTR_NONNULL_ALL { - wp->w_frame->fr_height = wp->w_height + wp->w_status_height; + wp->w_frame->fr_height = wp->w_height + wp->w_hsep_height + wp->w_status_height; } /* @@ -3435,10 +3688,11 @@ static int frame_minheight(frame_T *topfrp, win_T *next_curwin) if (topfrp->fr_win != NULL) { if (topfrp->fr_win == next_curwin) { - m = p_wh + topfrp->fr_win->w_status_height; + m = p_wh + topfrp->fr_win->w_hsep_height + topfrp->fr_win->w_status_height; } else { - // window: minimal height of the window plus status line - m = p_wmh + topfrp->fr_win->w_status_height; + // window: minimal height of the window plus separator column or status line + // depending on whether global statusline is enabled + m = p_wmh + topfrp->fr_win->w_hsep_height + topfrp->fr_win->w_status_height; if (topfrp->fr_win == curwin && next_curwin == NULL) { // Current window is minimal one line high. if (p_wmh == 0) { @@ -3529,7 +3783,7 @@ void close_others(int message, int forceit) return; } - if (one_window() && !lastwin->w_floating) { + if (one_nonfloat() && !lastwin->w_floating) { if (message && !autocmd_busy) { msg(_(m_onlyone)); @@ -3562,7 +3816,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) { @@ -3570,33 +3826,6 @@ void close_others(int message, int forceit) } } - -/* - * Init the current window "curwin". - * Called when a new file is being edited. - */ -void curwin_init(void) -{ - win_init_empty(curwin); -} - -void win_init_empty(win_T *wp) -{ - redraw_later(wp, NOT_VALID); - wp->w_lines_valid = 0; - wp->w_cursor.lnum = 1; - wp->w_curswant = wp->w_cursor.col = 0; - wp->w_cursor.coladd = 0; - wp->w_pcmark.lnum = 1; // pcmark not cleared but set to line 1 - wp->w_pcmark.col = 0; - wp->w_prev_pcmark.lnum = 0; - wp->w_prev_pcmark.col = 0; - wp->w_topline = 1; - wp->w_topfill = 0; - wp->w_botline = 2; - wp->w_s = &wp->w_buffer->b_s; -} - /* * Allocate the first window and put an empty buffer in it. * Called from main(). @@ -3628,7 +3857,7 @@ void win_alloc_aucmd_win(void) fconfig.width = Columns; fconfig.height = 5; fconfig.focusable = false; - aucmd_win = win_new_float(NULL, fconfig, &err); + aucmd_win = win_new_float(NULL, true, fconfig, &err); aucmd_win->w_buffer->b_nwindows--; RESET_BINDING(aucmd_win); } @@ -3665,7 +3894,7 @@ static int win_alloc_firstwin(win_T *oldwin) new_frame(curwin); topframe = curwin->w_frame; topframe->fr_width = Columns; - topframe->fr_height = Rows - p_ch; + topframe->fr_height = Rows - p_ch - global_stl_height(); return OK; } @@ -3752,6 +3981,11 @@ int win_new_tabpage(int after, char_u *filename) tabpage_T *newtp; int n; + if (cmdwin_type != 0) { + emsg(_(e_cmdwin)); + return FAIL; + } + newtp = alloc_tabpage(); // Remember the current windows in this Tab page. @@ -3801,6 +4035,8 @@ int win_new_tabpage(int after, char_u *filename) lastused_tabpage = old_curtab; + entering_window(curwin); + apply_autocmds(EVENT_WINNEW, NULL, NULL, false, curbuf); apply_autocmds(EVENT_WINENTER, NULL, NULL, false, curbuf); apply_autocmds(EVENT_TABNEW, filename, filename, false, curbuf); @@ -3956,6 +4192,7 @@ static int leave_tabpage(buf_T *new_curbuf, bool trigger_leave_autocmds) { tabpage_T *tp = curtab; + leaving_window(curwin); reset_VIsual_and_resel(); // stop Visual mode if (trigger_leave_autocmds) { if (new_curbuf != curbuf) { @@ -3994,8 +4231,8 @@ static void enter_tabpage(tabpage_T *tp, buf_T *old_curbuf, bool trigger_enter_a { int old_off = tp->tp_firstwin->w_winrow; win_T *next_prevwin = tp->tp_prevwin; - tabpage_T *old_curtab = curtab; + curtab = tp; firstwin = tp->tp_firstwin; lastwin = tp->tp_lastwin; @@ -4147,6 +4384,10 @@ void goto_tabpage(int n) /// @param trigger_leave_autocmds when true trigger *Leave autocommands. void goto_tabpage_tp(tabpage_T *tp, bool trigger_enter_autocmds, bool trigger_leave_autocmds) { + if (trigger_enter_autocmds || trigger_leave_autocmds) { + CHECK_CMDWIN; + } + // Don't repeat a message in another tab page. set_keep_msg(NULL, 0); @@ -4162,13 +4403,15 @@ void goto_tabpage_tp(tabpage_T *tp, bool trigger_enter_autocmds, bool trigger_le } } -// Go to the last accessed tab page, if there is one. -void goto_tabpage_lastused(void) +/// Go to the last accessed tab page, if there is one. +/// @return true if the tab page is valid, false otherwise. +bool goto_tabpage_lastused(void) { - int index = tabpage_index(lastused_tabpage); - if (index < tabpage_index(NULL)) { - goto_tabpage(index); + if (valid_tabpage(lastused_tabpage)) { + goto_tabpage_tp(lastused_tabpage, true, true); + return true; } + return false; } /* @@ -4478,6 +4721,10 @@ static void win_enter_ext(win_T *const wp, const int flags) return; } + if (!curwin_invalid) { + leaving_window(curwin); + } + if (!curwin_invalid && (flags & WEE_TRIGGER_LEAVE_AUTOCMDS)) { // Be careful: If autocommands delete the window, return now. if (wp->w_buffer != curbuf) { @@ -4525,6 +4772,8 @@ static void win_enter_ext(win_T *const wp, const int flags) fix_current_dir(); + entering_window(curwin); + // Careful: autocommands may close the window and make "wp" invalid if (flags & WEE_TRIGGER_NEW_AUTOCMDS) { apply_autocmds(EVENT_WINNEW, NULL, NULL, false, curbuf); } @@ -4558,7 +4807,7 @@ static void win_enter_ext(win_T *const wp, const int flags) } // set window width to desired minimal value - if (curwin->w_width < p_wiw && !curwin->w_p_wfw && !wp->w_floating) { + if (curwin->w_width < p_wiw && !curwin->w_p_wfw && !curwin->w_floating) { win_setwidth((int)p_wiw); } @@ -4587,22 +4836,33 @@ 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); } - 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); + last_chdir_reason = NULL; shorten_fnames(true); } } @@ -4754,6 +5014,8 @@ static void win_free(win_T *wp, tabpage_T *tp) clear_winopt(&wp->w_onebuf_opt); clear_winopt(&wp->w_allbuf_opt); + xfree(wp->w_p_lcs_chars.multispace); + vars_clear(&wp->w_vars->dv_hashtab); // free all w: variables hash_init(&wp->w_vars->dv_hashtab); unref_var_dict(wp->w_vars); @@ -4984,25 +5246,35 @@ void shell_new_columns(void) win_reconfig_floats(); // The size of floats might change } -/// Check if "wp" has scrolled since last time it was checked -/// @param wp the window to check -bool win_did_scroll(win_T *wp) +/// Trigger WinScrolled for "curwin" if needed. +void may_trigger_winscrolled(void) { - return (curwin->w_last_topline != curwin->w_topline - || curwin->w_last_leftcol != curwin->w_leftcol - || curwin->w_last_width != curwin->w_width - || curwin->w_last_height != curwin->w_height); -} + static bool recursive = false; -/// Trigger WinScrolled autocmd -void do_autocmd_winscrolled(win_T *wp) -{ - apply_autocmds(EVENT_WINSCROLLED, NULL, NULL, false, curbuf); + if (recursive || !has_event(EVENT_WINSCROLLED)) { + return; + } + + win_T *wp = curwin; + if (wp->w_last_topline != wp->w_topline + || wp->w_last_leftcol != wp->w_leftcol + || wp->w_last_width != wp->w_width + || wp->w_last_height != wp->w_height) { + char_u winid[NUMBUFLEN]; + vim_snprintf((char *)winid, sizeof(winid), "%d", wp->handle); + + recursive = true; + apply_autocmds(EVENT_WINSCROLLED, winid, winid, false, wp->w_buffer); + recursive = false; - wp->w_last_topline = wp->w_topline; - wp->w_last_leftcol = wp->w_leftcol; - wp->w_last_width = wp->w_width; - wp->w_last_height = wp->w_height; + // an autocmd may close the window, "wp" may be invalid now + if (win_valid_any_tab(wp)) { + wp->w_last_topline = wp->w_topline; + wp->w_last_leftcol = wp->w_leftcol; + wp->w_last_width = wp->w_width; + wp->w_last_height = wp->w_height; + } + } } /* @@ -5048,11 +5320,8 @@ void win_size_restore(garray_T *gap) } } -/* - * Update the position for all windows, using the width and height of the - * frames. - * Returns the row just after the last window. - */ +// Update the position for all windows, using the width and height of the frames. +// Returns the row just after the last window and global statusline (if there is one). int win_comp_pos(void) { int row = tabline_height(); @@ -5067,7 +5336,7 @@ int win_comp_pos(void) } } - return row; + return row + global_stl_height(); } void win_reconfig_floats(void) @@ -5101,7 +5370,7 @@ static void frame_comp_pos(frame_T *topfrp, int *row, int *col) wp->w_redr_status = true; wp->w_pos_changed = true; } - const int h = wp->w_height + wp->w_status_height; + const int h = wp->w_height + wp->w_hsep_height + wp->w_status_height; *row += h > topfrp->fr_height ? topfrp->fr_height : h; *col += wp->w_width + wp->w_vsep_width; } else { @@ -5150,7 +5419,7 @@ void win_setheight_win(int height, win_T *win) win_config_float(win, win->w_float_config); redraw_later(win, NOT_VALID); } else { - frame_setheight(win->w_frame, height + win->w_status_height); + frame_setheight(win->w_frame, height + win->w_hsep_height + win->w_status_height); // recompute the window positions int row = win_comp_pos(); @@ -5159,11 +5428,17 @@ void win_setheight_win(int height, win_T *win) // line, clear it. if (full_screen && msg_scrolled == 0 && row < cmdline_row) { grid_fill(&default_grid, row, cmdline_row, 0, Columns, ' ', ' ', 0); + if (msg_grid.chars) { + clear_cmdline = true; + } } cmdline_row = row; + p_ch = MAX(Rows - cmdline_row, 1); + curtab->tp_ch_used = p_ch; msg_row = row; msg_col = 0; redraw_all_later(NOT_VALID); + showmode(); } } @@ -5199,7 +5474,9 @@ static void frame_setheight(frame_T *curfrp, int height) if (curfrp->fr_parent == NULL) { // topframe: can only change the command line if (height > ROWS_AVAIL) { - height = ROWS_AVAIL; + // If height is greater than the available space, try to create space for the frame by + // reducing 'cmdheight' if possible, while making sure `cmdheight` doesn't go below 1. + height = MIN(ROWS_AVAIL + (p_ch - 1), height); } if (height > 0) { frame_new_height(curfrp, height, false, false); @@ -5241,8 +5518,8 @@ static void frame_setheight(frame_T *curfrp, int height) room_cmdline = 0; } else { win_T *wp = lastwin_nofloating(); - room_cmdline = Rows - p_ch - - (wp->w_winrow + wp->w_height + wp->w_status_height); + room_cmdline = Rows - p_ch - global_stl_height() + - (wp->w_winrow + wp->w_height + wp->w_hsep_height + wp->w_status_height); if (room_cmdline < 0) { room_cmdline = 0; } @@ -5608,7 +5885,7 @@ void win_drag_status_line(win_T *dragwin, int offset) if (curfr->fr_next == NULL) { room -= 1; } else { - room -= p_ch; + room -= p_ch + global_stl_height(); } if (room < 0) { room = 0; @@ -5734,7 +6011,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) { @@ -5747,7 +6023,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 @@ -6244,72 +6522,94 @@ char_u *file_name_in_line(char_u *line, int col, int options, long count, char_u return find_file_name_in_path(ptr, len, options, count, rel_fname); } -/// Add or remove a status line for the bottom window(s), according to the +/// Add or remove a status line from window(s), according to the /// value of 'laststatus'. /// /// @param morewin pretend there are two or more windows if true. void last_status(bool morewin) { // Don't make a difference between horizontal or vertical split. - last_status_rec(topframe, (p_ls == 2 - || (p_ls == 1 && (morewin || !one_window())))); + last_status_rec(topframe, (p_ls == 2 || (p_ls == 1 && (morewin || !one_nonfloat()))), + global_stl_height() > 0); } -static void last_status_rec(frame_T *fr, bool statusline) +// Look for resizable frames and take lines from them to make room for the statusline +static void resize_frame_for_status(frame_T *fr) +{ + // Find a frame to take a line from. + frame_T *fp = fr; + win_T *wp = fr->fr_win; + + while (fp->fr_height <= frame_minheight(fp, NULL)) { + if (fp == topframe) { + emsg(_(e_noroom)); + return; + } + // In a column of frames: go to frame above. If already at + // the top or in a row of frames: go to parent. + if (fp->fr_parent->fr_layout == FR_COL && fp->fr_prev != NULL) { + fp = fp->fr_prev; + } else { + fp = fp->fr_parent; + } + } + if (fp != fr) { + frame_new_height(fp, fp->fr_height - 1, false, false); + frame_fix_height(wp); + (void)win_comp_pos(); + } else { + win_new_height(wp, wp->w_height - 1); + } +} + +static void last_status_rec(frame_T *fr, bool statusline, bool is_stl_global) { frame_T *fp; win_T *wp; if (fr->fr_layout == FR_LEAF) { wp = fr->fr_win; - if (wp->w_status_height != 0 && !statusline) { - // remove status line - win_new_height(wp, wp->w_height + 1); + bool is_last = is_bottom_win(wp); + + if (is_last) { + if (wp->w_status_height != 0 && (!statusline || is_stl_global)) { + // Remove status line + wp->w_status_height = 0; + win_new_height(wp, wp->w_height + STATUS_HEIGHT); + comp_col(); + } else if (wp->w_status_height == 0 && !is_stl_global && statusline) { + // Add statusline to window if needed + wp->w_status_height = STATUS_HEIGHT; + resize_frame_for_status(fr); + comp_col(); + } + } else if (wp->w_status_height != 0 && is_stl_global) { + // If statusline is global and the window has a statusline, replace it with a horizontal + // separator wp->w_status_height = 0; + wp->w_hsep_height = 1; comp_col(); - } else if (wp->w_status_height == 0 && statusline) { - // Find a frame to take a line from. - fp = fr; - while (fp->fr_height <= frame_minheight(fp, NULL)) { - if (fp == topframe) { - emsg(_(e_noroom)); - return; - } - // In a column of frames: go to frame above. If already at - // the top or in a row of frames: go to parent. - if (fp->fr_parent->fr_layout == FR_COL && fp->fr_prev != NULL) { - fp = fp->fr_prev; - } else { - fp = fp->fr_parent; - } - } - wp->w_status_height = 1; - if (fp != fr) { - frame_new_height(fp, fp->fr_height - 1, false, false); - frame_fix_height(wp); - (void)win_comp_pos(); - } else { - win_new_height(wp, wp->w_height - 1); - } + } else if (wp->w_status_height == 0 && !is_stl_global) { + // If statusline isn't global and the window doesn't have a statusline, re-add it + wp->w_status_height = STATUS_HEIGHT; + wp->w_hsep_height = 0; comp_col(); - redraw_all_later(SOME_VALID); } - } else if (fr->fr_layout == FR_ROW) { - // vertically split windows, set status line for each one + redraw_all_later(SOME_VALID); + } else if (fr->fr_layout == FR_COL) { + // For a column frame, recursively call this function for all child frames FOR_ALL_FRAMES(fp, fr->fr_child) { - last_status_rec(fp, statusline); + last_status_rec(fp, statusline, is_stl_global); } } else { - // horizontally split window, set status line for last one - for (fp = fr->fr_child; fp->fr_next != NULL; fp = fp->fr_next) { + // For a row frame, recursively call this function for all child frames + FOR_ALL_FRAMES(fp, fr->fr_child) { + last_status_rec(fp, statusline, is_stl_global); } - last_status_rec(fp, statusline); } } -/* - * Return the number of lines used by the tab page line. - */ +/// Return the number of lines used by the tab page line. int tabline_height(void) { if (ui_has(kUITabline)) { @@ -6325,10 +6625,14 @@ int tabline_height(void) return 1; } -/* - * Return the minimal number of rows that is needed on the screen to display - * the current number of windows. - */ +/// Return the number of lines used by the global statusline +int global_stl_height(void) +{ + return (p_ls == 3) ? STATUS_HEIGHT : 0; +} + +/// Return the minimal number of rows that is needed on the screen to display +/// the current number of windows. int min_rows(void) { if (firstwin == NULL) { // not initialized yet @@ -6342,7 +6646,7 @@ int min_rows(void) total = n; } } - total += tabline_height(); + total += tabline_height() + global_stl_height(); total += 1; // count the room for the command line return total; } @@ -6569,20 +6873,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; @@ -6604,33 +6915,35 @@ 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 - // directory, it now has to be restored. - fix_current_dir(); } /// Make "buf" the current buffer. @@ -6660,286 +6973,6 @@ void restore_buffer(bufref_T *save_curbuf) } } - -/// Add match to the match list of window 'wp'. The pattern 'pat' will be -/// highlighted with the group 'grp' with priority 'prio'. -/// Optionally, a desired ID 'id' can be specified (greater than or equal to 1). -/// -/// @param[in] id a desired ID 'id' can be specified -/// (greater than or equal to 1). -1 must be specified if no -/// particular ID is desired -/// @param[in] conceal_char pointer to conceal replacement char -/// @return ID of added match, -1 on failure. -int match_add(win_T *wp, const char *const grp, const char *const pat, int prio, int id, - list_T *pos_list, const char *const conceal_char) - FUNC_ATTR_NONNULL_ARG(1, 2) -{ - matchitem_T *cur; - matchitem_T *prev; - matchitem_T *m; - int hlg_id; - regprog_T *regprog = NULL; - int rtype = SOME_VALID; - - if (*grp == NUL || (pat != NULL && *pat == NUL)) { - return -1; - } - if (id < -1 || id == 0) { - semsg(_("E799: Invalid ID: %" PRId64 - " (must be greater than or equal to 1)"), - (int64_t)id); - return -1; - } - if (id != -1) { - cur = wp->w_match_head; - while (cur != NULL) { - if (cur->id == id) { - semsg(_("E801: ID already taken: %" PRId64), (int64_t)id); - return -1; - } - cur = cur->next; - } - } - if ((hlg_id = syn_check_group(grp, strlen(grp))) == 0) { - return -1; - } - if (pat != NULL && (regprog = vim_regcomp((char_u *)pat, RE_MAGIC)) == NULL) { - semsg(_(e_invarg2), pat); - return -1; - } - - // Find available match ID. - while (id == -1) { - cur = wp->w_match_head; - while (cur != NULL && cur->id != wp->w_next_match_id) { - cur = cur->next; - } - if (cur == NULL) { - id = wp->w_next_match_id; - } - wp->w_next_match_id++; - } - - // Build new match. - m = xcalloc(1, sizeof(matchitem_T)); - m->id = id; - m->priority = prio; - m->pattern = pat == NULL ? NULL: (char_u *)xstrdup(pat); - m->hlg_id = hlg_id; - m->match.regprog = regprog; - m->match.rmm_ic = FALSE; - m->match.rmm_maxcol = 0; - m->conceal_char = 0; - if (conceal_char != NULL) { - m->conceal_char = utf_ptr2char((const char_u *)conceal_char); - } - - // Set up position matches - if (pos_list != NULL) { - linenr_T toplnum = 0; - linenr_T botlnum = 0; - - int i = 0; - TV_LIST_ITER(pos_list, li, { - linenr_T lnum = 0; - colnr_T col = 0; - int len = 1; - bool error = false; - - if (TV_LIST_ITEM_TV(li)->v_type == VAR_LIST) { - const list_T *const subl = TV_LIST_ITEM_TV(li)->vval.v_list; - const listitem_T *subli = tv_list_first(subl); - if (subli == NULL) { - semsg(_("E5030: Empty list at position %d"), - (int)tv_list_idx_of_item(pos_list, li)); - goto fail; - } - lnum = tv_get_number_chk(TV_LIST_ITEM_TV(subli), &error); - if (error) { - goto fail; - } - if (lnum <= 0) { - continue; - } - m->pos.pos[i].lnum = lnum; - subli = TV_LIST_ITEM_NEXT(subl, subli); - if (subli != NULL) { - col = tv_get_number_chk(TV_LIST_ITEM_TV(subli), &error); - if (error) { - goto fail; - } - if (col < 0) { - continue; - } - subli = TV_LIST_ITEM_NEXT(subl, subli); - if (subli != NULL) { - len = tv_get_number_chk(TV_LIST_ITEM_TV(subli), &error); - if (len < 0) { - continue; - } - if (error) { - goto fail; - } - } - } - m->pos.pos[i].col = col; - m->pos.pos[i].len = len; - } else if (TV_LIST_ITEM_TV(li)->v_type == VAR_NUMBER) { - if (TV_LIST_ITEM_TV(li)->vval.v_number <= 0) { - continue; - } - m->pos.pos[i].lnum = TV_LIST_ITEM_TV(li)->vval.v_number; - m->pos.pos[i].col = 0; - m->pos.pos[i].len = 0; - } else { - semsg(_("E5031: List or number required at position %d"), - (int)tv_list_idx_of_item(pos_list, li)); - goto fail; - } - if (toplnum == 0 || lnum < toplnum) { - toplnum = lnum; - } - if (botlnum == 0 || lnum >= botlnum) { - botlnum = lnum + 1; - } - i++; - if (i >= MAXPOSMATCH) { - break; - } - }); - - // Calculate top and bottom lines for redrawing area - if (toplnum != 0) { - if (wp->w_buffer->b_mod_set) { - if (wp->w_buffer->b_mod_top > toplnum) { - wp->w_buffer->b_mod_top = toplnum; - } - if (wp->w_buffer->b_mod_bot < botlnum) { - wp->w_buffer->b_mod_bot = botlnum; - } - } else { - wp->w_buffer->b_mod_set = true; - wp->w_buffer->b_mod_top = toplnum; - wp->w_buffer->b_mod_bot = botlnum; - wp->w_buffer->b_mod_xlines = 0; - } - m->pos.toplnum = toplnum; - m->pos.botlnum = botlnum; - rtype = VALID; - } - } - - // Insert new match. The match list is in ascending order with regard to - // the match priorities. - cur = wp->w_match_head; - prev = cur; - while (cur != NULL && prio >= cur->priority) { - prev = cur; - cur = cur->next; - } - if (cur == prev) { - wp->w_match_head = m; - } else { - prev->next = m; - } - m->next = cur; - - redraw_later(wp, rtype); - return id; - -fail: - xfree(m); - return -1; -} - - -/// Delete match with ID 'id' in the match list of window 'wp'. -/// -/// @param perr print error messages if true. -int match_delete(win_T *wp, int id, bool perr) -{ - matchitem_T *cur = wp->w_match_head; - matchitem_T *prev = cur; - int rtype = SOME_VALID; - - if (id < 1) { - if (perr) { - semsg(_("E802: Invalid ID: %" PRId64 - " (must be greater than or equal to 1)"), - (int64_t)id); - } - return -1; - } - while (cur != NULL && cur->id != id) { - prev = cur; - cur = cur->next; - } - if (cur == NULL) { - if (perr) { - semsg(_("E803: ID not found: %" PRId64), (int64_t)id); - } - return -1; - } - if (cur == prev) { - wp->w_match_head = cur->next; - } else { - prev->next = cur->next; - } - vim_regfree(cur->match.regprog); - xfree(cur->pattern); - if (cur->pos.toplnum != 0) { - if (wp->w_buffer->b_mod_set) { - if (wp->w_buffer->b_mod_top > cur->pos.toplnum) { - wp->w_buffer->b_mod_top = cur->pos.toplnum; - } - if (wp->w_buffer->b_mod_bot < cur->pos.botlnum) { - wp->w_buffer->b_mod_bot = cur->pos.botlnum; - } - } else { - wp->w_buffer->b_mod_set = true; - wp->w_buffer->b_mod_top = cur->pos.toplnum; - wp->w_buffer->b_mod_bot = cur->pos.botlnum; - wp->w_buffer->b_mod_xlines = 0; - } - rtype = VALID; - } - xfree(cur); - redraw_later(wp, rtype); - return 0; -} - -/* - * Delete all matches in the match list of window 'wp'. - */ -void clear_matches(win_T *wp) -{ - matchitem_T *m; - - while (wp->w_match_head != NULL) { - m = wp->w_match_head->next; - vim_regfree(wp->w_match_head->match.regprog); - xfree(wp->w_match_head->pattern); - xfree(wp->w_match_head); - wp->w_match_head = m; - } - redraw_later(wp, SOME_VALID); -} - -/* - * Get match from ID 'id' in window 'wp'. - * Return NULL if match not found. - */ -matchitem_T *get_match(win_T *wp, int id) -{ - matchitem_T *cur = wp->w_match_head; - - while (cur != NULL && cur->id != id) { - cur = cur->next; - } - return cur; -} - - /// Check that "topfrp" and its children are at the right height. /// /// @param topfrp top frame pointer @@ -7065,16 +7098,14 @@ void win_id2tabwin(typval_T *const argvars, typval_T *const rettv) tv_list_append_number(list, winnr); } -win_T *win_id2wp(typval_T *argvars) +win_T *win_id2wp(int id) { - return win_id2wp_tp(argvars, NULL); + return win_id2wp_tp(id, NULL); } // Return the window and tab pointer of window "id". -win_T *win_id2wp_tp(typval_T *argvars, tabpage_T **tpp) +win_T *win_id2wp_tp(int id, tabpage_T **tpp) { - int id = tv_get_number(&argvars[0]); - FOR_ALL_TAB_WINDOWS(tp, wp) { if (wp->handle == id) { if (tpp != NULL) { diff --git a/src/nvim/window.h b/src/nvim/window.h index 7e465a9f08..b75b8abd9b 100644 --- a/src/nvim/window.h +++ b/src/nvim/window.h @@ -4,19 +4,19 @@ #include <stdbool.h> #include "nvim/buffer_defs.h" +#include "nvim/mark.h" +#include "nvim/os/os.h" // Values for file_name_in_line() #define FNAME_MESS 1 // give error message #define FNAME_EXP 2 // expand to path #define FNAME_HYP 4 // check for hypertext link #define FNAME_INCL 8 // apply 'includeexpr' -#define FNAME_REL 16 /* ".." and "./" are relative to the (current) - file instead of the current directory */ +#define FNAME_REL 16 // ".." and "./" are relative to the (current) + // file instead of the current directory #define FNAME_UNESC 32 // remove backslashes used for escaping -/* - * arguments for win_split() - */ +// arguments for win_split() #define WSP_ROOM 1 // require enough room #define WSP_VERT 2 // split vertically #define WSP_TOP 4 // window at top-left of shell @@ -26,12 +26,66 @@ #define WSP_ABOVE 64 // put new window above/left #define WSP_NEWLOC 128 // don't copy location list -/* - * Minimum screen size - */ +// Minimum screen size #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; \ + char_u cwd_[MAXPATHL]; \ + char_u autocwd_[MAXPATHL]; \ + bool apply_acd_ = false; \ + int cwd_status_ = FAIL; \ + /* Getting and setting directory can be slow on some systems, only do */ \ + /* this when the current or target window/tab have a local directory or */ \ + /* 'acd' is set. */ \ + if (curwin != wp \ + && (curwin->w_localdir != NULL || wp->w_localdir != NULL \ + || (curtab != tp && (curtab->tp_localdir != NULL || tp->tp_localdir != NULL)) \ + || p_acd)) { \ + cwd_status_ = os_dirname(cwd_, MAXPATHL); \ + } \ + /* If 'acd' is set, check we are using that directory. If yes, then */ \ + /* apply 'acd' afterwards, otherwise restore the current directory. */ \ + if (cwd_status_ == OK && p_acd) { \ + do_autochdir(); \ + apply_acd_ = os_dirname(autocwd_, MAXPATHL) == OK && STRCMP(cwd_, autocwd_) == 0; \ + } \ + switchwin_T switchwin_; \ + if (switch_win_noblock(&switchwin_, wp_, (tp), true) == OK) { \ + check_cursor(); \ + block; \ + } \ + restore_win_noblock(&switchwin_, true); \ + if (apply_acd_) { \ + do_autochdir(); \ + } else if (cwd_status_ == OK) { \ + os_chdir((char *)cwd_); \ + } \ + /* 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/src/uncrustify.cfg b/src/uncrustify.cfg index 558fa1759f..49ce394dc9 100644 --- a/src/uncrustify.cfg +++ b/src/uncrustify.cfg @@ -1,4 +1,4 @@ -# Uncrustify-0.73.0-199-0dfafb27 +# Uncrustify-0.74.0 # # General options @@ -214,6 +214,10 @@ sp_after_ptr_star_func = remove # ignore/add/remove/force/not_defined # function prototype or function definition. sp_after_ptr_star_trailing = ignore # ignore/add/remove/force/not_defined +# Add or remove space between the pointer star '*' and the name of the variable +# in a function pointer definition. +sp_ptr_star_func_var = ignore # ignore/add/remove/force/not_defined + # Add or remove space after a pointer star '*', if followed by an open # parenthesis, as in 'void* (*)()'. sp_ptr_star_paren = ignore # ignore/add/remove/force/not_defined @@ -311,19 +315,33 @@ sp_permit_cpp11_shift = false # true/false # 'while', etc.). sp_before_sparen = force # ignore/add/remove/force/not_defined -# Add or remove space inside '(' and ')' of control statements. +# Add or remove space inside '(' and ')' of control statements other than +# 'for'. sp_inside_sparen = remove # ignore/add/remove/force/not_defined -# Add or remove space after '(' of control statements. +# Add or remove space after '(' of control statements other than 'for'. # # Overrides sp_inside_sparen. sp_inside_sparen_open = remove # ignore/add/remove/force/not_defined -# Add or remove space before ')' of control statements. +# Add or remove space before ')' of control statements other than 'for'. # # Overrides sp_inside_sparen. sp_inside_sparen_close = ignore # ignore/add/remove/force/not_defined +# Add or remove space inside '(' and ')' of 'for' statements. +sp_inside_for = ignore # ignore/add/remove/force/not_defined + +# Add or remove space after '(' of 'for' statements. +# +# Overrides sp_inside_for. +sp_inside_for_open = ignore # ignore/add/remove/force/not_defined + +# Add or remove space before ')' of 'for' statements. +# +# Overrides sp_inside_for. +sp_inside_for_close = ignore # ignore/add/remove/force/not_defined + # Add or remove space between '((' or '))' of control statements. sp_sparen_paren = ignore # ignore/add/remove/force/not_defined @@ -648,6 +666,11 @@ sp_func_class_paren = ignore # ignore/add/remove/force/not_defined # and '()'. sp_func_class_paren_empty = ignore # ignore/add/remove/force/not_defined +# Add or remove space after 'return'. +# +# Default: force +sp_return = force # ignore/add/remove/force/not_defined + # Add or remove space between 'return' and '('. sp_return_paren = force # ignore/add/remove/force/not_defined @@ -971,11 +994,31 @@ sp_inside_newop_paren_open = ignore # ignore/add/remove/force/not_defined # Overrides sp_inside_newop_paren. sp_inside_newop_paren_close = ignore # ignore/add/remove/force/not_defined -# Add or remove space before a trailing or embedded comment. -sp_before_tr_emb_cmt = add # ignore/add/remove/force/not_defined +# Add or remove space before a trailing comment. +sp_before_tr_cmt = add # ignore/add/remove/force/not_defined + +# Number of spaces before a trailing comment. +sp_num_before_tr_cmt = 2 # unsigned number + +# Add or remove space before an embedded comment. +# +# Default: force +sp_before_emb_cmt = force # ignore/add/remove/force/not_defined + +# Number of spaces before an embedded comment. +# +# Default: 1 +sp_num_before_emb_cmt = 1 # unsigned number + +# Add or remove space after an embedded comment. +# +# Default: force +sp_after_emb_cmt = force # ignore/add/remove/force/not_defined -# Number of spaces before a trailing or embedded comment. -sp_num_before_tr_emb_cmt = 2 # unsigned number +# Number of spaces after an embedded comment. +# +# Default: 1 +sp_num_after_emb_cmt = 1 # unsigned number # (Java) Add or remove space between an annotation and the open parenthesis. sp_annotation_paren = ignore # ignore/add/remove/force/not_defined @@ -1216,12 +1259,16 @@ indent_sparen_extra = 0 # number indent_relative_single_line_comments = true # true/false # Spaces to indent 'case' from 'switch'. Usually 0 or indent_columns. -# It might wise to choose the same value for the option indent_case_brace. +# It might be wise to choose the same value for the option indent_case_brace. indent_switch_case = 0 # unsigned number +# Spaces to indent the body of a 'switch' before any 'case'. +# Usually the same as indent_columns or indent_switch_case. +indent_switch_body = 0 # unsigned number + # Spaces to indent '{' from 'case'. By default, the brace will appear under # the 'c' in case. Usually set to 0 or indent_columns. Negative values are OK. -# It might wise to choose the same value for the option indent_switch_case. +# It might be wise to choose the same value for the option indent_switch_case. indent_case_brace = 0 # number # indent 'break' with 'case' from 'switch'. @@ -1236,13 +1283,31 @@ indent_switch_pp = true # true/false # Usually 0. indent_case_shift = 0 # unsigned number +# Whether to align comments before 'case' with the 'case'. +# +# Default: true +indent_case_comment = true # true/false + +# Whether to indent comments not found in first column. +# +# Default: true +indent_comment = true # true/false + # Whether to indent comments found in first column. indent_col1_comment = false # true/false # Whether to indent multi string literal in first column. indent_col1_multi_string_literal = false # true/false -# How to indent goto labels. +# Align comments on adjacent lines that are this many columns apart or less. +# +# Default: 3 +indent_comment_align_thresh = 3 # unsigned number + +# Whether to ignore indent for goto labels. +indent_ignore_label = false # true/false + +# How to indent goto labels. Requires indent_ignore_label=false. # # >0: Absolute column where 1 is the leftmost column # <=0: Subtract from brace indent @@ -1414,7 +1479,7 @@ indent_using_block = true # true/false # 0: Off (default) # 1: When the `if_false` is a continuation, indent it under `if_false` # 2: When the `:` is a continuation, indent it under `?` -indent_ternary_operator = 2 # unsigned number +indent_ternary_operator = 0 # unsigned number # Whether to indent the statements inside ternary operator. indent_inside_ternary_operator = false # true/false @@ -2622,6 +2687,22 @@ align_right_cmt_at_col = 0 # unsigned number # 0: Don't align (default). align_func_proto_span = 0 # unsigned number +# How to consider (or treat) the '*' in the alignment of function prototypes. +# +# 0: Part of the type 'void * foo();' (default) +# 1: Part of the function 'void *foo();' +# 2: Dangling 'void *foo();' +# Dangling: the '*' will not be taken into account when aligning. +align_func_proto_star_style = 0 # unsigned number + +# How to consider (or treat) the '&' in the alignment of function prototypes. +# +# 0: Part of the type 'long & foo();' (default) +# 1: Part of the function 'long &foo();' +# 2: Dangling 'long &foo();' +# Dangling: the '&' will not be taken into account when aligning. +align_func_proto_amp_style = 0 # unsigned number + # The threshold for aligning function prototypes. # Use a negative number for absolute thresholds. # @@ -3101,6 +3182,9 @@ pp_indent_in_guard = false # true/false # indented from column 1. pp_define_at_level = false # true/false +# Whether to indent '#include' at the brace level. +pp_include_at_level = false # true/false + # Whether to ignore the '#define' body while formatting. pp_ignore_define_body = false # true/false @@ -3307,5 +3391,5 @@ set QUESTION REAL_FATTR_CONST set QUESTION REAL_FATTR_NONNULL_ALL set QUESTION REAL_FATTR_PURE set QUESTION REAL_FATTR_WARN_UNUSED_RESULT -# option(s) with 'not default' value: 87 +# option(s) with 'not default' value: 86 # diff --git a/test/README.md b/test/README.md index 37aa54c157..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 @@ -116,7 +116,7 @@ Filtering Tests ### Filter by name -Another filter method is by setting a pattern of test name to `TEST_FILTER`. +Another filter method is by setting a pattern of test name to `TEST_FILTER` or `TEST_FILTER_OUT`. ``` lua it('foo api',function() @@ -131,6 +131,10 @@ To run only test with filter name: TEST_FILTER='foo.*api' make functionaltest +To run all tests except ones matching a filter: + + TEST_FILTER_OUT='foo.*api' make functionaltest + ### Filter by file To run a *specific* unit test: @@ -193,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/busted/outputHandlers/nvim.lua b/test/busted/outputHandlers/nvim.lua index 0e9801b94b..5e9c52e0bd 100644 --- a/test/busted/outputHandlers/nvim.lua +++ b/test/busted/outputHandlers/nvim.lua @@ -1,5 +1,4 @@ local pretty = require 'pl.pretty' -local global_helpers = require('test.helpers') -- Colors are disabled by default. #15610 local colors = setmetatable({}, {__index = function() return function(s) return s == nil and '' or tostring(s) end end}) @@ -192,7 +191,6 @@ return function(options) local tests = (testCount == 1 and 'test' or 'tests') local files = (fileCount == 1 and 'file' or 'files') io.write(globalTeardown) - io.write(global_helpers.read_nvim_log(nil, true)) io.write(suiteEndString:format(testCount, tests, fileCount, files, elapsedTime_ms)) io.write(getSummaryString()) io.flush() diff --git a/test/functional/api/autocmd_spec.lua b/test/functional/api/autocmd_spec.lua new file mode 100644 index 0000000000..377b4fecf0 --- /dev/null +++ b/test/functional/api/autocmd_spec.lua @@ -0,0 +1,1122 @@ +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 +local pcall_err = helpers.pcall_err + +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) + + it('removes an autocommand if the callback returns true', function() + meths.set_var("some_condition", false) + + exec_lua [[ + vim.api.nvim_create_autocmd("User", { + pattern = "Test", + desc = "A test autocommand", + callback = function() + return vim.g.some_condition + end, + }) + ]] + + meths.exec_autocmds("User", {pattern = "Test"}) + eq({{ + buflocal = false, + command = 'A test autocommand', + desc = 'A test autocommand', + event = 'User', + id = 1, + once = false, + pattern = 'Test', + }}, meths.get_autocmds({event = "User", pattern = "Test"})) + meths.set_var("some_condition", true) + meths.exec_autocmds("User", {pattern = "Test"}) + eq({}, meths.get_autocmds({event = "User", pattern = "Test"})) + end) + + it('receives an args table', function() + local res = exec_lua [[ + local group_id = vim.api.nvim_create_augroup("TestGroup", {}) + local autocmd_id = vim.api.nvim_create_autocmd("User", { + group = "TestGroup", + pattern = "Te*", + callback = function(args) + vim.g.autocmd_args = args + end, + }) + + return {group_id, autocmd_id} + ]] + + meths.exec_autocmds("User", {pattern = "Test pattern"}) + eq({ + id = res[2], + group = res[1], + event = "User", + match = "Test pattern", + file = "Test pattern", + buf = 1, + }, meths.get_var("autocmd_args")) + + -- Test without a group + res = exec_lua [[ + local autocmd_id = vim.api.nvim_create_autocmd("User", { + pattern = "*", + callback = function(args) + vim.g.autocmd_args = args + end, + }) + + return {autocmd_id} + ]] + + meths.exec_autocmds("User", {pattern = "some_pat"}) + eq({ + id = res[1], + group = nil, + event = "User", + match = "some_pat", + file = "some_pat", + buf = 1, + }, meths.get_var("autocmd_args")) + + 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) + + it('should work with buffer numbers', function() + command [[new]] + command [[au! InsertEnter]] + command [[au InsertEnter <buffer=1> :echo "1"]] + command [[au InsertEnter <buffer=2> :echo "2"]] + + local aus = meths.get_autocmds { event = "InsertEnter", buffer = 0 } + eq({{ + buffer = 2, + buflocal = true, + command = ':echo "2"', + event = 'InsertEnter', + once = false, + pattern = '<buffer=2>', + }}, aus) + + aus = meths.get_autocmds { event = "InsertEnter", buffer = 1 } + eq({{ + buffer = 1, + buflocal = true, + command = ':echo "1"', + event = "InsertEnter", + once = false, + pattern = "<buffer=1>", + }}, aus) + + aus = meths.get_autocmds { event = "InsertEnter", buffer = { 1, 2 } } + eq({{ + buffer = 1, + buflocal = true, + command = ':echo "1"', + event = "InsertEnter", + once = false, + pattern = "<buffer=1>", + }, { + buffer = 2, + buflocal = true, + command = ':echo "2"', + event = "InsertEnter", + once = false, + pattern = "<buffer=2>", + }}, aus) + + eq("Invalid value for 'buffer': must be an integer or array of integers", pcall_err(meths.get_autocmds, { event = "InsertEnter", buffer = "foo" })) + eq("Invalid value for 'buffer': must be an integer", pcall_err(meths.get_autocmds, { event = "InsertEnter", buffer = { "foo", 42 } })) + eq("Invalid buffer id: 42", pcall_err(meths.get_autocmds, { event = "InsertEnter", buffer = { 42 } })) + + local bufs = {} + for _ = 1, 257 do + table.insert(bufs, meths.create_buf(true, false)) + end + + eq("Too many buffers. Please limit yourself to 256 or fewer", pcall_err(meths.get_autocmds, { event = "InsertEnter", buffer = bufs })) + end) + + it('should return autocmds when group is specified by id', function() + local auid = meths.create_augroup("nvim_test_augroup", { clear = true }) + meths.create_autocmd("FileType", { group = auid, command = 'echo "1"' }) + meths.create_autocmd("FileType", { group = auid, command = 'echo "2"' }) + + local aus = meths.get_autocmds { group = auid } + eq(2, #aus) + + local aus2 = meths.get_autocmds { group = auid, event = "InsertEnter" } + eq(0, #aus2) + end) + + it('should return autocmds when group is specified by name', function() + local auname = "nvim_test_augroup" + meths.create_augroup(auname, { clear = true }) + meths.create_autocmd("FileType", { group = auname, command = 'echo "1"' }) + meths.create_autocmd("FileType", { group = auname, command = 'echo "2"' }) + + local aus = meths.get_autocmds { group = auname } + eq(2, #aus) + + local aus2 = meths.get_autocmds { group = auname, event = "InsertEnter" } + eq(0, #aus2) + end) + + it('should respect nested', function() + local bufs = exec_lua [[ + local count = 0 + vim.api.nvim_create_autocmd("BufNew", { + once = false, + nested = true, + callback = function() + count = count + 1 + if count > 5 then + return true + end + + vim.cmd(string.format("new README_%s.md", count)) + end + }) + + vim.cmd "new First.md" + + return vim.api.nvim_list_bufs() + ]] + + -- 1 for the first buffer + -- 2 for First.md + -- 3-7 for the 5 we make in the autocmd + eq({1, 2, 3, 4, 5, 6, 7}, bufs) + 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 name', 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) + + it('raises error for undefined augroup id', function() + local success, code = unpack(meths.exec_lua([[ + return {pcall(function() + -- Make sure the augroup is deleted + vim.api.nvim_del_augroup_by_id(1) + + vim.api.nvim_create_autocmd("FileType", { + pattern = "*", + group = 1, + command = "echo 'hello'", + }) + end)} + ]], {})) + + eq(false, success) + matches('invalid augroup: 1', code) + end) + + it('raises error for invalid group type', function() + local success, code = unpack(meths.exec_lua([[ + return {pcall(function() + vim.api.nvim_create_autocmd("FileType", { + pattern = "*", + group = true, + command = "echo 'hello'", + }) + end)} + ]], {})) + + eq(false, success) + matches("'group' must be a string or an integer", 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_exec_autocmds', 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.exec_autocmds("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.exec_autocmds("CursorHold", { buffer = 1 }) + eq(-1, meths.get_var("buffer_executed")) + + meths.exec_autocmds("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.exec_autocmds("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.exec_autocmds, "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.exec_autocmds("CursorHoldI", { buffer = 1 }) + eq('none', meths.get_var("filename_executed")) + + meths.exec_autocmds("CursorHoldI", { buffer = tonumber(meths.get_current_buf()) }) + eq('__init__.py', meths.get_var("filename_executed")) + + -- Reset filename + meths.set_var("filename_executed", 'none') + + meths.exec_autocmds("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.exec_autocmds("User", { pattern = "OtherCommand" }) + eq('none', meths.get_var('matched')) + meths.exec_autocmds("User", { pattern = "TestCommand" }) + eq('matched', meths.get_var('matched')) + end) + + it('can pass group by id', function() + meths.set_var("group_executed", false) + + local auid = meths.create_augroup("nvim_test_augroup", { clear = true }) + meths.create_autocmd("FileType", { + group = auid, + command = 'let g:group_executed = v:true', + }) + + eq(false, meths.get_var("group_executed")) + meths.exec_autocmds("FileType", { group = auid }) + eq(true, meths.get_var("group_executed")) + end) + + it('can pass group by name', function() + meths.set_var("group_executed", false) + + local auname = "nvim_test_augroup" + meths.create_augroup(auname, { clear = true }) + meths.create_autocmd("FileType", { + group = auname, + command = 'let g:group_executed = v:true', + }) + + eq(false, meths.get_var("group_executed")) + meths.exec_autocmds("FileType", { group = auname }) + eq(true, meths.get_var("group_executed")) + 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('can delete non-existent groups with pcall', function() + eq(false, exec_lua[[return pcall(vim.api.nvim_del_augroup_by_name, 'noexist')]]) + eq('Vim:E367: No such group: "noexist"', pcall_err(meths.del_augroup_by_name, 'noexist')) + + eq(false, exec_lua[[return pcall(vim.api.nvim_del_augroup_by_id, -12342)]]) + eq('Vim:E367: No such group: "--Deleted--"', pcall_err(meths.del_augroup_by_id, -12312)) + 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) + + describe('nvim_clear_autocmds', function() + it('should clear based on event + pattern', function() + command('autocmd InsertEnter *.py :echo "Python can be cool sometimes"') + command('autocmd InsertEnter *.txt :echo "Text Files Are Cool"') + + local search = { event = "InsertEnter", pattern = "*.txt" } + local before_delete = meths.get_autocmds(search) + eq(1, #before_delete) + + local before_delete_all = meths.get_autocmds { event = search.event } + eq(2, #before_delete_all) + + meths.clear_autocmds(search) + local after_delete = meths.get_autocmds(search) + eq(0, #after_delete) + + local after_delete_all = meths.get_autocmds { event = search.event } + eq(1, #after_delete_all) + end) + + it('should clear based on event', function() + command('autocmd InsertEnter *.py :echo "Python can be cool sometimes"') + command('autocmd InsertEnter *.txt :echo "Text Files Are Cool"') + + local search = { event = "InsertEnter"} + local before_delete = meths.get_autocmds(search) + eq(2, #before_delete) + + meths.clear_autocmds(search) + local after_delete = meths.get_autocmds(search) + eq(0, #after_delete) + end) + + it('should clear based on pattern', function() + command('autocmd InsertEnter *.TestPat1 :echo "Enter 1"') + command('autocmd InsertLeave *.TestPat1 :echo "Leave 1"') + command('autocmd InsertEnter *.TestPat2 :echo "Enter 2"') + command('autocmd InsertLeave *.TestPat2 :echo "Leave 2"') + + local search = { pattern = "*.TestPat1"} + local before_delete = meths.get_autocmds(search) + eq(2, #before_delete) + local before_delete_events = meths.get_autocmds { event = { "InsertEnter", "InsertLeave" } } + eq(4, #before_delete_events) + + meths.clear_autocmds(search) + local after_delete = meths.get_autocmds(search) + eq(0, #after_delete) + + local after_delete_events = meths.get_autocmds { event = { "InsertEnter", "InsertLeave" } } + eq(2, #after_delete_events) + end) + + it('should allow clearing by buffer', function() + command('autocmd! InsertEnter') + command('autocmd InsertEnter <buffer> :echo "Enter Buffer"') + command('autocmd InsertEnter *.TestPat1 :echo "Enter Pattern"') + + local search = { event = "InsertEnter" } + local before_delete = meths.get_autocmds(search) + eq(2, #before_delete) + + meths.clear_autocmds { buffer = 0 } + local after_delete = meths.get_autocmds(search) + eq(1, #after_delete) + eq("*.TestPat1", after_delete[1].pattern) + end) + + it('should allow clearing by buffer and group', function() + command('augroup TestNvimClearAutocmds') + command(' au!') + command(' autocmd InsertEnter <buffer> :echo "Enter Buffer"') + command(' autocmd InsertEnter *.TestPat1 :echo "Enter Pattern"') + command('augroup END') + + local search = { event = "InsertEnter", group = "TestNvimClearAutocmds" } + local before_delete = meths.get_autocmds(search) + eq(2, #before_delete) + + -- Doesn't clear without passing group. + meths.clear_autocmds { buffer = 0 } + local without_group = meths.get_autocmds(search) + eq(2, #without_group) + + -- Doest clear with passing group. + meths.clear_autocmds { buffer = 0, group = search.group } + local with_group = meths.get_autocmds(search) + eq(1, #with_group) + end) + end) +end) diff --git a/test/functional/api/buffer_spec.lua b/test/functional/api/buffer_spec.lua index a0c97804b7..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() @@ -629,6 +664,13 @@ describe('api/buf', function() -- Doesn't change the global value eq([[^\s*#\s*define]], nvim('get_option', 'define')) end) + + it('returns values for unset local options', function() + -- 'undolevels' is only set to its "unset" value when a new buffer is + -- created + command('enew') + eq(-123456, curbuf('get_option', 'undolevels')) + end) end) describe('nvim_buf_get_name, nvim_buf_set_name', function() diff --git a/test/functional/api/buffer_updates_spec.lua b/test/functional/api/buffer_updates_spec.lua index c9c9be5406..fc09e4cde0 100644 --- a/test/functional/api/buffer_updates_spec.lua +++ b/test/functional/api/buffer_updates_spec.lua @@ -827,6 +827,7 @@ describe('API: buffer events:', function() end it('when :terminal lines change', function() + if helpers.pending_win32(pending) then return end local buffer_lines = {} local expected_lines = {} command('terminal "'..nvim_prog..'" -u NONE -i NONE -n -c "set shortmess+=A"') diff --git a/test/functional/api/command_spec.lua b/test/functional/api/command_spec.lua index 6f929ad1ca..e4963e8a65 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,12 +59,13 @@ 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([[ + let s:foo = 1 command -complete=custom,ListUsers -nargs=+ Finger !finger <args> ]]) eq({Finger=cmd1}, meths.get_commands({builtin=false})) @@ -66,15 +73,234 @@ describe('nvim_get_commands', function() eq({Finger=cmd1, TestCmd=cmd0}, meths.get_commands({builtin=false})) source([[ + function! s:foo() abort + endfunction command -bang -nargs=* Cmd2 call <SID>foo(<q-args>) ]]) source([[ + function! s:ohyeah() abort + endfunction command -bar -nargs=0 Cmd3 call <SID>ohyeah() ]]) source([[ + function! s:just_great() abort + endfunction command -register Cmd4 call <SID>just_great() ]]) -- TODO(justinmk): Order is stable but undefined. Sort before return? eq({Cmd2=cmd2, Cmd3=cmd3, Cmd4=cmd4, Finger=cmd1, TestCmd=cmd0}, meths.get_commands({builtin=false})) end) end) + +describe('nvim_create_user_command', function() + before_each(clear) + + it('works with strings', function() + meths.create_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_create_user_command('CommandWithLuaCallback', function(opts) + result = opts + end, { + nargs = "*", + bang = true, + count = 2, + }) + ]] + + eq({ + args = [[this is a\ test]], + fargs = {"this", "is", "a test"}, + bang = false, + line1 = 1, + line2 = 1, + mods = "", + range = 0, + count = 2, + reg = "", + }, exec_lua [=[ + vim.api.nvim_command([[CommandWithLuaCallback this is a\ test]]) + return result + ]=]) + + eq({ + args = [[this includes\ a backslash: \\]], + fargs = {"this", "includes a", "backslash:", "\\"}, + bang = false, + line1 = 1, + line2 = 1, + mods = "", + range = 0, + count = 2, + reg = "", + }, exec_lua [=[ + vim.api.nvim_command([[CommandWithLuaCallback this includes\ a backslash: \\]]) + return result + ]=]) + + eq({ + args = "a\\b", + fargs = {"a\\b"}, + bang = false, + line1 = 1, + line2 = 1, + mods = "", + range = 0, + count = 2, + reg = "", + }, exec_lua [=[ + vim.api.nvim_command('CommandWithLuaCallback a\\b') + 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_create_user_command('CommandWithOneArg', function(opts) + result = opts + end, { + nargs = "?", + bang = true, + count = 2, + }) + ]] + + eq({ + args = "hello I'm one argument", + fargs = {"hello I'm one argument"}, -- 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 argument') + return result + ]]) + + end) + + it('can define buffer-local commands', function() + local bufnr = meths.create_buf(false, false) + bufmeths.create_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_create_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_create_user_command('test', 'echo "hi"', {}) + ]])) + + matches('Invalid command name', pcall_err(exec_lua, [[ + vim.api.nvim_create_user_command('t@', 'echo "hi"', {}) + ]])) + + matches('Invalid command name', pcall_err(exec_lua, [[ + vim.api.nvim_create_user_command('T@st', 'echo "hi"', {}) + ]])) + + matches('Invalid command name', pcall_err(exec_lua, [[ + vim.api.nvim_create_user_command('Test!', 'echo "hi"', {}) + ]])) + + matches('Invalid command name', pcall_err(exec_lua, [[ + vim.api.nvim_create_user_command('💩', 'echo "hi"', {}) + ]])) + end) +end) + +describe('nvim_del_user_command', function() + before_each(clear) + + it('can delete global commands', function() + meths.create_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.create_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 50b4b85d2a..3f96efd4ef 100644 --- a/test/functional/api/extmark_spec.lua +++ b/test/functional/api/extmark_spec.lua @@ -104,10 +104,26 @@ describe('API/extmarks', function() it("can end extranges past final newline using end_col = 0", function() set_extmark(ns, marks[1], 0, 0, { end_col = 0, - end_line = 1 + end_row = 1 }) eq("end_col value outside range", - pcall_err(set_extmark, ns, marks[2], 0, 0, { end_col = 1, end_line = 1 })) + 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() @@ -420,7 +436,7 @@ describe('API/extmarks', function() end) it('marks move with open line', function() - -- open_line in misc1.c + -- open_line in change.c -- testing marks below are also moved feed("yyP") set_extmark(ns, marks[1], 0, 4) @@ -489,7 +505,7 @@ describe('API/extmarks', function() end) it('marks move with line splits (using enter)', function() - -- open_line in misc1.c + -- open_line in change.c -- testing marks below are also moved feed("yyP") set_extmark(ns, marks[1], 0, 4) @@ -500,7 +516,7 @@ describe('API/extmarks', function() end) it('marks at last line move on insert new line', function() - -- open_line in misc1.c + -- open_line in change.c set_extmark(ns, marks[1], 0, 4) feed('0i<cr><esc>') check_undo_redo(ns, marks[1], 0, 4, 1, 4) @@ -1424,6 +1440,67 @@ describe('API/extmarks', function() eq({ {1, 0, 0}, {2, 0, 8} }, meths.buf_get_extmarks(0, ns, 0, -1, {})) end) + + it('can accept "end_row" or "end_line" #16548', function() + set_extmark(ns, marks[1], 0, 0, { + end_col = 0, + end_line = 1 + }) + 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) describe('Extmarks buffer api with many marks', function() diff --git a/test/functional/api/highlight_spec.lua b/test/functional/api/highlight_spec.lua index 21e3094f8e..06cdb0bc19 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 @@ -27,8 +28,11 @@ describe('API: highlight',function() bold = true, italic = true, reverse = true, - undercurl = true, underline = true, + underlineline = true, + undercurl = true, + underdot = true, + underdash = true, strikethrough = true, } @@ -51,7 +55,7 @@ describe('API: highlight',function() eq('Invalid highlight id: 30000', string.match(emsg, 'Invalid.*')) -- Test all highlight properties. - command('hi NewHighlight gui=underline,bold,undercurl,italic,reverse,strikethrough') + command('hi NewHighlight gui=underline,bold,underlineline,undercurl,underdot,underdash,italic,reverse,strikethrough') eq(expected_rgb2, nvim("get_hl_by_id", hl_id, true)) -- Test nil argument. @@ -194,10 +198,15 @@ describe("API: set highlight", function() reverse = true, undercurl = true, underline = true, + underdash = true, + underdot = true, + underlineline = true, + strikethrough = true, cterm = { italic = true, reverse = true, undercurl = true, + strikethrough = true, } } local highlight3_result_gui = { @@ -208,6 +217,10 @@ describe("API: set highlight", function() reverse = true, undercurl = true, underline = true, + underdash = true, + underdot = true, + underlineline = true, + strikethrough = true, } local highlight3_result_cterm = { background = highlight_color.ctermbg, @@ -215,6 +228,7 @@ describe("API: set highlight", function() italic = true, reverse = true, undercurl = true, + strikethrough = true, } local function get_ns() @@ -237,6 +251,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 +272,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,underlineline,undercurl,underdot,underdash,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_notifications_spec.lua b/test/functional/api/server_notifications_spec.lua index 6367cc5caa..1c00f001ff 100644 --- a/test/functional/api/server_notifications_spec.lua +++ b/test/functional/api/server_notifications_spec.lua @@ -81,6 +81,8 @@ describe('notify', function() if isCI() then pending('hangs on CI #14083 #15251') return + elseif helpers.skip_fragile(pending) then + return end if helpers.pending_win32(pending) then return end local catchan = eval("jobstart(['cat'], {'rpc': v:true})") 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 21de4925b5..e6ed0f939b 100644 --- a/test/functional/api/vim_spec.lua +++ b/test/functional/api/vim_spec.lua @@ -6,6 +6,7 @@ local assert_alive = helpers.assert_alive local NIL = helpers.NIL local clear, nvim, eq, neq = helpers.clear, helpers.nvim, helpers.eq, helpers.neq local command = helpers.command +local exec = helpers.exec local eval = helpers.eval local expect = helpers.expect local funcs = helpers.funcs @@ -23,6 +24,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 +335,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 +538,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)', {}) @@ -609,34 +637,374 @@ describe('API', function() eq('Invalid phase: 4', pcall_err(request, 'nvim_paste', 'foo', true, 4)) end) - it('stream: multiple chunks form one undo-block', function() - nvim('paste', '1/chunk 1 (start)\n', true, 1) - nvim('paste', '1/chunk 2 (end)\n', true, 3) - local expected1 = [[ - 1/chunk 1 (start) - 1/chunk 2 (end) - ]] - expect(expected1) - nvim('paste', '2/chunk 1 (start)\n', true, 1) - nvim('paste', '2/chunk 2\n', true, 2) - expect([[ - 1/chunk 1 (start) - 1/chunk 2 (end) - 2/chunk 1 (start) - 2/chunk 2 - ]]) - nvim('paste', '2/chunk 3\n', true, 2) - nvim('paste', '2/chunk 4 (end)\n', true, 3) - expect([[ - 1/chunk 1 (start) - 1/chunk 2 (end) - 2/chunk 1 (start) - 2/chunk 2 - 2/chunk 3 - 2/chunk 4 (end) - ]]) - feed('u') -- Undo. - expect(expected1) + local function run_streamed_paste_tests() + it('stream: multiple chunks form one undo-block', function() + nvim('paste', '1/chunk 1 (start)\n', true, 1) + nvim('paste', '1/chunk 2 (end)\n', true, 3) + local expected1 = [[ + 1/chunk 1 (start) + 1/chunk 2 (end) + ]] + expect(expected1) + nvim('paste', '2/chunk 1 (start)\n', true, 1) + nvim('paste', '2/chunk 2\n', true, 2) + expect([[ + 1/chunk 1 (start) + 1/chunk 2 (end) + 2/chunk 1 (start) + 2/chunk 2 + ]]) + nvim('paste', '2/chunk 3\n', true, 2) + nvim('paste', '2/chunk 4 (end)\n', true, 3) + expect([[ + 1/chunk 1 (start) + 1/chunk 2 (end) + 2/chunk 1 (start) + 2/chunk 2 + 2/chunk 3 + 2/chunk 4 (end) + ]]) + feed('u') -- Undo. + expect(expected1) + end) + it('stream: Insert mode', function() + -- If nvim_paste() calls :undojoin without making any changes, this makes it an error. + feed('afoo<Esc>u') + feed('i') + nvim('paste', 'aaaaaa', false, 1) + nvim('paste', 'bbbbbb', false, 2) + nvim('paste', 'cccccc', false, 2) + nvim('paste', 'dddddd', false, 3) + expect('aaaaaabbbbbbccccccdddddd') + feed('<Esc>u') + expect('') + end) + describe('stream: Normal mode', function() + describe('on empty line', function() + before_each(function() + -- If nvim_paste() calls :undojoin without making any changes, this makes it an error. + feed('afoo<Esc>u') + end) + after_each(function() + feed('u') + expect('') + end) + it('pasting one line', function() + nvim('paste', 'aaaaaa', false, 1) + nvim('paste', 'bbbbbb', false, 2) + nvim('paste', 'cccccc', false, 2) + nvim('paste', 'dddddd', false, 3) + expect('aaaaaabbbbbbccccccdddddd') + end) + it('pasting multiple lines', function() + nvim('paste', 'aaaaaa\n', false, 1) + nvim('paste', 'bbbbbb\n', false, 2) + nvim('paste', 'cccccc\n', false, 2) + nvim('paste', 'dddddd', false, 3) + expect([[ + aaaaaa + bbbbbb + cccccc + dddddd]]) + end) + end) + describe('not at the end of a line', function() + before_each(function() + feed('i||<Esc>') + -- If nvim_paste() calls :undojoin without making any changes, this makes it an error. + feed('afoo<Esc>u') + feed('0') + end) + after_each(function() + feed('u') + expect('||') + end) + it('pasting one line', function() + nvim('paste', 'aaaaaa', false, 1) + nvim('paste', 'bbbbbb', false, 2) + nvim('paste', 'cccccc', false, 2) + nvim('paste', 'dddddd', false, 3) + expect('|aaaaaabbbbbbccccccdddddd|') + end) + it('pasting multiple lines', function() + nvim('paste', 'aaaaaa\n', false, 1) + nvim('paste', 'bbbbbb\n', false, 2) + nvim('paste', 'cccccc\n', false, 2) + nvim('paste', 'dddddd', false, 3) + expect([[ + |aaaaaa + bbbbbb + cccccc + dddddd|]]) + end) + end) + describe('at the end of a line', function() + before_each(function() + feed('i||<Esc>') + -- If nvim_paste() calls :undojoin without making any changes, this makes it an error. + feed('afoo<Esc>u') + feed('2|') + end) + after_each(function() + feed('u') + expect('||') + end) + it('pasting one line', function() + nvim('paste', 'aaaaaa', false, 1) + nvim('paste', 'bbbbbb', false, 2) + nvim('paste', 'cccccc', false, 2) + nvim('paste', 'dddddd', false, 3) + expect('||aaaaaabbbbbbccccccdddddd') + end) + it('pasting multiple lines', function() + nvim('paste', 'aaaaaa\n', false, 1) + nvim('paste', 'bbbbbb\n', false, 2) + nvim('paste', 'cccccc\n', false, 2) + nvim('paste', 'dddddd', false, 3) + expect([[ + ||aaaaaa + bbbbbb + cccccc + dddddd]]) + end) + end) + end) + describe('stream: Visual mode', function() + describe('neither end at the end of a line', function() + before_each(function() + feed('i|xxx<CR>xxx|<Esc>') + -- If nvim_paste() calls :undojoin without making any changes, this makes it an error. + feed('afoo<Esc>u') + feed('3|vhk') + end) + after_each(function() + feed('u') + expect([[ + |xxx + xxx|]]) + end) + it('with non-empty chunks', function() + nvim('paste', 'aaaaaa', false, 1) + nvim('paste', 'bbbbbb', false, 2) + nvim('paste', 'cccccc', false, 2) + nvim('paste', 'dddddd', false, 3) + expect('|aaaaaabbbbbbccccccdddddd|') + end) + it('with empty first chunk', function() + nvim('paste', '', false, 1) + nvim('paste', 'bbbbbb', false, 2) + nvim('paste', 'cccccc', false, 2) + nvim('paste', 'dddddd', false, 3) + expect('|bbbbbbccccccdddddd|') + end) + it('with all chunks empty', function() + nvim('paste', '', false, 1) + nvim('paste', '', false, 2) + nvim('paste', '', false, 2) + nvim('paste', '', false, 3) + expect('||') + end) + end) + describe('cursor at the end of a line', function() + before_each(function() + feed('i||xxx<CR>xxx<Esc>') + -- If nvim_paste() calls :undojoin without making any changes, this makes it an error. + feed('afoo<Esc>u') + feed('3|vko') + end) + after_each(function() + feed('u') + expect([[ + ||xxx + xxx]]) + end) + it('with non-empty chunks', function() + nvim('paste', 'aaaaaa', false, 1) + nvim('paste', 'bbbbbb', false, 2) + nvim('paste', 'cccccc', false, 2) + nvim('paste', 'dddddd', false, 3) + expect('||aaaaaabbbbbbccccccdddddd') + end) + it('with empty first chunk', function() + nvim('paste', '', false, 1) + nvim('paste', 'bbbbbb', false, 2) + nvim('paste', 'cccccc', false, 2) + nvim('paste', 'dddddd', false, 3) + expect('||bbbbbbccccccdddddd') + end) + end) + describe('other end at the end of a line', function() + before_each(function() + feed('i||xxx<CR>xxx<Esc>') + -- If nvim_paste() calls :undojoin without making any changes, this makes it an error. + feed('afoo<Esc>u') + feed('3|vk') + end) + after_each(function() + feed('u') + expect([[ + ||xxx + xxx]]) + end) + it('with non-empty chunks', function() + nvim('paste', 'aaaaaa', false, 1) + nvim('paste', 'bbbbbb', false, 2) + nvim('paste', 'cccccc', false, 2) + nvim('paste', 'dddddd', false, 3) + expect('||aaaaaabbbbbbccccccdddddd') + end) + it('with empty first chunk', function() + nvim('paste', '', false, 1) + nvim('paste', 'bbbbbb', false, 2) + nvim('paste', 'cccccc', false, 2) + nvim('paste', 'dddddd', false, 3) + expect('||bbbbbbccccccdddddd') + end) + end) + end) + describe('stream: linewise Visual mode', function() + before_each(function() + feed('i123456789<CR>987654321<CR>123456789<Esc>') + -- If nvim_paste() calls :undojoin without making any changes, this makes it an error. + feed('afoo<Esc>u') + end) + after_each(function() + feed('u') + expect([[ + 123456789 + 987654321 + 123456789]]) + end) + describe('selecting the start of a file', function() + before_each(function() + feed('ggV') + end) + it('pasting text without final new line', function() + nvim('paste', 'aaaaaa\n', false, 1) + nvim('paste', 'bbbbbb\n', false, 2) + nvim('paste', 'cccccc\n', false, 2) + nvim('paste', 'dddddd', false, 3) + expect([[ + aaaaaa + bbbbbb + cccccc + dddddd987654321 + 123456789]]) + end) + it('pasting text with final new line', function() + nvim('paste', 'aaaaaa\n', false, 1) + nvim('paste', 'bbbbbb\n', false, 2) + nvim('paste', 'cccccc\n', false, 2) + nvim('paste', 'dddddd\n', false, 3) + expect([[ + aaaaaa + bbbbbb + cccccc + dddddd + 987654321 + 123456789]]) + end) + end) + describe('selecting the middle of a file', function() + before_each(function() + feed('2ggV') + end) + it('pasting text without final new line', function() + nvim('paste', 'aaaaaa\n', false, 1) + nvim('paste', 'bbbbbb\n', false, 2) + nvim('paste', 'cccccc\n', false, 2) + nvim('paste', 'dddddd', false, 3) + expect([[ + 123456789 + aaaaaa + bbbbbb + cccccc + dddddd123456789]]) + end) + it('pasting text with final new line', function() + nvim('paste', 'aaaaaa\n', false, 1) + nvim('paste', 'bbbbbb\n', false, 2) + nvim('paste', 'cccccc\n', false, 2) + nvim('paste', 'dddddd\n', false, 3) + expect([[ + 123456789 + aaaaaa + bbbbbb + cccccc + dddddd + 123456789]]) + end) + end) + describe('selecting the end of a file', function() + before_each(function() + feed('3ggV') + end) + it('pasting text without final new line', function() + nvim('paste', 'aaaaaa\n', false, 1) + nvim('paste', 'bbbbbb\n', false, 2) + nvim('paste', 'cccccc\n', false, 2) + nvim('paste', 'dddddd', false, 3) + expect([[ + 123456789 + 987654321 + aaaaaa + bbbbbb + cccccc + dddddd]]) + end) + it('pasting text with final new line', function() + nvim('paste', 'aaaaaa\n', false, 1) + nvim('paste', 'bbbbbb\n', false, 2) + nvim('paste', 'cccccc\n', false, 2) + nvim('paste', 'dddddd\n', false, 3) + expect([[ + 123456789 + 987654321 + aaaaaa + bbbbbb + cccccc + dddddd + ]]) + end) + end) + describe('selecting the whole file', function() + before_each(function() + feed('ggVG') + end) + it('pasting text without final new line', function() + nvim('paste', 'aaaaaa\n', false, 1) + nvim('paste', 'bbbbbb\n', false, 2) + nvim('paste', 'cccccc\n', false, 2) + nvim('paste', 'dddddd', false, 3) + expect([[ + aaaaaa + bbbbbb + cccccc + dddddd]]) + end) + it('pasting text with final new line', function() + nvim('paste', 'aaaaaa\n', false, 1) + nvim('paste', 'bbbbbb\n', false, 2) + nvim('paste', 'cccccc\n', false, 2) + nvim('paste', 'dddddd\n', false, 3) + expect([[ + aaaaaa + bbbbbb + cccccc + dddddd + ]]) + end) + end) + end) + end + describe('without virtualedit,', function() + run_streamed_paste_tests() + end) + describe('with virtualedit=onemore,', function() + before_each(function() + command('set virtualedit=onemore') + end) + run_streamed_paste_tests() end) it('non-streaming', function() -- With final "\n". @@ -711,6 +1079,37 @@ describe('API', function() eeffgghh iijjkkll]]) end) + it('when searching in Visual mode', function() + feed('v/') + nvim('paste', 'aabbccdd', true, -1) + eq('aabbccdd', funcs.getcmdline()) + expect('') + end) + it('pasting with empty last chunk in Cmdline mode', function() + local screen = Screen.new(20, 4) + screen:attach() + feed(':') + nvim('paste', 'Foo', true, 1) + nvim('paste', '', true, 3) + screen:expect([[ + | + ~ | + ~ | + :Foo^ | + ]]) + end) + it('pasting text with control characters in Cmdline mode', function() + local screen = Screen.new(20, 4) + screen:attach() + feed(':') + nvim('paste', 'normal! \023\022\006\027', true, -1) + screen:expect([[ + | + ~ | + ~ | + :normal! ^W^V^F^[^ | + ]]) + end) it('crlf=false does not break lines at CR, CRLF', function() nvim('paste', 'line 1\r\n\r\rline 2\nline 3\rline 4\r', false, -1) expect('line 1\r\n\r\rline 2\nline 3\rline 4\r') @@ -872,6 +1271,30 @@ 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)) + + exec([[ + function Test() + endfunction + function s:Test() + endfunction + let g:Unknown_func = function('Test') + let g:Unknown_script_func = function('s:Test') + ]]) + eq(NIL, meths.get_var('Unknown_func')) + eq(NIL, meths.get_var('Unknown_script_func')) + + -- 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() @@ -949,6 +1372,67 @@ describe('API', function() end) end) + describe('nvim_get_option_value, nvim_set_option_value', function() + it('works', function() + ok(nvim('get_option_value', 'equalalways', {})) + nvim('set_option_value', 'equalalways', false, {}) + ok(not nvim('get_option_value', 'equalalways', {})) + end) + + it('can get local values when global value is set', function() + eq(0, nvim('get_option_value', 'scrolloff', {})) + eq(-1, nvim('get_option_value', 'scrolloff', {scope = 'local'})) + end) + + it('can set global and local values', function() + nvim('set_option_value', 'makeprg', 'hello', {}) + eq('hello', nvim('get_option_value', 'makeprg', {})) + eq('', nvim('get_option_value', 'makeprg', {scope = 'local'})) + nvim('set_option_value', 'makeprg', 'world', {scope = 'local'}) + eq('world', nvim('get_option_value', 'makeprg', {scope = 'local'})) + nvim('set_option_value', 'makeprg', 'goodbye', {scope = 'global'}) + eq('goodbye', nvim('get_option_value', 'makeprg', {scope = 'global'})) + nvim('set_option_value', 'makeprg', 'hello', {}) + eq('hello', nvim('get_option_value', 'makeprg', {scope = 'global'})) + eq('hello', nvim('get_option_value', 'makeprg', {})) + eq('', nvim('get_option_value', 'makeprg', {scope = 'local'})) + end) + + it('clears the local value of an option with nil', function() + -- Set global value + nvim('set_option_value', 'shiftwidth', 42, {}) + eq(42, nvim('get_option_value', 'shiftwidth', {})) + + -- Set local value + nvim('set_option_value', 'shiftwidth', 8, {scope = 'local'}) + eq(8, nvim('get_option_value', 'shiftwidth', {})) + eq(8, nvim('get_option_value', 'shiftwidth', {scope = 'local'})) + eq(42, nvim('get_option_value', 'shiftwidth', {scope = 'global'})) + + -- Clear value without scope + nvim('set_option_value', 'shiftwidth', NIL, {}) + eq(42, nvim('get_option_value', 'shiftwidth', {})) + eq(42, nvim('get_option_value', 'shiftwidth', {scope = 'local'})) + + -- Clear value with explicit scope + nvim('set_option_value', 'shiftwidth', 8, {scope = 'local'}) + nvim('set_option_value', 'shiftwidth', NIL, {scope = 'local'}) + eq(42, nvim('get_option_value', 'shiftwidth', {})) + eq(42, nvim('get_option_value', 'shiftwidth', {scope = 'local'})) + + -- Now try with options with a special "local is unset" value (e.g. 'undolevels') + nvim('set_option_value', 'undolevels', 1000, {}) + eq(1000, nvim('get_option_value', 'undolevels', {scope = 'local'})) + nvim('set_option_value', 'undolevels', NIL, {scope = 'local'}) + eq(-123456, nvim('get_option_value', 'undolevels', {scope = 'local'})) + + nvim('set_option_value', 'autoread', true, {}) + eq(true, nvim('get_option_value', 'autoread', {scope = 'local'})) + nvim('set_option_value', 'autoread', NIL, {scope = 'local'}) + eq(NIL, nvim('get_option_value', 'autoread', {scope = 'local'})) + end) + end) + describe('nvim_{get,set}_current_buf, nvim_list_bufs', function() it('works', function() eq(1, #nvim('list_bufs')) @@ -1032,7 +1516,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'") @@ -1056,6 +1553,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'") @@ -1091,10 +1589,22 @@ describe('API', function() feed(':digraphs<cr>') eq({mode='rm', blocking=true}, nvim("get_mode")) end) + + it('after <Nop> mapping returns blocking=false #17257', function() + command('nnoremap <F2> <Nop>') + feed('<F2>') + eq({mode='n', blocking=false}, nvim("get_mode")) + end) + + it('after empty string <expr> mapping returns blocking=false #17257', function() + command('nnoremap <expr> <F2> ""') + feed('<F2>') + eq({mode='n', blocking=false}, nvim("get_mode")) + end) end) - describe('RPC (K_EVENT) #6166', function() - it('does not complete ("interrupt") normal-mode operator-pending', function() + describe('RPC (K_EVENT)', function() + it('does not complete ("interrupt") normal-mode operator-pending #6166', function() helpers.insert([[ FIRST LINE SECOND LINE]]) @@ -1130,7 +1640,7 @@ describe('API', function() ]]) end) - it('does not complete ("interrupt") normal-mode map-pending', function() + it('does not complete ("interrupt") normal-mode map-pending #6166', function() command("nnoremap dd :let g:foo='it worked...'<CR>") helpers.insert([[ FIRST LINE @@ -1146,7 +1656,8 @@ describe('API', function() SECOND LINE]]) eq('it worked...', helpers.eval('g:foo')) end) - it('does not complete ("interrupt") insert-mode map-pending', function() + + it('does not complete ("interrupt") insert-mode map-pending #6166', function() command('inoremap xx foo') command('set timeoutlen=9999') helpers.insert([[ @@ -1161,6 +1672,37 @@ describe('API', function() FIRST LINE SECOND LINfooE]]) end) + + it('does not interrupt Insert mode i_CTRL-O #10035', function() + feed('iHello World<c-o>') + eq({mode='niI', blocking=false}, meths.get_mode()) -- fast event + eq(2, eval('1+1')) -- causes K_EVENT key + eq({mode='niI', blocking=false}, meths.get_mode()) -- still in ctrl-o mode + feed('dd') + eq({mode='i', blocking=false}, meths.get_mode()) -- left ctrl-o mode + expect('') -- executed the command + end) + + it('does not interrupt Select mode v_CTRL-O #15688', function() + feed('iHello World<esc>gh<c-o>') + eq({mode='vs', blocking=false}, meths.get_mode()) -- fast event + eq({mode='vs', blocking=false}, meths.get_mode()) -- again #15288 + eq(2, eval('1+1')) -- causes K_EVENT key + eq({mode='vs', blocking=false}, meths.get_mode()) -- still in ctrl-o mode + feed('^') + eq({mode='s', blocking=false}, meths.get_mode()) -- left ctrl-o mode + feed('h') + eq({mode='i', blocking=false}, meths.get_mode()) -- entered insert mode + expect('h') -- selection is the whole line and is replaced + end) + + it('does not interrupt Insert mode i_0_CTRL-D #13997', function() + command('set timeoutlen=9999') + feed('i<Tab><Tab>a0') + eq(2, eval('1+1')) -- causes K_EVENT key + feed('<C-D>') + expect('a') -- recognized i_0_CTRL-D + end) end) describe('nvim_get_context', function() @@ -1274,18 +1816,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 @@ -2094,6 +2636,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() @@ -2458,6 +3008,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('treats double-width fillchar as single-width', function() + eq({ str = 'a哦哦哦b', width = 5 }, + meths.eval_statusline('a%=b', { fillchar = '哦', maxwidth = 5 })) + end) + it('treats control character fillchar as single-width', function() + eq({ str = 'a\031\031\031b', width = 5 }, + meths.eval_statusline('a%=b', { fillchar = '\031', maxwidth = 5 })) + end) + it('rejects multiple-character fillchar', function() + eq('fillchar must be a single character', + pcall_err(meths.eval_statusline, '', { fillchar = 'aa' })) + end) + it('rejects empty string fillchar', function() + eq('fillchar must be a single character', + pcall_err(meths.eval_statusline, '', { fillchar = '' })) + end) + it('rejects non-string fillchar', function() + eq('fillchar must be a single 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 11755a9d97..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() @@ -222,9 +262,9 @@ describe('API/win', function() eq('', nvim('get_option', 'statusline')) command("set modified") command("enew") -- global-local: not preserved in new buffer - eq("Failed to get value for option 'statusline'", - pcall_err(curwin, 'get_option', 'statusline')) - eq('', eval('&l:statusline')) -- confirm local value was not copied + -- confirm local value was not copied + eq('', curwin('get_option', 'statusline')) + eq('', eval('&l:statusline')) end) it('after switching windows #15390', function() @@ -238,6 +278,10 @@ describe('API/win', function() eq('window-status', window('get_option', win1, 'statusline')) assert_alive() end) + + it('returns values for unset local options', function() + eq(-1, curwin('get_option', 'scrolloff')) + end) end) describe('get_position', 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..6111654b5e 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,7 +15,9 @@ 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 retry = helpers.retry local source = helpers.source describe('autocmd', function() @@ -333,6 +337,87 @@ 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) + + describe('closing last non-floating window in tab from `aucmd_win`', function() + before_each(function() + command('edit Xa.txt') + command('tabnew Xb.txt') + command('autocmd BufAdd Xa.txt 1close') + end) + + it('gives E814 when there are no other floating windows', function() + eq('Vim(close):E814: Cannot close window, only autocmd window would remain', + pcall_err(command, 'doautoall BufAdd')) + end) + + it('gives E814 when there are other floating windows', function() + meths.open_win(0, true, {width = 10, height = 10, relative = 'editor', row = 10, col = 10}) + eq('Vim(close):E814: Cannot close window, only autocmd window would remain', + pcall_err(command, 'doautoall BufAdd')) + end) + end) + it(':doautocmd does not warn "No matching autocommands" #10689', function() local screen = Screen.new(32, 3) screen:attach() @@ -354,4 +439,137 @@ describe('autocmd', function() :doautocmd SessionLoadPost | ]]} end) + + describe('v:event is readonly #18063', function() + it('during ChanOpen event', function() + command('autocmd ChanOpen * let v:event.info.id = 0') + funcs.jobstart({'cat'}) + retry(nil, nil, function() + eq('E46: Cannot change read-only variable "v:event.info"', meths.get_vvar('errmsg')) + end) + end) + + it('during ChanOpen event', function() + command('autocmd ChanInfo * let v:event.info.id = 0') + meths.set_client_info('foo', {}, 'remote', {}, {}) + retry(nil, nil, function() + eq('E46: Cannot change read-only variable "v:event.info"', meths.get_vvar('errmsg')) + end) + end) + + it('during RecordingLeave event', function() + command([[autocmd RecordingLeave * let v:event.regname = '']]) + eq('Vim(let):E46: Cannot change read-only variable "v:event.regname"', + pcall_err(command, 'normal! qqq')) + end) + + it('during TermClose event', function() + command('autocmd TermClose * let v:event.status = 0') + command('terminal') + eq('Vim(let):E46: Cannot change read-only variable "v:event.status"', + pcall_err(command, 'bdelete!')) + end) + 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 d0f46e689b..85d8628d7e 100644 --- a/test/functional/autocmd/cursormoved_spec.lua +++ b/test/functional/autocmd/cursormoved_spec.lua @@ -31,4 +31,13 @@ describe('CursorMoved', function() eq({'aaa'}, funcs.nvim_buf_get_lines(eval('g:buf'), 0, -1, true)) eq(0, eval('g:cursormoved')) end) + + 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')) + end) end) 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/modechanged_spec.lua b/test/functional/autocmd/modechanged_spec.lua new file mode 100644 index 0000000000..be5a291ac9 --- /dev/null +++ b/test/functional/autocmd/modechanged_spec.lua @@ -0,0 +1,31 @@ +local helpers = require('test.functional.helpers')(after_each) +local clear, eval, eq = helpers.clear, helpers.eval, helpers.eq +local feed, command = helpers.feed, helpers.command + +describe('ModeChanged', function() + before_each(function() + clear() + command('let g:count = 0') + command('au ModeChanged * let g:event = copy(v:event)') + command('au ModeChanged * let g:count += 1') + end) + + it('picks up terminal mode changes', function() + command("term") + feed('i') + eq({ + old_mode = 'nt', + new_mode = 't' + }, eval('g:event')) + feed('<c-\\><c-n>') + eq({ + old_mode = 't', + new_mode = 'nt' + }, eval('g:event')) + eq(3, eval('g:count')) + command("bd!") + + -- v:event is cleared after the autocommand is done + eq({}, eval('v:event')) + end) +end) diff --git a/test/functional/autocmd/recording_spec.lua b/test/functional/autocmd/recording_spec.lua new file mode 100644 index 0000000000..b9aec774f1 --- /dev/null +++ b/test/functional/autocmd/recording_spec.lua @@ -0,0 +1,72 @@ +local helpers = require('test.functional.helpers')(after_each) + +local clear = helpers.clear +local eq = helpers.eq +local eval = helpers.eval +local source_vim = helpers.source + +describe('RecordingEnter', function() + before_each(clear) + it('works', function() + source_vim [[ + let g:recorded = 0 + autocmd RecordingEnter * let g:recorded += 1 + call feedkeys("qqyyq", 'xt') + ]] + eq(1, eval('g:recorded')) + end) + + it('gives a correct reg_recording()', function() + source_vim [[ + let g:recording = '' + autocmd RecordingEnter * let g:recording = reg_recording() + call feedkeys("qqyyq", 'xt') + ]] + eq('q', eval('g:recording')) + end) +end) + +describe('RecordingLeave', function() + before_each(clear) + it('works', function() + source_vim [[ + let g:recorded = 0 + autocmd RecordingLeave * let g:recorded += 1 + call feedkeys("qqyyq", 'xt') + ]] + eq(1, eval('g:recorded')) + end) + + it('gives the correct reg_recorded()', function() + source_vim [[ + let g:recorded = 'a' + let g:recording = '' + autocmd RecordingLeave * let g:recording = reg_recording() + autocmd RecordingLeave * let g:recorded = reg_recorded() + 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/searchwrapped_spec.lua b/test/functional/autocmd/searchwrapped_spec.lua new file mode 100644 index 0000000000..46c2c99b3d --- /dev/null +++ b/test/functional/autocmd/searchwrapped_spec.lua @@ -0,0 +1,53 @@ +local helpers = require('test.functional.helpers')(after_each) + +local clear = helpers.clear +local command = helpers.command +local curbufmeths = helpers.curbufmeths +local eq = helpers.eq +local eval = helpers.eval +local feed = helpers.feed + +describe('autocmd SearchWrapped', function() + before_each(function() + clear() + command('set ignorecase') + command('let g:test = 0') + command('autocmd! SearchWrapped * let g:test += 1') + curbufmeths.set_lines(0, 1, false, { + 'The quick brown fox', + 'jumps over the lazy dog'}) + end) + + it('gets triggered when search wraps the end', function() + feed('/the<Return>') + eq(0, eval('g:test')) + + feed('n') + eq(1, eval('g:test')) + + feed('nn') + eq(2, eval('g:test')) + end) + + it('gets triggered when search wraps in reverse order', function() + feed('/the<Return>') + eq(0, eval('g:test')) + + feed('NN') + eq(1, eval('g:test')) + + feed('NN') + eq(2, eval('g:test')) + end) + + it('does not get triggered on failed searches', function() + feed('/blargh<Return>') + eq(0, eval('g:test')) + + feed('NN') + eq(0, eval('g:test')) + + feed('NN') + eq(0, eval('g:test')) + end) +end) diff --git a/test/functional/autocmd/show_spec.lua b/test/functional/autocmd/show_spec.lua new file mode 100644 index 0000000000..505bed834b --- /dev/null +++ b/test/functional/autocmd/show_spec.lua @@ -0,0 +1,183 @@ +local helpers = require('test.functional.helpers')(after_each) +local Screen = require('test.functional.ui.screen') + +local clear = helpers.clear +local command = helpers.command +local dedent = helpers.dedent +local eq = helpers.eq +local funcs = helpers.funcs +local eval = helpers.eval +local exec = helpers.exec +local feed = helpers.feed + +describe(":autocmd", function() + before_each(function() + clear({'-u', 'NONE'}) + end) + + 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) + + it('should not show group information if interrupted', function() + local screen = Screen.new(50, 6) + screen:set_default_attr_ids({ + [1] = {bold = true, foreground = Screen.colors.Blue1}, -- NonText + [2] = {bold = true, foreground = Screen.colors.SeaGreen}, -- MoreMsg + [3] = {bold = true, foreground = Screen.colors.Magenta}, -- Title + }) + screen:attach() + exec([[ + set more + autocmd! BufEnter + augroup test_1 + autocmd BufEnter A echo 'A' + autocmd BufEnter B echo 'B' + autocmd BufEnter C echo 'C' + autocmd BufEnter D echo 'D' + autocmd BufEnter E echo 'E' + autocmd BufEnter F echo 'F' + augroup END + autocmd! BufLeave + augroup test_1 + autocmd BufLeave A echo 'A' + autocmd BufLeave B echo 'B' + autocmd BufLeave C echo 'C' + autocmd BufLeave D echo 'D' + autocmd BufLeave E echo 'E' + autocmd BufLeave F echo 'F' + augroup END + ]]) + feed(':autocmd<CR>') + screen:expect([[ + :autocmd | + {3:--- Autocommands ---} | + {3:test_1} {3:BufEnter} | + A echo 'A' | + B echo 'B' | + {2:-- More --}^ | + ]]) + feed('q') + screen:expect([[ + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]) + end) + + it('should not show group information for deleted pattern', function() + exec([[ + autocmd! BufEnter + augroup test_1 + autocmd BufEnter A echo 'A' + autocmd BufEnter B echo 'B' + autocmd BufEnter C echo 'C' + augroup END + augroup test_2 + autocmd BufEnter foo echo 'foo' + augroup END + augroup test_3 + autocmd BufEnter D echo 'D' + autocmd BufEnter E echo 'E' + autocmd BufEnter F echo 'F' + augroup END + + func Func() + autocmd! test_2 BufEnter + let g:output = execute('autocmd BufEnter') + endfunc + + autocmd User foo call Func() + doautocmd User foo + ]]) + eq(dedent([[ + + --- Autocommands --- + test_1 BufEnter + A echo 'A' + B echo 'B' + C echo 'C' + test_3 BufEnter + D echo 'D' + E echo 'E' + F echo 'F']]), eval('g:output')) + end) + + it('can filter by pattern #17973', function() + exec([[ + autocmd! BufEnter + autocmd! User + augroup test_1 + autocmd BufEnter A echo "A1" + autocmd BufEnter B echo "B1" + autocmd User A echo "A1" + autocmd User B echo "B1" + augroup END + augroup test_2 + autocmd BufEnter A echo "A2" + autocmd BufEnter B echo "B2" + autocmd User A echo "A2" + autocmd User B echo "B2" + augroup END + augroup test_3 + autocmd BufEnter A echo "A3" + autocmd BufEnter B echo "B3" + autocmd User A echo "A3" + autocmd User B echo "B3" + augroup END + ]]) + eq(dedent([[ + + --- Autocommands --- + test_1 User + A echo "A1" + test_2 User + A echo "A2" + test_3 User + A echo "A3"]]), funcs.execute('autocmd User A')) + eq(dedent([[ + + --- Autocommands --- + test_1 BufEnter + B echo "B1" + test_2 BufEnter + B echo "B2" + test_3 BufEnter + B echo "B3" + test_1 User + B echo "B1" + test_2 User + B echo "B2" + test_3 User + B echo "B3"]]), funcs.execute('autocmd * B')) + eq(dedent([[ + + --- Autocommands --- + test_3 BufEnter + B echo "B3" + test_3 User + B echo "B3"]]), funcs.execute('autocmd test_3 * B')) + end) +end) diff --git a/test/functional/autocmd/signal_spec.lua b/test/functional/autocmd/signal_spec.lua index 719adeaf1b..d4f65cc61d 100644 --- a/test/functional/autocmd/signal_spec.lua +++ b/test/functional/autocmd/signal_spec.lua @@ -30,6 +30,12 @@ describe('autocmd Signal', function() eq({'notification', 'foo', {}}, next_msg()) end) + it('matches SIGWINCH', function() + command('autocmd Signal SIGWINCH call rpcnotify(1, "foo")') + posix_kill('WINCH', funcs.getpid()) + eq({'notification', 'foo', {}}, next_msg()) + end) + it('does not match unknown patterns', function() command('autocmd Signal SIGUSR2 call rpcnotify(1, "foo")') posix_kill('USR1', funcs.getpid()) diff --git a/test/functional/autocmd/winscrolled_spec.lua b/test/functional/autocmd/winscrolled_spec.lua index 1ef5a37479..5c1b758961 100644 --- a/test/functional/autocmd/winscrolled_spec.lua +++ b/test/functional/autocmd/winscrolled_spec.lua @@ -3,60 +3,83 @@ local helpers = require('test.functional.helpers')(after_each) local clear = helpers.clear local eq = helpers.eq local eval = helpers.eval -local source = helpers.source +local command = helpers.command +local feed = helpers.feed +local meths = helpers.meths +local assert_alive = helpers.assert_alive + +before_each(clear) describe('WinScrolled', function() - before_each(clear) + local win_id + + before_each(function() + win_id = meths.get_current_win().id + command(string.format('autocmd WinScrolled %d let g:matched = v:true', win_id)) + command('let g:scrolled = 0') + command('autocmd WinScrolled * let g:scrolled += 1') + command([[autocmd WinScrolled * let g:amatch = str2nr(expand('<amatch>'))]]) + command([[autocmd WinScrolled * let g:afile = str2nr(expand('<afile>'))]]) + end) + + after_each(function() + eq(true, eval('g:matched')) + eq(win_id, eval('g:amatch')) + eq(win_id, eval('g:afile')) + end) it('is triggered by scrolling vertically', function() - source([[ - set nowrap - let width = winwidth(0) - let line = '123' . repeat('*', width * 2) - let lines = [line, line] - call nvim_buf_set_lines(0, 0, -1, v:true, lines) - - let g:scrolled = 0 - autocmd WinScrolled * let g:scrolled += 1 - execute "normal! \<C-e>" - ]]) + local lines = {'123', '123'} + meths.buf_set_lines(0, 0, -1, true, lines) + eq(0, eval('g:scrolled')) + feed('<C-E>') eq(1, eval('g:scrolled')) end) it('is triggered by scrolling horizontally', function() - source([[ - set nowrap - let width = winwidth(0) - let line = '123' . repeat('*', width * 2) - let lines = [line, line] - call nvim_buf_set_lines(0, 0, -1, v:true, lines) - - let g:scrolled = 0 - autocmd WinScrolled * let g:scrolled += 1 - execute "normal! zl" - ]]) + command('set nowrap') + local width = meths.win_get_width(0) + local line = '123' .. ('*'):rep(width * 2) + local lines = {line, line} + meths.buf_set_lines(0, 0, -1, true, lines) + eq(0, eval('g:scrolled')) + feed('zl') eq(1, eval('g:scrolled')) end) - it('is triggered when the window scrolls in insert mode', function() - source([[ - let height = winheight(0) - let lines = map(range(height * 2), {_, i -> string(i)}) - call nvim_buf_set_lines(0, 0, -1, v:true, lines) - - let g:scrolled = 0 - autocmd WinScrolled * let g:scrolled += 1 - call feedkeys("LA\<CR><Esc>", "n") - ]]) + it('is triggered by horizontal scrolling from cursor move', function() + command('set nowrap') + local lines = {'', '', 'Foo'} + meths.buf_set_lines(0, 0, -1, true, lines) + meths.win_set_cursor(0, {3, 0}) + eq(0, eval('g:scrolled')) + feed('zl') + eq(1, eval('g:scrolled')) + feed('zl') eq(2, eval('g:scrolled')) + feed('h') + eq(3, eval('g:scrolled')) end) - it('is triggered when the window is resized', function() - source([[ - let g:scrolled = 0 - autocmd WinScrolled * let g:scrolled += 1 - wincmd v - ]]) + it('is triggered when the window scrolls in Insert mode', function() + local height = meths.win_get_height(0) + local lines = {} + for i = 1, height * 2 do + lines[i] = tostring(i) + end + meths.buf_set_lines(0, 0, -1, true, lines) + feed('L') + eq(0, eval('g:scrolled')) + feed('A<CR><Esc>') eq(1, eval('g:scrolled')) end) end) + +it('closing window in WinScrolled does not cause use-after-free #13265', function() + local lines = {'aaa', 'bbb'} + meths.buf_set_lines(0, 0, -1, true, lines) + command('vsplit') + command('autocmd WinScrolled * close') + feed('<C-E>') + assert_alive() +end) diff --git a/test/functional/core/channels_spec.lua b/test/functional/core/channels_spec.lua index 93dec9fb35..ca52404d3b 100644 --- a/test/functional/core/channels_spec.lua +++ b/test/functional/core/channels_spec.lua @@ -10,6 +10,8 @@ local nvim_prog = helpers.nvim_prog local is_os = helpers.is_os local retry = helpers.retry local expect_twostreams = helpers.expect_twostreams +local assert_alive = helpers.assert_alive +local pcall_err = helpers.pcall_err describe('channels', function() local init = [[ @@ -100,6 +102,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} @@ -282,3 +316,22 @@ describe('channels', function() eq({"notification", "exit", {id, 1, {''}}}, next_msg()) end) end) + +describe('loopback', function() + before_each(function() + clear() + command("let chan = sockconnect('pipe', v:servername, {'rpc': v:true})") + end) + + it('does not crash when sending raw data', function() + eq("Vim(call):Can't send raw data to rpc channel", + pcall_err(command, "call chansend(chan, 'test')")) + assert_alive() + end) + + it('are released when closed', function() + local chans = eval('len(nvim_list_chans())') + command('call chanclose(chan)') + eq(chans - 1, eval('len(nvim_list_chans())')) + end) +end) diff --git a/test/functional/core/fileio_spec.lua b/test/functional/core/fileio_spec.lua index f4c476560d..c68bc18eed 100644 --- a/test/functional/core/fileio_spec.lua +++ b/test/functional/core/fileio_spec.lua @@ -15,6 +15,7 @@ local read_file = helpers.read_file local trim = helpers.trim local currentdir = helpers.funcs.getcwd local iswin = helpers.iswin +local assert_alive = helpers.assert_alive describe('fileio', function() before_each(function() @@ -26,6 +27,7 @@ describe('fileio', function() os.remove('Xtest_startup_file1~') os.remove('Xtest_startup_file2') os.remove('Xtest_тест.md') + os.remove('Xtest-u8-int-max') rmdir('Xtest_startup_swapdir') rmdir('Xtest_backupdir') end) @@ -128,5 +130,12 @@ describe('fileio', function() table.insert(text, '') eq(text, funcs.readfile(fname, 'b')) end) + it('read invalid u8 over INT_MAX doesn\'t segfault', function() + clear() + command('call writefile(0zFFFFFFFF, "Xtest-u8-int-max")') + -- This should not segfault + command('edit ++enc=utf32 Xtest-u8-int-max') + assert_alive() + end) end) 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/remote_spec.lua b/test/functional/core/remote_spec.lua new file mode 100644 index 0000000000..602a5a71eb --- /dev/null +++ b/test/functional/core/remote_spec.lua @@ -0,0 +1,142 @@ +local helpers = require('test.functional.helpers')(after_each) + +local clear = helpers.clear +local command = helpers.command +local eq = helpers.eq +local expect = helpers.expect +local funcs = helpers.funcs +local insert = helpers.insert +local meths = helpers.meths +local new_argv = helpers.new_argv +local neq = helpers.neq +local set_session = helpers.set_session +local spawn = helpers.spawn +local tmpname = helpers.tmpname +local write_file = helpers.write_file + +describe('Remote', function() + local fname, other_fname + local contents = 'The call is coming from outside the process' + local other_contents = "A second file's contents" + + before_each(function() + fname = tmpname() .. ' with spaces in the filename' + other_fname = tmpname() + write_file(fname, contents) + write_file(other_fname, other_contents) + end) + + describe('connect to server and', function() + local server + before_each(function() + server = spawn(new_argv(), true) + set_session(server) + end) + + after_each(function() + server:close() + end) + + local function run_remote(...) + set_session(server) + local addr = funcs.serverlist()[1] + local client_argv = new_argv({args={'--server', addr, ...}}) + + -- Create an nvim instance just to run the remote-invoking nvim. We want + -- to wait for the remote instance to exit and calling jobwait blocks + -- the event loop. If the server event loop is blocked, it can't process + -- our incoming --remote calls. + local client_starter = spawn(new_argv(), false, nil, true) + set_session(client_starter) + local client_job_id = funcs.jobstart(client_argv) + eq({ 0 }, funcs.jobwait({client_job_id})) + client_starter:close() + set_session(server) + end + + it('edit a single file', function() + run_remote('--remote', fname) + expect(contents) + eq(2, #funcs.getbufinfo()) + end) + + it('tab edit a single file with a non-changed buffer', function() + run_remote('--remote-tab', fname) + expect(contents) + eq(1, #funcs.gettabinfo()) + end) + + it('tab edit a single file with a changed buffer', function() + insert('hello') + run_remote('--remote-tab', fname) + expect(contents) + eq(2, #funcs.gettabinfo()) + end) + + it('edit multiple files', function() + run_remote('--remote', fname, other_fname) + expect(contents) + command('next') + expect(other_contents) + eq(3, #funcs.getbufinfo()) + end) + + it('send keys', function() + run_remote('--remote-send', ':edit '..fname..'<CR><C-W>v') + expect(contents) + eq(2, #funcs.getwininfo()) + -- Only a single buffer as we're using edit and not drop like --remote does + eq(1, #funcs.getbufinfo()) + end) + + it('evaluate expressions', function() + run_remote('--remote-expr', 'setline(1, "Yo")') + expect('Yo') + end) + end) + + it('creates server if not found', function() + clear('--remote', fname) + expect(contents) + eq(1, #funcs.getbufinfo()) + -- Since we didn't pass silent, we should get a complaint + neq(nil, string.find(meths.exec('messages', true), 'E247')) + end) + + it('creates server if not found with tabs', function() + clear('--remote-tab-silent', fname, other_fname) + expect(contents) + eq(2, #funcs.gettabinfo()) + eq(2, #funcs.getbufinfo()) + -- We passed silent, so no message should be issued about the server not being found + eq(nil, string.find(meths.exec('messages', true), 'E247')) + end) + + describe('exits with error on', function() + local function run_and_check_exit_code(...) + local bogus_argv = new_argv(...) + + -- Create an nvim instance just to run the remote-invoking nvim. We want + -- to wait for the remote instance to exit and calling jobwait blocks + -- the event loop. If the server event loop is blocked, it can't process + -- our incoming --remote calls. + clear() + local bogus_job_id = funcs.jobstart(bogus_argv) + eq({2}, funcs.jobwait({bogus_job_id})) + end + it('bogus subcommand', function() + run_and_check_exit_code('--remote-bogus') + end) + + it('send without server', function() + run_and_check_exit_code('--remote-send', 'i') + end) + + it('expr without server', function() + run_and_check_exit_code('--remote-expr', 'setline(1, "Yo")') + end) + it('wait subcommand', function() + run_and_check_exit_code('--remote-wait', fname) + end) + end) +end) diff --git a/test/functional/core/startup_spec.lua b/test/functional/core/startup_spec.lua index d1dce0f8da..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() @@ -560,7 +581,7 @@ describe('user config init', function() it('loads default lua config, but shows an error', function() clear{ args_rm={'-u'}, env=xenv } - feed('<cr>') -- TODO check this, test execution is blocked without it + feed('<cr>') -- confirm "Conflicting config ..." message eq(1, eval('g:lua_rc')) matches('^E5422: Conflicting configs', meths.exec('messages', true)) end) @@ -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/completion_spec.lua b/test/functional/editor/completion_spec.lua index befad29922..e27da0947f 100644 --- a/test/functional/editor/completion_spec.lua +++ b/test/functional/editor/completion_spec.lua @@ -1193,4 +1193,64 @@ describe('completion', function() eq('foobar', eval('g:word')) feed('<esc>') end) + + it('is stopped by :stopinsert from timer #12976', function() + screen:try_resize(32,14) + command([[call setline(1, ['hello', 'hullo', 'heeee', ''])]]) + feed('Gah<c-x><c-n>') + screen:expect([[ + hello | + hullo | + heeee | + hello^ | + {2:hello }{0: }| + {1:hullo }{0: }| + {1:heeee }{0: }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {3:-- }{4: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 | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + | + ]]) + end) + + it('does not crash if text is changed by first call to complete function #17489', function() + source([[ + func Complete(findstart, base) abort + if a:findstart + let col = col('.') + call complete_add('#') + return col - 1 + else + return [] + endif + endfunc + + set completeopt=longest + set completefunc=Complete + ]]) + feed('ifoo#<C-X><C-U>') + assert_alive() + end) end) diff --git a/test/functional/editor/macro_spec.lua b/test/functional/editor/macro_spec.lua index 102d8fc723..d4cf6b28fd 100644 --- a/test/functional/editor/macro_spec.lua +++ b/test/functional/editor/macro_spec.lua @@ -6,9 +6,14 @@ local feed = helpers.feed local clear = helpers.clear local expect = helpers.expect local command = helpers.command +local funcs = helpers.funcs +local meths = helpers.meths +local insert = helpers.insert +local curbufmeths = helpers.curbufmeths + +before_each(clear) describe('macros', function() - before_each(clear) it('can be recorded and replayed', function() feed('qiahello<esc>q') expect('hello') @@ -27,4 +32,67 @@ describe('macros', function() expect('llllll') eq(eval('@i'), 'lxxx') end) + + it('can be replayed with Q', function() + insert [[hello +hello +hello]] + feed [[gg]] + + feed [[qqAFOO<esc>q]] + eq({'helloFOO', 'hello', 'hello'}, curbufmeths.get_lines(0, -1, false)) + + feed[[Q]] + eq({'helloFOOFOO', 'hello', 'hello'}, curbufmeths.get_lines(0, -1, false)) + + feed[[G3Q]] + eq({'helloFOOFOO', 'hello', 'helloFOOFOOFOO'}, curbufmeths.get_lines(0, -1, false)) + end) +end) + +describe('immediately after a macro has finished executing,', function() + before_each(function() + command([[let @a = 'gg0']]) + end) + + describe('reg_executing() from RPC returns an empty string', function() + it('if the macro does not end with a <Nop> mapping', function() + feed('@a') + eq('', funcs.reg_executing()) + end) + + it('if the macro ends with a <Nop> mapping', function() + command('nnoremap 0 <Nop>') + feed('@a') + eq('', funcs.reg_executing()) + end) + end) + + describe('characters from a mapping are not treated as a part of the macro #18015', function() + before_each(function() + command('nnoremap s qa') + end) + + it('if the macro does not end with a <Nop> mapping', function() + feed('@asq') -- "q" from "s" mapping should start recording a macro instead of being no-op + eq({mode = 'n', blocking = false}, meths.get_mode()) + expect('') + eq('', eval('@a')) + end) + + it('if the macro ends with a <Nop> mapping', function() + command('nnoremap 0 <Nop>') + feed('@asq') -- "q" from "s" mapping should start recording a macro instead of being no-op + eq({mode = 'n', blocking = false}, meths.get_mode()) + expect('') + eq('', eval('@a')) + end) + end) +end) + +describe('reg_recorded()', function() + it('returns the correct value', function() + feed [[qqyyq]] + eq('q', eval('reg_recorded()')) + 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 46ab483036..c38acbe96a 100644 --- a/test/functional/editor/mode_insert_spec.lua +++ b/test/functional/editor/mode_insert_spec.lua @@ -7,6 +7,7 @@ local command = helpers.command local eq = helpers.eq local eval = helpers.eval local meths = helpers.meths +local poke_eventloop = helpers.poke_eventloop describe('insert-mode', function() before_each(function() @@ -75,15 +76,80 @@ describe('insert-mode', function() feed('ooo') 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) + + describe([[With 'insertmode', Insert mode is not re-entered immediately after <C-L>]], function() + before_each(function() + command('set insertmode') + poke_eventloop() + eq({mode = 'i', blocking = false}, meths.get_mode()) + end) + + it('after calling :edit from <Cmd> mapping', function() + command('inoremap <C-B> <Cmd>edit Xfoo<CR>') + feed('<C-B><C-L>') + poke_eventloop() + eq({mode = 'n', blocking = false}, meths.get_mode()) + end) - it("doesn't cancel Ctrl-O mode when processing event", function() - feed('iHello World<c-o>') - eq({mode='niI', blocking=false}, meths.get_mode()) -- fast event - eq(2, eval('1+1')) -- causes K_EVENT key - eq({mode='niI', blocking=false}, meths.get_mode()) -- still in ctrl-o mode - feed('dd') - eq({mode='i', blocking=false}, meths.get_mode()) -- left ctrl-o mode - expect('') -- executed the command + it('after calling :edit from RPC #16823', function() + command('edit Xfoo') + feed('<C-L>') + poke_eventloop() + eq({mode = 'n', blocking = false}, meths.get_mode()) end) end) end) diff --git a/test/functional/editor/mode_visual_spec.lua b/test/functional/editor/mode_visual_spec.lua deleted file mode 100644 index 468ae00e01..0000000000 --- a/test/functional/editor/mode_visual_spec.lua +++ /dev/null @@ -1,27 +0,0 @@ --- Visual-mode tests. - -local helpers = require('test.functional.helpers')(after_each) -local clear = helpers.clear -local eq = helpers.eq -local eval = helpers.eval -local expect = helpers.expect -local feed = helpers.feed -local meths = helpers.meths - -describe('visual-mode', function() - before_each(clear) - - it("select-mode Ctrl-O doesn't cancel Ctrl-O mode when processing event #15688", function() - feed('iHello World<esc>gh<c-o>') - eq({mode='vs', blocking=false}, meths.get_mode()) -- fast event - eq({mode='vs', blocking=false}, meths.get_mode()) -- again #15288 - eq(2, eval('1+1')) -- causes K_EVENT key - eq({mode='vs', blocking=false}, meths.get_mode()) -- still in ctrl-o mode - feed('^') - eq({mode='s', blocking=false}, meths.get_mode()) -- left ctrl-o mode - feed('h') - eq({mode='i', blocking=false}, meths.get_mode()) -- entered insert mode - expect('h') -- selection is the whole line and is replaced - end) -end) - diff --git a/test/functional/editor/put_spec.lua b/test/functional/editor/put_spec.lua index 26967ecbba..cc9fce8f67 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 @@ -138,9 +138,9 @@ describe('put command', function() end -- create_test_defs() }}} local function find_cursor_position(expect_string) -- {{{ - -- There must only be one occurance of the character 'x' in + -- There must only be one occurrence of the character 'x' in -- expect_string. - -- This function removes that occurance, and returns the position that + -- This function removes that occurrence, and returns the position that -- it was in. -- This returns the cursor position that would leave the 'x' in that -- place if we feed 'ix<esc>' and the string existed before it. @@ -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/editor/tabpage_spec.lua b/test/functional/editor/tabpage_spec.lua index d1d6854b07..2494daf99b 100644 --- a/test/functional/editor/tabpage_spec.lua +++ b/test/functional/editor/tabpage_spec.lua @@ -3,8 +3,10 @@ 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 feed = helpers.feed local eval = helpers.eval +local exec = helpers.exec describe('tabpage', function() before_each(clear) @@ -34,5 +36,20 @@ describe('tabpage', function() eq(3, eval('tabpagenr()')) end) + + it('does not crash or loop 999 times if BufWipeout autocommand switches window #17868', function() + exec([[ + tabedit + let s:window_id = win_getid() + botright new + setlocal bufhidden=wipe + let g:win_closed = 0 + autocmd WinClosed * let g:win_closed += 1 + autocmd BufWipeout <buffer> call win_gotoid(s:window_id) + tabprevious + +tabclose + ]]) + neq(999, eval('g:win_closed')) + 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/cd_spec.lua b/test/functional/ex_cmds/cd_spec.lua index 283fcf9672..f9cce0deb6 100644 --- a/test/functional/ex_cmds/cd_spec.lua +++ b/test/functional/ex_cmds/cd_spec.lua @@ -294,7 +294,16 @@ describe("getcwd()", function () command('set autochdir') command('edit ' .. directories.global .. '/foo') eq(curdir .. pathsep .. directories.global, cwd()) + eq(curdir, wcwd()) + call('mkdir', 'bar') + command('edit ' .. 'bar/foo') + eq(curdir .. pathsep .. directories.global .. pathsep .. 'bar', cwd()) + eq(curdir, wcwd()) + command('lcd ..') + eq(curdir .. pathsep .. directories.global, cwd()) + eq(curdir .. pathsep .. directories.global, wcwd()) + command('edit') + eq(curdir .. pathsep .. directories.global .. pathsep .. 'bar', cwd()) + eq(curdir .. pathsep .. directories.global, wcwd()) end) end) - - diff --git a/test/functional/ex_cmds/cmd_map_spec.lua b/test/functional/ex_cmds/cmd_map_spec.lua index 0b2190bbcf..64cf53dfa9 100644 --- a/test/functional/ex_cmds/cmd_map_spec.lua +++ b/test/functional/ex_cmds/cmd_map_spec.lua @@ -1,21 +1,23 @@ local helpers = require('test.functional.helpers')(after_each) local clear = helpers.clear -local feed_command = helpers.feed_command local feed = helpers.feed local eq = helpers.eq local expect = helpers.expect local eval = helpers.eval local funcs = helpers.funcs local insert = helpers.insert +local write_file = helpers.write_file local exc_exec = helpers.exc_exec -local source = helpers.source +local command = helpers.command local Screen = require('test.functional.ui.screen') describe('mappings with <Cmd>', function() local screen + local tmpfile = 'X_ex_cmds_cmd_map' + local function cmdmap(lhs, rhs) - feed_command('noremap '..lhs..' <Cmd>'..rhs..'<cr>') - feed_command('noremap! '..lhs..' <Cmd>'..rhs..'<cr>') + command('noremap '..lhs..' <Cmd>'..rhs..'<cr>') + command('noremap! '..lhs..' <Cmd>'..rhs..'<cr>') end before_each(function() @@ -39,7 +41,7 @@ describe('mappings with <Cmd>', function() cmdmap('<F4>', 'normal! ww') cmdmap('<F5>', 'normal! "ay') cmdmap('<F6>', 'throw "very error"') - feed_command([[ + command([[ function! TextObj() if mode() !=# "v" normal! v @@ -55,11 +57,15 @@ describe('mappings with <Cmd>', function() feed('gg') cmdmap('<F8>', 'startinsert') cmdmap('<F9>', 'stopinsert') - feed_command("abbr foo <Cmd>let g:y = 17<cr>bar") + command("abbr foo <Cmd>let g:y = 17<cr>bar") + end) + + after_each(function() + os.remove(tmpfile) end) it('can be displayed', function() - feed_command('map <F3>') + command('map <F3>') screen:expect([[ ^some short lines | of test text | @@ -73,8 +79,8 @@ describe('mappings with <Cmd>', function() end) it('handles invalid mappings', function() - feed_command('let x = 0') - feed_command('noremap <F3> <Cmd><Cmd>let x = 1<cr>') + command('let x = 0') + command('noremap <F3> <Cmd><Cmd>let x = 1<cr>') feed('<F3>') screen:expect([[ ^some short lines | @@ -87,7 +93,7 @@ describe('mappings with <Cmd>', function() {2:E5521: <Cmd> mapping must end with <CR> before second <Cmd>} | ]]) - feed_command('noremap <F3> <Cmd><F3>let x = 2<cr>') + command('noremap <F3> <Cmd><F3>let x = 2<cr>') feed('<F3>') screen:expect([[ ^some short lines | @@ -100,7 +106,7 @@ describe('mappings with <Cmd>', function() {2:E5522: <Cmd> mapping must not include <F3> key} | ]]) - feed_command('noremap <F3> <Cmd>let x = 3') + command('noremap <F3> <Cmd>let x = 3') feed('<F3>') screen:expect([[ ^some short lines | @@ -137,7 +143,7 @@ describe('mappings with <Cmd>', function() eq('n', eval('mode(1)')) -- select mode mapping - feed_command('snoremap <F3> <Cmd>let m = mode(1)<cr>') + command('snoremap <F3> <Cmd>let m = mode(1)<cr>') feed('gh<F3>') eq('s', eval('m')) -- didn't leave select mode @@ -184,8 +190,8 @@ describe('mappings with <Cmd>', function() eq('n', eval('mode(1)')) -- terminal mode - feed_command('tnoremap <F3> <Cmd>let m = mode(1)<cr>') - feed_command('split | terminal') + command('tnoremap <F3> <Cmd>let m = mode(1)<cr>') + command('split | terminal') feed('i') eq('t', eval('mode(1)')) feed('<F3>') @@ -264,11 +270,11 @@ describe('mappings with <Cmd>', function() end) it('works in :normal command', function() - feed_command('noremap ,x <Cmd>call append(1, "xx")\\| call append(1, "aa")<cr>') - feed_command('noremap ,f <Cmd>nosuchcommand<cr>') - feed_command('noremap ,e <Cmd>throw "very error"\\| call append(1, "yy")<cr>') - feed_command('noremap ,m <Cmd>echoerr "The message."\\| call append(1, "zz")<cr>') - feed_command('noremap ,w <Cmd>for i in range(5)\\|if i==1\\|echoerr "Err"\\|endif\\|call append(1, i)\\|endfor<cr>') + command('noremap ,x <Cmd>call append(1, "xx")\\| call append(1, "aa")<cr>') + command('noremap ,f <Cmd>nosuchcommand<cr>') + command('noremap ,e <Cmd>throw "very error"\\| call append(1, "yy")<cr>') + command('noremap ,m <Cmd>echoerr "The message."\\| call append(1, "zz")<cr>') + command('noremap ,w <Cmd>for i in range(5)\\|if i==1\\|echoerr "Err"\\|endif\\|call append(1, i)\\|endfor<cr>') feed(":normal ,x<cr>") screen:expect([[ @@ -297,7 +303,7 @@ describe('mappings with <Cmd>', function() :normal ,x | ]]) - feed_command(':%d') + command(':%d') eq('Vim(echoerr):Err', exc_exec("normal ,w")) screen:expect([[ ^ | @@ -310,8 +316,8 @@ describe('mappings with <Cmd>', function() --No lines in buffer-- | ]]) - feed_command(':%d') - feed_command(':normal ,w') + command(':%d') + feed(':normal ,w<cr>') screen:expect([[ ^ | 4 | @@ -401,8 +407,8 @@ describe('mappings with <Cmd>', function() end) it('works in select mode', function() - feed_command('snoremap <F1> <cmd>throw "very error"<cr>') - feed_command('snoremap <F2> <cmd>normal! <c-g>"by<cr>') + command('snoremap <F1> <cmd>throw "very error"<cr>') + command('snoremap <F2> <cmd>normal! <c-g>"by<cr>') -- can extend select mode feed('gh<F4>') screen:expect([[ @@ -830,12 +836,14 @@ describe('mappings with <Cmd>', function() end) it("works with <SID> mappings", function() - source([[ + command('new!') + write_file(tmpfile, [[ map <f2> <Cmd>call <SID>do_it()<Cr> function! s:do_it() let g:x = 10 endfunction ]]) + command('source '..tmpfile) feed('<f2>') eq('', eval('v:errmsg')) eq(10, eval('g:x')) 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/echo_spec.lua b/test/functional/ex_cmds/echo_spec.lua index d320425de1..a6be04138b 100644 --- a/test/functional/ex_cmds/echo_spec.lua +++ b/test/functional/ex_cmds/echo_spec.lua @@ -181,9 +181,9 @@ describe(':echo :echon :echomsg :echoerr', function() end) it('dumps references to script functions', function() - eq('<SNR>2_Test2', eval('String(Test2_f)')) - eq("function('<SNR>2_Test2')", eval('StringMsg(Test2_f)')) - eq("function('<SNR>2_Test2')", eval('StringErr(Test2_f)')) + eq('<SNR>1_Test2', eval('String(Test2_f)')) + eq("function('<SNR>1_Test2')", eval('StringMsg(Test2_f)')) + eq("function('<SNR>1_Test2')", eval('StringErr(Test2_f)')) end) it('dump references to lambdas', function() @@ -205,11 +205,11 @@ describe(':echo :echon :echomsg :echoerr', function() it('dumps automatically created partials', function() assert_same_echo_dump( - "function('<SNR>2_Test2', {'f': function('<SNR>2_Test2')})", + "function('<SNR>1_Test2', {'f': function('<SNR>1_Test2')})", '{"f": Test2_f}.f', true) assert_same_echo_dump( - "function('<SNR>2_Test2', [1], {'f': function('<SNR>2_Test2', [1])})", + "function('<SNR>1_Test2', [1], {'f': function('<SNR>1_Test2', [1])})", '{"f": function(Test2_f, [1])}.f', true) end) @@ -227,7 +227,7 @@ describe(':echo :echon :echomsg :echoerr', function() function() meths.set_var('d', {v=true}) eq(dedent([[ - {'p': function('<SNR>2_Test2', {...@0}), 'f': function('<SNR>2_Test2'), 'v': v:true}]]), + {'p': function('<SNR>1_Test2', {...@0}), 'f': function('<SNR>1_Test2'), 'v': v:true}]]), exec_capture('echo String(extend(extend(g:d, {"f": g:Test2_f}), {"p": g:d.f}))')) end) @@ -264,7 +264,7 @@ describe(':echo :echon :echomsg :echoerr', function() eval('add(l, function("Test1", l))') eval('add(l, function("Test1", d))') eq(dedent([=[ - {'p': function('<SNR>2_Test2', [[[...@3], function('Test1', [[...@3]]), function('Test1', {...@0})], function('Test1', [[[...@5], function('Test1', [[...@5]]), function('Test1', {...@0})]]), function('Test1', {...@0})], {...@0}), 'f': function('<SNR>2_Test2'), 'v': v:true}]=]), + {'p': function('<SNR>1_Test2', [[[...@3], function('Test1', [[...@3]]), function('Test1', {...@0})], function('Test1', [[[...@5], function('Test1', [[...@5]]), function('Test1', {...@0})]]), function('Test1', {...@0})], {...@0}), 'f': function('<SNR>1_Test2'), 'v': v:true}]=]), exec_capture('echo String(extend(extend(g:d, {"f": g:Test2_f}), {"p": function(g:d.f, l)}))')) end) 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/normal_spec.lua b/test/functional/ex_cmds/normal_spec.lua new file mode 100644 index 0000000000..f6e7dd2b3a --- /dev/null +++ b/test/functional/ex_cmds/normal_spec.lua @@ -0,0 +1,27 @@ +local helpers = require('test.functional.helpers')(after_each) +local clear = helpers.clear +local command = helpers.command +local feed = helpers.feed +local expect = helpers.expect +local eq = helpers.eq +local eval = helpers.eval + +before_each(clear) + +describe(':normal', function() + it('can get out of Insert mode if called from Ex mode #17924', function() + feed('gQnormal! Ifoo<CR>') + expect('foo') + end) + + it('normal! does not execute command in Ex mode when running out of characters', function() + command('let g:var = 0') + command('normal! gQlet g:var = 1') + eq(0, eval('g:var')) + end) + + it('normal! gQinsert does not hang #17980', function() + command('normal! gQinsert') + expect('') + end) +end) diff --git a/test/functional/ex_cmds/script_spec.lua b/test/functional/ex_cmds/script_spec.lua index 0a772c559b..bf69ada820 100644 --- a/test/functional/ex_cmds/script_spec.lua +++ b/test/functional/ex_cmds/script_spec.lua @@ -2,18 +2,30 @@ local helpers = require('test.functional.helpers')(after_each) local eq = helpers.eq local neq = helpers.neq +local command = helpers.command +local write_file = helpers.write_file local meths = helpers.meths local clear = helpers.clear local dedent = helpers.dedent -local source = helpers.source local exc_exec = helpers.exc_exec local missing_provider = helpers.missing_provider +local tmpfile = 'X_ex_cmds_script' + before_each(clear) +local function source(code) + write_file(tmpfile, code) + command('source '..tmpfile) +end + describe('script_get-based command', function() local garbage = ')}{+*({}]*[;(+}{&[]}{*])(' + after_each(function() + os.remove(tmpfile) + end) + local function test_garbage_exec(cmd, check_neq) describe(cmd, function() it('works correctly when skipping oneline variant', function() @@ -62,10 +74,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/swapfile_preserve_recover_spec.lua b/test/functional/ex_cmds/swapfile_preserve_recover_spec.lua index d91feb4bc1..4d984af41e 100644 --- a/test/functional/ex_cmds/swapfile_preserve_recover_spec.lua +++ b/test/functional/ex_cmds/swapfile_preserve_recover_spec.lua @@ -10,6 +10,7 @@ local feed = helpers.feed local nvim_prog = helpers.nvim_prog local ok = helpers.ok local rmdir = helpers.rmdir +local os_kill = helpers.os_kill local set_session = helpers.set_session local spawn = helpers.spawn local nvim_async = helpers.nvim_async @@ -62,6 +63,7 @@ describe(':preserve', function() local swappath1 = eval('g:swapname') + os_kill(eval('getpid()')) -- Start another Nvim instance. local nvim2 = spawn({nvim_prog, '-u', 'NONE', '-i', 'NONE', '--embed'}, true) @@ -122,6 +124,7 @@ describe('swapfile detection', function() feed('isometext<esc>') command('preserve') + os_kill(eval('getpid()')) -- Start another Nvim instance. local nvim2 = spawn({nvim_prog, '-u', 'NONE', '-i', 'NONE', '--embed'}, true) diff --git a/test/functional/ex_cmds/verbose_spec.lua b/test/functional/ex_cmds/verbose_spec.lua new file mode 100644 index 0000000000..e6f67ef18e --- /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_create_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_create_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/api_level_8.mpack b/test/functional/fixtures/api_level_8.mpack Binary files differnew file mode 100644 index 0000000000..0447fce3ed --- /dev/null +++ b/test/functional/fixtures/api_level_8.mpack 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..173fc54af5 100644 --- a/test/functional/helpers.lua +++ b/test/functional/helpers.lua @@ -21,7 +21,6 @@ local map = global_helpers.tbl_map local ok = global_helpers.ok local sleep = global_helpers.sleep local tbl_contains = global_helpers.tbl_contains -local write_file = global_helpers.write_file local fail = global_helpers.fail local module = { @@ -54,7 +53,6 @@ if module.nvim_dir == module.nvim_prog then module.nvim_dir = "." end -local tmpname = global_helpers.tmpname local iswin = global_helpers.iswin local prepend_argv @@ -451,6 +449,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) @@ -493,24 +492,9 @@ function module.feed_command(...) end end -local sourced_fnames = {} +-- @deprecated use nvim_exec() function module.source(code) - local fname = tmpname() - write_file(fname, code) - module.command('source '..fname) - -- DO NOT REMOVE FILE HERE. - -- do_source() has a habit of checking whether files are “same” by using inode - -- and device IDs. If you run two source() calls in quick succession there is - -- a good chance that underlying filesystem will reuse the inode, making files - -- appear as “symlinks” to do_source when it checks FileIDs. With current - -- setup linux machines (both QB, travis and mine(ZyX-I) with XFS) do reuse - -- inodes, Mac OS machines (again, both QB and travis) do not. - -- - -- Files appearing as “symlinks” mean that both the first and the second - -- source() calls will use same SID, which may fail some tests which check for - -- exact numbers after `<SNR>` in e.g. function names. - sourced_fnames[#sourced_fnames + 1] = fname - return fname + module.exec(dedent(code)) end function module.has_powershell() @@ -524,7 +508,7 @@ function module.set_shell_powershell() local cmd = set_encoding..'Remove-Item -Force '..table.concat(iswin() and {'alias:cat', 'alias:echo', 'alias:sleep'} or {'alias:echo'}, ',')..';' - module.source([[ + module.exec([[ let &shell = ']]..shell..[[' set shellquote= shellxquote= let &shellpipe = '2>&1 | Out-File -Encoding UTF8 %s; exit $LastExitCode' @@ -886,9 +870,6 @@ module = global_helpers.tbl_extend('error', module, global_helpers) return function(after_each) if after_each then after_each(function() - for _, fname in ipairs(sourced_fnames) do - os.remove(fname) - end check_logs() check_cores('build/bin/nvim') if session then 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/059_utf8_spell_checking_spec.lua b/test/functional/legacy/059_utf8_spell_checking_spec.lua deleted file mode 100644 index 8630ac58ef..0000000000 --- a/test/functional/legacy/059_utf8_spell_checking_spec.lua +++ /dev/null @@ -1,1010 +0,0 @@ --- Tests for spell checking with 'encoding' set to "utf-8". - -local helpers = require('test.functional.helpers')(after_each) -local feed, insert, source = helpers.feed, helpers.insert, helpers.source -local clear, feed_command, expect = helpers.clear, helpers.feed_command, helpers.expect -local write_file, call = helpers.write_file, helpers.call - -local function write_latin1(name, text) - text = call('iconv', text, 'utf-8', 'latin-1') - write_file(name, text) -end - -describe("spell checking with 'encoding' set to utf-8", function() - setup(function() - clear() - feed_command("syntax off") - write_latin1('Xtest1.aff',[[ - SET ISO8859-1 - TRY esianrtolcdugmphbyfvkwjkqxz-ëéèêïîäàâöüû'ESIANRTOLCDUGMPHBYFVKWJKQXZ - - FOL àáâãäåæçèéêëìíîïðñòóôõöøùúûüýþßÿ - LOW àáâãäåæçèéêëìíîïðñòóôõöøùúûüýþßÿ - UPP ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞßÿ - - SOFOFROM abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZàáâãäåæçèéêëìíîïðñòóôõöøùúûüýþßÿÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞ¿ - SOFOTO ebctefghejklnnepkrstevvkesebctefghejklnnepkrstevvkeseeeeeeeceeeeeeeedneeeeeeeeeeepseeeeeeeeceeeeeeeedneeeeeeeeeeep? - - MIDWORD '- - - KEP = - RAR ? - BAD ! - - PFX I N 1 - PFX I 0 in . - - PFX O Y 1 - PFX O 0 out . - - SFX S Y 2 - SFX S 0 s [^s] - SFX S 0 es s - - SFX N N 3 - SFX N 0 en [^n] - SFX N 0 nen n - SFX N 0 n . - - REP 3 - REP g ch - REP ch g - REP svp s.v.p. - - MAP 9 - MAP aàáâãäå - MAP eèéêë - MAP iìíîï - MAP oòóôõö - MAP uùúûü - MAP nñ - MAP cç - MAP yÿý - MAP sß - ]]) - write_latin1('Xtest1.dic', [[ - 123456 - test/NO - # comment - wrong - Comment - OK - uk - put/ISO - the end - deol - déôr - ]]) - write_latin1('Xtest2.aff', [[ - SET ISO8859-1 - - FOL àáâãäåæçèéêëìíîïðñòóôõöøùúûüýþßÿ - LOW àáâãäåæçèéêëìíîïðñòóôõöøùúûüýþßÿ - UPP ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞßÿ - - PFXPOSTPONE - - MIDWORD '- - - KEP = - RAR ? - BAD ! - - PFX I N 1 - PFX I 0 in . - - PFX O Y 1 - PFX O 0 out [a-z] - - SFX S Y 2 - SFX S 0 s [^s] - SFX S 0 es s - - SFX N N 3 - SFX N 0 en [^n] - SFX N 0 nen n - SFX N 0 n . - - REP 3 - REP g ch - REP ch g - REP svp s.v.p. - - MAP 9 - MAP aàáâãäå - MAP eèéêë - MAP iìíîï - MAP oòóôõö - MAP uùúûü - MAP nñ - MAP cç - MAP yÿý - MAP sß - ]]) - write_latin1('Xtest3.aff', [[ - SET ISO8859-1 - - COMPOUNDMIN 3 - COMPOUNDRULE m* - NEEDCOMPOUND x - ]]) - write_latin1('Xtest3.dic', [[ - 1234 - foo/m - bar/mx - mï/m - la/mx - ]]) - write_latin1('Xtest4.aff', [[ - SET ISO8859-1 - - FOL àáâãäåæçèéêëìíîïðñòóôõöøùúûüýþßÿ - LOW àáâãäåæçèéêëìíîïðñòóôõöøùúûüýþßÿ - UPP ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞßÿ - - COMPOUNDRULE m+ - COMPOUNDRULE sm*e - COMPOUNDRULE sm+ - COMPOUNDMIN 3 - COMPOUNDWORDMAX 3 - COMPOUNDFORBIDFLAG t - - COMPOUNDSYLMAX 5 - SYLLABLE aáeéiíoóöõuúüûy/aa/au/ea/ee/ei/ie/oa/oe/oo/ou/uu/ui - - MAP 9 - MAP aàáâãäå - MAP eèéêë - MAP iìíîï - MAP oòóôõö - MAP uùúûü - MAP nñ - MAP cç - MAP yÿý - MAP sß - - NEEDAFFIX x - - PFXPOSTPONE - - MIDWORD '- - - SFX q N 1 - SFX q 0 -ok . - - SFX a Y 2 - SFX a 0 s . - SFX a 0 ize/t . - - PFX p N 1 - PFX p 0 pre . - - PFX P N 1 - PFX P 0 nou . - ]]) - write_latin1('Xtest4.dic', [[ - 1234 - word/mP - util/am - pro/xq - tomato/m - bork/mp - start/s - end/e - ]]) - write_latin1('Xtest5.aff', [[ - SET ISO8859-1 - - FLAG long - - NEEDAFFIX !! - - COMPOUNDRULE ssmm*ee - - NEEDCOMPOUND xx - COMPOUNDPERMITFLAG pp - - SFX 13 Y 1 - SFX 13 0 bork . - - SFX a1 Y 1 - SFX a1 0 a1 . - - SFX aé Y 1 - SFX aé 0 aé . - - PFX zz Y 1 - PFX zz 0 pre/pp . - - PFX yy Y 1 - PFX yy 0 nou . - ]]) - write_latin1('Xtest5.dic', [[ - 1234 - foo/a1aé!! - bar/zz13ee - start/ss - end/eeyy - middle/mmxx - ]]) - write_latin1('Xtest6.aff', [[ - SET ISO8859-1 - - FLAG caplong - - NEEDAFFIX A! - - COMPOUNDRULE sMm*Ee - - NEEDCOMPOUND Xx - - COMPOUNDPERMITFLAG p - - SFX N3 Y 1 - SFX N3 0 bork . - - SFX A1 Y 1 - SFX A1 0 a1 . - - SFX Aé Y 1 - SFX Aé 0 aé . - - PFX Zz Y 1 - PFX Zz 0 pre/p . - ]]) - write_latin1('Xtest6.dic', [[ - 1234 - mee/A1AéA! - bar/ZzN3Ee - lead/s - end/Ee - middle/MmXx - ]]) - write_latin1('Xtest7.aff', [[ - SET ISO8859-1 - - FOL àáâãäåæçèéêëìíîïðñòóôõöøùúûüýþßÿ - LOW àáâãäåæçèéêëìíîïðñòóôõöøùúûüýþßÿ - UPP ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞßÿ - - FLAG num - - NEEDAFFIX 9999 - - COMPOUNDRULE 2,77*123 - - NEEDCOMPOUND 1 - COMPOUNDPERMITFLAG 432 - - SFX 61003 Y 1 - SFX 61003 0 meat . - - SFX 391 Y 1 - SFX 391 0 a1 . - - SFX 111 Y 1 - SFX 111 0 aé . - - PFX 17 Y 1 - PFX 17 0 pre/432 . - ]]) - write_latin1('Xtest7.dic', [[ - 1234 - mee/391,111,9999 - bar/17,61003,123 - lead/2 - tail/123 - middle/77,1 - ]]) - write_latin1('Xtest8.aff', [[ - SET ISO8859-1 - - NOSPLITSUGS - ]]) - write_latin1('Xtest8.dic', [[ - 1234 - foo - bar - faabar - ]]) - write_latin1('Xtest9.aff', [[ - ]]) - write_latin1('Xtest9.dic', [[ - 1234 - foo - bar - ]]) - write_latin1('Xtest-sal.aff', [[ - SET ISO8859-1 - TRY esianrtolcdugmphbyfvkwjkqxz-ëéèêïîäàâöüû'ESIANRTOLCDUGMPHBYFVKWJKQXZ - - FOL àáâãäåæçèéêëìíîïðñòóôõöøùúûüýþßÿ - LOW àáâãäåæçèéêëìíîïðñòóôõöøùúûüýþßÿ - UPP ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞßÿ - - MIDWORD '- - - KEP = - RAR ? - BAD ! - - PFX I N 1 - PFX I 0 in . - - PFX O Y 1 - PFX O 0 out . - - SFX S Y 2 - SFX S 0 s [^s] - SFX S 0 es s - - SFX N N 3 - SFX N 0 en [^n] - SFX N 0 nen n - SFX N 0 n . - - REP 3 - REP g ch - REP ch g - REP svp s.v.p. - - MAP 9 - MAP aàáâãäå - MAP eèéêë - MAP iìíîï - MAP oòóôõö - MAP uùúûü - MAP nñ - MAP cç - MAP yÿý - MAP sß - - SAL AH(AEIOUY)-^ *H - SAL AR(AEIOUY)-^ *R - SAL A(HR)^ * - SAL A^ * - SAL AH(AEIOUY)- H - SAL AR(AEIOUY)- R - SAL A(HR) _ - SAL À^ * - SAL Å^ * - SAL BB- _ - SAL B B - SAL CQ- _ - SAL CIA X - SAL CH X - SAL C(EIY)- S - SAL CK K - SAL COUGH^ KF - SAL CC< C - SAL C K - SAL DG(EIY) K - SAL DD- _ - SAL D T - SAL É< E - SAL EH(AEIOUY)-^ *H - SAL ER(AEIOUY)-^ *R - SAL E(HR)^ * - SAL ENOUGH^$ *NF - SAL E^ * - SAL EH(AEIOUY)- H - SAL ER(AEIOUY)- R - SAL E(HR) _ - SAL FF- _ - SAL F F - SAL GN^ N - SAL GN$ N - SAL GNS$ NS - SAL GNED$ N - SAL GH(AEIOUY)- K - SAL GH _ - SAL GG9 K - SAL G K - SAL H H - SAL IH(AEIOUY)-^ *H - SAL IR(AEIOUY)-^ *R - SAL I(HR)^ * - SAL I^ * - SAL ING6 N - SAL IH(AEIOUY)- H - SAL IR(AEIOUY)- R - SAL I(HR) _ - SAL J K - SAL KN^ N - SAL KK- _ - SAL K K - SAL LAUGH^ LF - SAL LL- _ - SAL L L - SAL MB$ M - SAL MM M - SAL M M - SAL NN- _ - SAL N N - SAL OH(AEIOUY)-^ *H - SAL OR(AEIOUY)-^ *R - SAL O(HR)^ * - SAL O^ * - SAL OH(AEIOUY)- H - SAL OR(AEIOUY)- R - SAL O(HR) _ - SAL PH F - SAL PN^ N - SAL PP- _ - SAL P P - SAL Q K - SAL RH^ R - SAL ROUGH^ RF - SAL RR- _ - SAL R R - SAL SCH(EOU)- SK - SAL SC(IEY)- S - SAL SH X - SAL SI(AO)- X - SAL SS- _ - SAL S S - SAL TI(AO)- X - SAL TH @ - SAL TCH-- _ - SAL TOUGH^ TF - SAL TT- _ - SAL T T - SAL UH(AEIOUY)-^ *H - SAL UR(AEIOUY)-^ *R - SAL U(HR)^ * - SAL U^ * - SAL UH(AEIOUY)- H - SAL UR(AEIOUY)- R - SAL U(HR) _ - SAL V^ W - SAL V F - SAL WR^ R - SAL WH^ W - SAL W(AEIOU)- W - SAL X^ S - SAL X KS - SAL Y(AEIOU)- Y - SAL ZZ- _ - SAL Z S - ]]) - write_file('Xtest.utf-8.add', [[ - /regions=usgbnz - elequint/2 - elekwint/3 - ]]) - end) - - teardown(function() - os.remove('Xtest-sal.aff') - os.remove('Xtest.aff') - os.remove('Xtest.dic') - os.remove('Xtest.utf-8.add') - os.remove('Xtest.utf-8.add.spl') - os.remove('Xtest.utf-8.spl') - os.remove('Xtest.utf-8.sug') - os.remove('Xtest1.aff') - os.remove('Xtest1.dic') - os.remove('Xtest2.aff') - os.remove('Xtest3.aff') - os.remove('Xtest3.dic') - os.remove('Xtest4.aff') - os.remove('Xtest4.dic') - os.remove('Xtest5.aff') - os.remove('Xtest5.dic') - os.remove('Xtest6.aff') - os.remove('Xtest6.dic') - os.remove('Xtest7.aff') - os.remove('Xtest7.dic') - os.remove('Xtest8.aff') - os.remove('Xtest8.dic') - os.remove('Xtest9.aff') - os.remove('Xtest9.dic') - end) - - -- Function to test .aff/.dic with list of good and bad words. This was a - -- Vim function in the original legacy test. - local function test_one(aff, dic) - -- Generate a .spl file from a .dic and .aff file. - if helpers.iswin() then - os.execute('copy /y Xtest'..aff..'.aff Xtest.aff') - os.execute('copy /y Xtest'..dic..'.dic Xtest.dic') - else - os.execute('cp -f Xtest'..aff..'.aff Xtest.aff') - os.execute('cp -f Xtest'..dic..'.dic Xtest.dic') - end - source([[ - set spellfile= - function! SpellDumpNoShow() - " spelling scores depend on what happens to be drawn on screen - spelldump - %yank - quit - endfunction - $put ='' - $put ='test ]]..aff..'-'..dic..[[' - mkspell! Xtest Xtest - " Use that spell file. - set spl=Xtest.utf-8.spl spell - " List all valid words. - call SpellDumpNoShow() - $put - $put ='-------' - " Find all bad words and suggestions for them. - 1;/^]]..aff..[[good: - normal 0f:]s - let prevbad = '' - while 1 - let [bad, a] = spellbadword() - if bad == '' || bad == prevbad || bad == 'badend' - break - endif - let prevbad = bad - let lst = spellsuggest(bad, 3) - normal mm - $put =bad - $put =string(lst) - normal `m]s - endwhile - ]]) - end - - it('part 1-1', function() - insert([[ - 1good: wrong OK puts. Test the end - bad: inputs comment ok Ok. test déôl end the - badend - - test2: - elequint test elekwint test elekwent asdf - ]]) - test_one(1, 1) - feed_command([[$put =soundfold('goobledygoook')]]) - feed_command([[$put =soundfold('kóopërÿnôven')]]) - feed_command([[$put =soundfold('oeverloos gezwets edale')]]) - -- And now with SAL instead of SOFO items; test automatic reloading. - if helpers.iswin() then - os.execute('copy /y Xtest-sal.aff Xtest.aff') - else - os.execute('cp -f Xtest-sal.aff Xtest.aff') - end - feed_command('mkspell! Xtest Xtest') - feed_command([[$put =soundfold('goobledygoook')]]) - feed_command([[$put =soundfold('kóopërÿnôven')]]) - feed_command([[$put =soundfold('oeverloos gezwets edale')]]) - -- Also use an addition file. - feed_command('mkspell! Xtest.utf-8.add.spl Xtest.utf-8.add') - feed_command('set spellfile=Xtest.utf-8.add') - feed_command('/^test2:') - feed(']s') - feed_command('let [str, a] = spellbadword()') - feed_command('$put =str') - feed_command('set spl=Xtest_us.utf-8.spl') - feed_command('/^test2:') - feed(']smm') - feed_command('let [str, a] = spellbadword()') - feed_command('$put =str') - feed('`m]s') - feed_command('let [str, a] = spellbadword()') - feed_command('$put =str') - feed_command('set spl=Xtest_gb.utf-8.spl') - feed_command('/^test2:') - feed(']smm') - feed_command('let [str, a] = spellbadword()') - feed_command('$put =str') - feed('`m]s') - feed_command('let [str, a] = spellbadword()') - feed_command('$put =str') - feed_command('set spl=Xtest_nz.utf-8.spl') - feed_command('/^test2:') - feed(']smm') - feed_command('let [str, a] = spellbadword()') - feed_command('$put =str') - feed('`m]s') - feed_command('let [str, a] = spellbadword()') - feed_command('$put =str') - feed_command('set spl=Xtest_ca.utf-8.spl') - feed_command('/^test2:') - feed(']smm') - feed_command('let [str, a] = spellbadword()') - feed_command('$put =str') - feed('`m]s') - feed_command('let [str, a] = spellbadword()') - feed_command('$put =str') - feed_command('1,/^test 1-1/-1d') - expect([[ - test 1-1 - # file: Xtest.utf-8.spl - Comment - deol - déôr - input - OK - output - outputs - outtest - put - puts - test - testen - testn - the end - uk - wrong - ------- - bad - ['put', 'uk', 'OK'] - inputs - ['input', 'puts', 'outputs'] - comment - ['Comment', 'outtest', 'the end'] - ok - ['OK', 'uk', 'put'] - Ok - ['OK', 'Uk', 'Put'] - test - ['Test', 'testn', 'testen'] - déôl - ['deol', 'déôr', 'test'] - end - ['put', 'uk', 'test'] - the - ['put', 'uk', 'test'] - gebletegek - kepereneven - everles gesvets etele - kbltykk - kprnfn - *fls kswts tl - elekwent - elequint - elekwint - elekwint - elekwent - elequint - elekwent - elequint - elekwint]]) - end) - - it('part 2-1', function() - insert([[ - 2good: puts - bad: inputs comment ok Ok end the. test déôl - badend - ]]) - -- Postponed prefixes. - test_one(2, 1) - feed_command('1,/^test 2-1/-1d') - expect([=[ - test 2-1 - # file: Xtest.utf-8.spl - Comment - deol - déôr - OK - put - input - output - puts - outputs - test - outtest - testen - testn - the end - uk - wrong - ------- - bad - ['put', 'uk', 'OK'] - inputs - ['input', 'puts', 'outputs'] - comment - ['Comment'] - ok - ['OK', 'uk', 'put'] - Ok - ['OK', 'Uk', 'Put'] - end - ['put', 'uk', 'deol'] - the - ['put', 'uk', 'test'] - test - ['Test', 'testn', 'testen'] - déôl - ['deol', 'déôr', 'test']]=]) - end) - - it('part 3-3', function() - insert([[ - Test rules for compounding. - - 3good: foo mï foobar foofoobar barfoo barbarfoo - bad: bar la foomï barmï mïfoo mïbar mïmï lala mïla lamï foola labar - badend - ]]) - test_one(3, 3) - feed_command('1,/^test 3-3/-1d') - expect([=[ - test 3-3 - # file: Xtest.utf-8.spl - foo - mï - ------- - bad - ['foo', 'mï'] - bar - ['barfoo', 'foobar', 'foo'] - la - ['mï', 'foo'] - foomï - ['foo mï', 'foo', 'foofoo'] - barmï - ['barfoo', 'mï', 'barbar'] - mïfoo - ['mï foo', 'foo', 'foofoo'] - mïbar - ['foobar', 'barbar', 'mï'] - mïmï - ['mï mï', 'mï'] - lala - [] - mïla - ['mï', 'mï mï'] - lamï - ['mï', 'mï mï'] - foola - ['foo', 'foobar', 'foofoo'] - labar - ['barbar', 'foobar']]=]) - end) - - it('part 4-4', function() - insert([[ - Tests for compounding. - - 4good: word util bork prebork start end wordutil wordutils pro-ok - bork borkbork borkborkbork borkborkborkbork borkborkborkborkbork - tomato tomatotomato startend startword startwordword startwordend - startwordwordend startwordwordwordend prebork preborkbork - preborkborkbork - nouword - bad: wordutilize pro borkborkborkborkborkbork tomatotomatotomato - endstart endend startstart wordend wordstart - preborkprebork preborkpreborkbork - startwordwordwordwordend borkpreborkpreborkbork - utilsbork startnouword - badend - ]]) - test_one(4, 4) - feed_command('1,/^test 4-4/-1d') - expect([=[ - test 4-4 - # file: Xtest.utf-8.spl - bork - prebork - end - pro-ok - start - tomato - util - utilize - utils - word - nouword - ------- - bad - ['end', 'bork', 'word'] - wordutilize - ['word utilize', 'wordutils', 'wordutil'] - pro - ['bork', 'word', 'end'] - borkborkborkborkborkbork - ['bork borkborkborkborkbork', 'borkbork borkborkborkbork', 'borkborkbork borkborkbork'] - tomatotomatotomato - ['tomato tomatotomato', 'tomatotomato tomato', 'tomato tomato tomato'] - endstart - ['end start', 'start'] - endend - ['end end', 'end'] - startstart - ['start start'] - wordend - ['word end', 'word', 'wordword'] - wordstart - ['word start', 'bork start'] - preborkprebork - ['prebork prebork', 'preborkbork', 'preborkborkbork'] - preborkpreborkbork - ['prebork preborkbork', 'preborkborkbork', 'preborkborkborkbork'] - startwordwordwordwordend - ['startwordwordwordword end', 'startwordwordwordword', 'start wordwordwordword end'] - borkpreborkpreborkbork - ['bork preborkpreborkbork', 'bork prebork preborkbork', 'bork preborkprebork bork'] - utilsbork - ['utilbork', 'utils bork', 'util bork'] - startnouword - ['start nouword', 'startword', 'startborkword']]=]) - end) - - it('part 5-5', function() - insert([[ - Test affix flags with two characters - - 5good: fooa1 fooaé bar prebar barbork prebarbork startprebar - start end startend startmiddleend nouend - bad: foo fooa2 prabar probarbirk middle startmiddle middleend endstart - startprobar startnouend - badend - ]]) - test_one(5, 5) - feed_command('1,/^test 5-5/-1d') - expect([=[ - test 5-5 - # file: Xtest.utf-8.spl - bar - barbork - end - fooa1 - fooaé - nouend - prebar - prebarbork - start - ------- - bad - ['bar', 'end', 'fooa1'] - foo - ['fooa1', 'fooaé', 'bar'] - fooa2 - ['fooa1', 'fooaé', 'bar'] - prabar - ['prebar', 'bar', 'bar bar'] - probarbirk - ['prebarbork'] - middle - [] - startmiddle - ['startmiddleend', 'startmiddlebar'] - middleend - [] - endstart - ['end start', 'start'] - startprobar - ['startprebar', 'start prebar', 'startbar'] - startnouend - ['start nouend', 'startend']]=]) - end) - - it('part 6-6', function() - insert([[ - 6good: meea1 meeaé bar prebar barbork prebarbork leadprebar - lead end leadend leadmiddleend - bad: mee meea2 prabar probarbirk middle leadmiddle middleend endlead - leadprobar - badend - ]]) - test_one(6, 6) - feed_command('1,/^test 6-6/-1d') - expect([=[ - test 6-6 - # file: Xtest.utf-8.spl - bar - barbork - end - lead - meea1 - meeaé - prebar - prebarbork - ------- - bad - ['bar', 'end', 'lead'] - mee - ['meea1', 'meeaé', 'bar'] - meea2 - ['meea1', 'meeaé', 'lead'] - prabar - ['prebar', 'bar', 'leadbar'] - probarbirk - ['prebarbork'] - middle - [] - leadmiddle - ['leadmiddleend', 'leadmiddlebar'] - middleend - [] - endlead - ['end lead', 'lead', 'end end'] - leadprobar - ['leadprebar', 'lead prebar', 'leadbar']]=]) - end) - - it('part 7-7', function() - insert([[ - 7good: meea1 meeaé bar prebar barmeat prebarmeat leadprebar - lead tail leadtail leadmiddletail - bad: mee meea2 prabar probarmaat middle leadmiddle middletail taillead - leadprobar - badend - ]]) - -- Compound words. - test_one(7, 7) - -- Assert buffer contents. - feed_command('1,/^test 7-7/-1d') - expect([=[ - test 7-7 - # file: Xtest.utf-8.spl - bar - barmeat - lead - meea1 - meeaé - prebar - prebarmeat - tail - ------- - bad - ['bar', 'lead', 'tail'] - mee - ['meea1', 'meeaé', 'bar'] - meea2 - ['meea1', 'meeaé', 'lead'] - prabar - ['prebar', 'bar', 'leadbar'] - probarmaat - ['prebarmeat'] - middle - [] - leadmiddle - ['leadmiddlebar'] - middletail - [] - taillead - ['tail lead', 'tail'] - leadprobar - ['leadprebar', 'lead prebar', 'leadbar']]=]) - end) - - it('part 8-8', function() - insert([[ - 8good: foo bar faabar - bad: foobar barfoo - badend - ]]) - -- NOSPLITSUGS - test_one(8, 8) - -- Assert buffer contents. - feed_command('1,/^test 8-8/-1d') - expect([=[ - test 8-8 - # file: Xtest.utf-8.spl - bar - faabar - foo - ------- - bad - ['bar', 'foo'] - foobar - ['faabar', 'foo bar', 'bar'] - barfoo - ['bar foo', 'bar', 'foo']]=]) - end) - - it('part 9-9', function() - insert([[ - 9good: 0b1011 0777 1234 0x01ff - badend - ]]) - -- NOSPLITSUGS - test_one(9, 9) - -- Assert buffer contents. - feed_command('1,/^test 9-9/-1d') - expect([=[ - test 9-9 - # file: Xtest.utf-8.spl - bar - foo - -------]=]) - end) -end) diff --git a/test/functional/legacy/assert_spec.lua b/test/functional/legacy/assert_spec.lua index c2b22472bf..4829a0bbe1 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() @@ -202,91 +136,25 @@ describe('assert function:', function() end) it('should have file names and passed messages', function() - local tmpname_one = source([[ + source([[ call assert_equal(1, 100, 'equal assertion failed') call assert_false('true', 'true assertion failed') call assert_true('false', 'false assertion failed') ]]) - local tmpname_two = source([[ - call assert_true('', 'file two') - ]]) - expected_errors({ - tmpname_one .. " line 1: equal assertion failed: Expected 1 but got 100", - tmpname_one .. " line 2: true assertion failed: Expected False but got 'true'", - tmpname_one .. " line 3: false assertion failed: Expected True but got 'false'", - 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)) + call assert_true('', 'file two') ]]) - 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'" + "nvim_exec(): equal assertion failed: Expected 1 but got 100", + "nvim_exec(): true assertion failed: Expected False but got 'true'", + "nvim_exec(): false assertion failed: Expected True but got 'false'", + "nvim_exec(): file two: Expected True but got ''", }) 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/autochdir_spec.lua b/test/functional/legacy/autochdir_spec.lua index 466e3ed830..13cb6cd287 100644 --- a/test/functional/legacy/autochdir_spec.lua +++ b/test/functional/legacy/autochdir_spec.lua @@ -1,26 +1,116 @@ local lfs = require('lfs') local helpers = require('test.functional.helpers')(after_each) -local clear, eq = helpers.clear, helpers.eq -local eval, command = helpers.eval, helpers.command +local clear, eq, matches = helpers.clear, helpers.eq, helpers.matches +local eval, command, call, meths = helpers.eval, helpers.command, helpers.call, helpers.meths +local source, exec_capture = helpers.source, helpers.exec_capture + +local function expected_empty() + eq({}, meths.get_vvar('errors')) +end describe('autochdir behavior', function() - local dir = 'Xtest-functional-legacy-autochdir' + local dir = 'Xtest_functional_legacy_autochdir' before_each(function() lfs.mkdir(dir) clear() + command('set shellslash') end) after_each(function() helpers.rmdir(dir) end) - -- Tests vim/vim/777 without test_autochdir(). + -- Tests vim/vim#777 without test_autochdir(). it('sets filename', function() command('set acd') command('new') command('w '..dir..'/Xtest') eq('Xtest', eval("expand('%')")) - eq(dir, eval([[substitute(getcwd(), '.*[/\\]\(\k*\)', '\1', '')]])) + eq(dir, eval([[substitute(getcwd(), '.*/\(\k*\)', '\1', '')]])) + end) + + it(':file in win_execute() does not cause wrong directory', function() + command('cd '..dir) + source([[ + func Test_set_filename_other_window() + let cwd = getcwd() + call mkdir('Xa') + call mkdir('Xb') + call mkdir('Xc') + try + args Xa/aaa.txt Xb/bbb.txt + set acd + let winid = win_getid() + snext + call assert_equal('Xb', substitute(getcwd(), '.*/\([^/]*\)$', '\1', '')) + call win_execute(winid, 'file ' .. cwd .. '/Xc/ccc.txt') + call assert_equal('Xb', substitute(getcwd(), '.*/\([^/]*\)$', '\1', '')) + finally + set noacd + call chdir(cwd) + call delete('Xa', 'rf') + call delete('Xb', 'rf') + call delete('Xc', 'rf') + bwipe! aaa.txt + bwipe! bbb.txt + bwipe! ccc.txt + endtry + endfunc + ]]) + call('Test_set_filename_other_window') + expected_empty() + end) + + it('win_execute() does not change directory', function() + local subdir = 'Xfile' + command('cd '..dir) + command('set acd') + call('mkdir', subdir) + local winid = eval('win_getid()') + command('new '..subdir..'/file') + matches(dir..'/'..subdir..'$', eval('getcwd()')) + command('cd ..') + matches(dir..'$', eval('getcwd()')) + call('win_execute', winid, 'echo') + matches(dir..'$', eval('getcwd()')) + end) + + it(':verbose pwd shows whether autochdir is used', function() + local subdir = 'Xautodir' + command('cd '..dir) + local cwd = eval('getcwd()') + command('edit global.txt') + matches('%[global%].*'..dir..'$', exec_capture('verbose pwd')) + call('mkdir', subdir) + command('split '..subdir..'/local.txt') + command('lcd '..subdir) + matches('%[window%].*'..dir..'/'..subdir..'$', exec_capture('verbose pwd')) + command('set acd') + command('wincmd w') + matches('%[autochdir%].*'..dir..'$', exec_capture('verbose pwd')) + command('tcd '..cwd) + matches('%[tabpage%].*'..dir..'$', exec_capture('verbose pwd')) + command('cd '..cwd) + matches('%[global%].*'..dir..'$', exec_capture('verbose pwd')) + command('lcd '..cwd) + matches('%[window%].*'..dir..'$', exec_capture('verbose pwd')) + command('edit') + matches('%[autochdir%].*'..dir..'$', exec_capture('verbose pwd')) + command('enew') + command('wincmd w') + matches('%[autochdir%].*'..dir..'/'..subdir..'$', exec_capture('verbose pwd')) + command('wincmd w') + matches('%[window%].*'..dir..'$', exec_capture('verbose pwd')) + command('wincmd w') + matches('%[autochdir%].*'..dir..'/'..subdir..'$', exec_capture('verbose pwd')) + command('set noacd') + matches('%[autochdir%].*'..dir..'/'..subdir..'$', exec_capture('verbose pwd')) + command('wincmd w') + matches('%[window%].*'..dir..'$', exec_capture('verbose pwd')) + command('cd '..cwd) + matches('%[global%].*'..dir..'$', exec_capture('verbose pwd')) + command('wincmd w') + matches('%[window%].*'..dir..'/'..subdir..'$', exec_capture('verbose pwd')) end) end) diff --git a/test/functional/legacy/autocmd_option_spec.lua b/test/functional/legacy/autocmd_option_spec.lua index 1914818215..5e586d3a6a 100644 --- a/test/functional/legacy/autocmd_option_spec.lua +++ b/test/functional/legacy/autocmd_option_spec.lua @@ -1,6 +1,6 @@ local helpers = require('test.functional.helpers')(after_each) local nvim = helpers.meths -local clear, eq, neq = helpers.clear, helpers.eq, helpers.neq +local clear, eq, neq, eval = helpers.clear, helpers.eq, helpers.neq, helpers.eval local curbuf, buf = helpers.curbuf, helpers.bufmeths local curwin = helpers.curwin local exec_capture = helpers.exec_capture @@ -10,11 +10,14 @@ local function declare_hook_function() source([[ fu! AutoCommand(match, bufnr, winnr) let l:acc = { - \ 'option' : a:match, - \ 'oldval' : v:option_old, - \ 'newval' : v:option_new, - \ 'scope' : v:option_type, - \ 'attr' : { + \ 'option' : a:match, + \ 'oldval' : v:option_old, + \ 'oldval_l' : v:option_oldlocal, + \ 'oldval_g' : v:option_oldglobal, + \ 'newval' : v:option_new, + \ 'scope' : v:option_type, + \ 'cmd' : v:option_command, + \ 'attr' : { \ 'bufnr' : a:bufnr, \ 'winnr' : a:winnr, \ } @@ -42,13 +45,16 @@ local function get_result() return ret end -local function expected_table(option, oldval, newval, scope, attr) +local function expected_table(option, oldval, oldval_l, oldval_g, newval, scope, cmd, attr) return { - option = option, - oldval = tostring(oldval), - newval = tostring(newval), - scope = scope, - attr = attr, + option = option, + oldval = tostring(oldval), + oldval_l = tostring(oldval_l), + oldval_g = tostring(oldval_g), + newval = tostring(newval), + scope = scope, + cmd = cmd, + attr = attr, } end @@ -66,7 +72,7 @@ local function expected_combination(...) end for i, v in ipairs(args) do - local attr = v[5] + local attr = v[8] if not attr then -- remove attr entries ret[i].attr = nil @@ -112,7 +118,7 @@ local function get_new_window_number() end describe('au OptionSet', function() - describe('with any opton (*)', function() + describe('with any option (*)', function() before_each(function() clear() @@ -123,44 +129,44 @@ describe('au OptionSet', function() it('should be called in setting number option', function() command('set nu') - expected_combination({'number', 0, 1, 'global'}) + expected_combination({'number', 0, 0, 0, 1, 'global', 'set'}) command('setlocal nonu') - expected_combination({'number', 1, 0, 'local'}) + expected_combination({'number', 1, 1, '', 0, 'local', 'setlocal'}) command('setglobal nonu') - expected_combination({'number', 1, 0, 'global'}) + expected_combination({'number', 1, '', 1, 0, 'global', 'setglobal'}) end) it('should be called in setting autoindent option',function() command('setlocal ai') - expected_combination({'autoindent', 0, 1, 'local'}) + expected_combination({'autoindent', 0, 0, '', 1, 'local', 'setlocal'}) command('setglobal ai') - expected_combination({'autoindent', 0, 1, 'global'}) + expected_combination({'autoindent', 0, '', 0, 1, 'global', 'setglobal'}) command('set noai') - expected_combination({'autoindent', 1, 0, 'global'}) + expected_combination({'autoindent', 1, 1, 1, 0, 'global', 'set'}) end) it('should be called in inverting global autoindent option',function() command('set ai!') - expected_combination({'autoindent', 0, 1, 'global'}) + expected_combination({'autoindent', 0, 0, 0, 1, 'global', 'set'}) end) it('should be called in being unset local autoindent option',function() command('setlocal ai') - expected_combination({'autoindent', 0, 1, 'local'}) + expected_combination({'autoindent', 0, 0, '', 1, 'local', 'setlocal'}) command('setlocal ai<') - expected_combination({'autoindent', 1, 0, 'local'}) + expected_combination({'autoindent', 1, 1, '', 0, 'local', 'setlocal'}) end) it('should be called in setting global list and number option at the same time',function() command('set list nu') expected_combination( - {'list', 0, 1, 'global'}, - {'number', 0, 1, 'global'} + {'list', 0, 0, 0, 1, 'global', 'set'}, + {'number', 0, 0, 0, 1, 'global', 'set'} ) end) @@ -171,25 +177,27 @@ describe('au OptionSet', function() it('should be called in setting local acd', function() command('setlocal acd') - expected_combination({'autochdir', 0, 1, 'local'}) + expected_combination({'autochdir', 0, 0, '', 1, 'local', 'setlocal'}) end) it('should be called in setting autoread', function() command('set noar') - expected_combination({'autoread', 1, 0, 'global'}) + expected_combination({'autoread', 1, 1, 1, 0, 'global', 'set'}) command('setlocal ar') - expected_combination({'autoread', 0, 1, 'local'}) + expected_combination({'autoread', 0, 0, '', 1, 'local', 'setlocal'}) end) it('should be called in inverting global autoread', function() command('setglobal invar') - expected_combination({'autoread', 1, 0, 'global'}) + expected_combination({'autoread', 1, '', 1, 0, 'global', 'setglobal'}) end) it('should be called in setting backspace option through :let', function() + local oldval = eval('&backspace') + command('let &bs=""') - expected_combination({'backspace', 'indent,eol,start', '', 'global'}) + expected_combination({'backspace', oldval, oldval, oldval, '', 'global', 'set'}) end) describe('being set by setbufvar()', function() @@ -200,7 +208,7 @@ describe('au OptionSet', function() it('should trigger using correct option name', function() command('call setbufvar(1, "&backup", 1)') - expected_combination({'backup', 0, 1, 'local'}) + expected_combination({'backup', 0, 0, '', 1, 'local', 'setlocal'}) end) it('should trigger if the current buffer is different from the targetted buffer', function() @@ -208,9 +216,339 @@ describe('au OptionSet', function() local new_bufnr = buf.get_number(new_buffer) command('call setbufvar(' .. new_bufnr .. ', "&buftype", "nofile")') - expected_combination({'buftype', '', 'nofile', 'local', {bufnr = new_bufnr}}) + expected_combination({'buftype', '', '', '', 'nofile', 'local', 'setlocal', {bufnr = new_bufnr}}) end) end) + + it('with string global option', function() + local oldval = eval('&backupext') + + command('set backupext=foo') + expected_combination({'backupext', oldval, oldval, oldval, 'foo', 'global', 'set'}) + + command('set backupext&') + expected_combination({'backupext', 'foo', 'foo', 'foo', oldval, 'global', 'set'}) + + command('setglobal backupext=bar') + expected_combination({'backupext', oldval, '', oldval, 'bar', 'global', 'setglobal'}) + + command('noa set backupext&') + -- As this is a global option this sets the global value even though :setlocal is used! + command('setlocal backupext=baz') + expected_combination({'backupext', oldval, oldval, '', 'baz', 'local', 'setlocal'}) + + command('noa setglobal backupext=ext_global') + command('noa setlocal backupext=ext_local') -- Sets the global(!) value + command('set backupext=foo') + expected_combination({ + 'backupext', 'ext_local', 'ext_local', 'ext_local', 'foo', 'global', 'set' + }) + end) + + it('with string global-local (to buffer) option', function() + local oldval = eval('&tags') + + command('set tags=tagpath') + expected_combination({'tags', oldval, oldval, oldval, 'tagpath', 'global', 'set'}) + + command('set tags&') + expected_combination({'tags', 'tagpath', 'tagpath', 'tagpath', oldval, 'global', 'set'}) + + command('setglobal tags=tagpath1') + expected_combination({'tags', oldval, '', oldval, 'tagpath1', 'global', 'setglobal'}) + + command('setlocal tags=tagpath2') + expected_combination({'tags', 'tagpath1', 'tagpath1', '', 'tagpath2', 'local', 'setlocal'}) + + -- Note: v:option_old is the old global value for global-local string options + -- but the old local value for all other kinds of options. + command('noa setglobal tags=tag_global') + command('noa setlocal tags=tag_local') + command('set tags=tagpath') + expected_combination({ + 'tags', 'tag_global', 'tag_local', 'tag_global', 'tagpath', 'global', 'set' + }) + + -- Note: v:option_old is the old global value for global-local string options + -- but the old local value for all other kinds of options. + command('noa set tags=tag_global') + command('noa setlocal tags=') + command('set tags=tagpath') + expected_combination({'tags', 'tag_global', '', 'tag_global', 'tagpath', 'global', 'set'}) + end) + + it('with string local (to buffer) option', function() + local oldval = eval('&spelllang') + + command('set spelllang=elvish,klingon') + expected_combination({'spelllang', oldval, oldval, oldval, 'elvish,klingon', 'global', 'set'}) + + command('set spelllang&') + expected_combination({ + 'spelllang', 'elvish,klingon', 'elvish,klingon', 'elvish,klingon', oldval, 'global', 'set' + }) + + command('setglobal spelllang=elvish') + expected_combination({'spelllang', oldval, '', oldval, 'elvish', 'global', 'setglobal'}) + + command('noa set spelllang&') + command('setlocal spelllang=klingon') + expected_combination({'spelllang', oldval, oldval, '', 'klingon', 'local', 'setlocal'}) + + -- Note: v:option_old is the old global value for global-local string options + -- but the old local value for all other kinds of options. + command('noa setglobal spelllang=spellglobal') + command('noa setlocal spelllang=spelllocal') + command('set spelllang=foo') + expected_combination({ + 'spelllang', 'spelllocal', 'spelllocal', 'spellglobal', 'foo', 'global', 'set' + }) + end) + + it('with string global-local (to window) option', function() + local oldval = eval('&statusline') + + command('set statusline=foo') + expected_combination({'statusline', oldval, oldval, '', 'foo', 'global', 'set'}) + + -- Note: v:option_old is the old global value for global-local string options + -- but the old local value for all other kinds of options. + command('set statusline&') + expected_combination({'statusline', 'foo', 'foo', 'foo', oldval, 'global', 'set'}) + + command('setglobal statusline=bar') + expected_combination({'statusline', oldval, '', oldval, 'bar', 'global', 'setglobal'}) + + command('noa set statusline&') + command('setlocal statusline=baz') + expected_combination({'statusline', oldval, oldval, '', 'baz', 'local', 'setlocal'}) + + -- Note: v:option_old is the old global value for global-local string options + -- but the old local value for all other kinds of options. + command('noa setglobal statusline=bar') + command('noa setlocal statusline=baz') + command('set statusline=foo') + expected_combination({'statusline', 'bar', 'baz', 'bar', 'foo', 'global', 'set'}) + end) + + it('with string local (to window) option', function() + local oldval = eval('&foldignore') + + command('set foldignore=fo') + expected_combination({'foldignore', oldval, oldval, oldval, 'fo', 'global', 'set'}) + + command('set foldignore&') + expected_combination({'foldignore', 'fo', 'fo', 'fo', oldval, 'global', 'set'}) + + command('setglobal foldignore=bar') + expected_combination({'foldignore', oldval, '', oldval, 'bar', 'global', 'setglobal'}) + + command('noa set foldignore&') + command('setlocal foldignore=baz') + expected_combination({'foldignore', oldval, oldval, '', 'baz', 'local', 'setlocal'}) + + command('noa setglobal foldignore=glob') + command('noa setlocal foldignore=loc') + command('set foldignore=fo') + expected_combination({'foldignore', 'loc', 'loc', 'glob', 'fo', 'global', 'set'}) + end) + + it('with number global option', function() + command('noa setglobal cmdheight=8') + command('noa setlocal cmdheight=1') -- Sets the global(!) value + command('setglobal cmdheight=2') + expected_combination({'cmdheight', 1, '', 1, 2, 'global', 'setglobal'}) + + command('noa setglobal cmdheight=8') + command('noa setlocal cmdheight=1') -- Sets the global(!) value + command('setlocal cmdheight=2') + expected_combination({'cmdheight', 1, 1, '', 2, 'local', 'setlocal'}) + + command('noa setglobal cmdheight=8') + command('noa setlocal cmdheight=1') -- Sets the global(!) value + command('set cmdheight=2') + expected_combination({'cmdheight', 1, 1, 1, 2, 'global', 'set'}) + + command('noa set cmdheight=8') + command('set cmdheight=2') + expected_combination({'cmdheight', 8, 8, 8, 2, 'global', 'set'}) + end) + + it('with number global-local (to buffer) option', function() + command('noa setglobal undolevels=8') + command('noa setlocal undolevels=1') + command('setglobal undolevels=2') + expected_combination({'undolevels', 8, '', 8, 2, 'global', 'setglobal'}) + + command('noa setglobal undolevels=8') + command('noa setlocal undolevels=1') + command('setlocal undolevels=2') + expected_combination({'undolevels', 1, 1, '', 2, 'local', 'setlocal'}) + + command('noa setglobal undolevels=8') + command('noa setlocal undolevels=1') + command('set undolevels=2') + expected_combination({'undolevels', 1, 1, 8, 2, 'global', 'set'}) + + command('noa set undolevels=8') + command('set undolevels=2') + expected_combination({'undolevels', 8, 8, 8, 2, 'global', 'set'}) + end) + + it('with number local (to buffer) option', function() + command('noa setglobal wrapmargin=8') + command('noa setlocal wrapmargin=1') + command('setglobal wrapmargin=2') + expected_combination({'wrapmargin', 8, '', 8, 2, 'global', 'setglobal'}) + + command('noa setglobal wrapmargin=8') + command('noa setlocal wrapmargin=1') + command('setlocal wrapmargin=2') + expected_combination({'wrapmargin', 1, 1, '', 2, 'local', 'setlocal'}) + + command('noa setglobal wrapmargin=8') + command('noa setlocal wrapmargin=1') + command('set wrapmargin=2') + expected_combination({'wrapmargin', 1, 1, 8, 2, 'global', 'set'}) + + command('noa set wrapmargin=8') + command('set wrapmargin=2') + expected_combination({'wrapmargin', 8, 8, 8, 2, 'global', 'set'}) + end) + + it('with number global-local (to window) option', function() + command('noa setglobal scrolloff=8') + command('noa setlocal scrolloff=1') + command('setglobal scrolloff=2') + expected_combination({'scrolloff', 8, '', 8, 2, 'global', 'setglobal'}) + + command('noa setglobal scrolloff=8') + command('noa setlocal scrolloff=1') + command('setlocal scrolloff=2') + expected_combination({'scrolloff', 1, 1, '', 2, 'local', 'setlocal'}) + + command('noa setglobal scrolloff=8') + command('noa setlocal scrolloff=1') + command('set scrolloff=2') + expected_combination({'scrolloff', 1, 1, 8, 2, 'global', 'set'}) + + command('noa set scrolloff=8') + command('set scrolloff=2') + expected_combination({'scrolloff', 8, 8, 8, 2, 'global', 'set'}) + end) + + it('with number local (to window) option', function() + command('noa setglobal foldcolumn=8') + command('noa setlocal foldcolumn=1') + command('setglobal foldcolumn=2') + expected_combination({'foldcolumn', 8, '', 8, 2, 'global', 'setglobal'}) + + command('noa setglobal foldcolumn=8') + command('noa setlocal foldcolumn=1') + command('setlocal foldcolumn=2') + expected_combination({'foldcolumn', 1, 1, '', 2, 'local', 'setlocal'}) + + command('noa setglobal foldcolumn=8') + command('noa setlocal foldcolumn=1') + command('set foldcolumn=2') + expected_combination({'foldcolumn', 1, 1, 8, 2, 'global', 'set'}) + + command('noa set foldcolumn=8') + command('set foldcolumn=2') + expected_combination({'foldcolumn', 8, 8, 8, 2, 'global', 'set'}) + end) + + it('with boolean global option', function() + command('noa setglobal nowrapscan') + command('noa setlocal wrapscan') -- Sets the global(!) value + command('setglobal nowrapscan') + expected_combination({'wrapscan', 1, '', 1, 0, 'global', 'setglobal'}) + + command('noa setglobal nowrapscan') + command('noa setlocal wrapscan') -- Sets the global(!) value + command('setlocal nowrapscan') + expected_combination({'wrapscan', 1, 1, '', 0, 'local', 'setlocal'}) + + command('noa setglobal nowrapscan') + command('noa setlocal wrapscan') -- Sets the global(!) value + command('set nowrapscan') + expected_combination({'wrapscan', 1, 1, 1, 0, 'global', 'set'}) + + command('noa set nowrapscan') + command('set wrapscan') + expected_combination({'wrapscan', 0, 0, 0, 1, 'global', 'set'}) + end) + + it('with boolean global-local (to buffer) option', function() + command('noa setglobal noautoread') + command('noa setlocal autoread') + command('setglobal autoread') + expected_combination({'autoread', 0, '', 0, 1, 'global', 'setglobal'}) + + command('noa setglobal noautoread') + command('noa setlocal autoread') + command('setlocal noautoread') + expected_combination({'autoread', 1, 1, '', 0, 'local', 'setlocal'}) + + command('noa setglobal noautoread') + command('noa setlocal autoread') + command('set autoread') + expected_combination({'autoread', 1, 1, 0, 1, 'global', 'set'}) + + command('noa set noautoread') + command('set autoread') + expected_combination({'autoread', 0, 0, 0, 1, 'global', 'set'}) + end) + + it('with boolean local (to buffer) option', function() + command('noa setglobal nocindent') + command('noa setlocal cindent') + command('setglobal cindent') + expected_combination({'cindent', 0, '', 0, 1, 'global', 'setglobal'}) + + command('noa setglobal nocindent') + command('noa setlocal cindent') + command('setlocal nocindent') + expected_combination({'cindent', 1, 1, '', 0, 'local', 'setlocal'}) + + command('noa setglobal nocindent') + command('noa setlocal cindent') + command('set cindent') + expected_combination({'cindent', 1, 1, 0, 1, 'global', 'set'}) + + command('noa set nocindent') + command('set cindent') + expected_combination({'cindent', 0, 0, 0, 1, 'global', 'set'}) + end) + + it('with boolean local (to window) option', function() + command('noa setglobal nocursorcolumn') + command('noa setlocal cursorcolumn') + command('setglobal cursorcolumn') + expected_combination({'cursorcolumn', 0, '', 0, 1, 'global', 'setglobal'}) + + command('noa setglobal nocursorcolumn') + command('noa setlocal cursorcolumn') + command('setlocal nocursorcolumn') + expected_combination({'cursorcolumn', 1, 1, '', 0, 'local', 'setlocal'}) + + command('noa setglobal nocursorcolumn') + command('noa setlocal cursorcolumn') + command('set cursorcolumn') + expected_combination({'cursorcolumn', 1, 1, 0, 1, 'global', 'set'}) + + command('noa set nocursorcolumn') + command('set cursorcolumn') + expected_combination({'cursorcolumn', 0, 0, 0, 1, 'global', 'set'}) + end) + + it('with option value converted internally', function() + command('noa set backspace=1') + command('set backspace=2') + expected_combination(({ + 'backspace', 'indent,eol', 'indent,eol', 'indent,eol', '2', 'global', 'set' + })) + end) end) describe('with specific option', function() @@ -228,13 +566,13 @@ describe('au OptionSet', function() expected_empty() command('setlocal ro') - expected_combination({'readonly', 0, 1, 'local'}) + expected_combination({'readonly', 0, 0, '', 1, 'local', 'setlocal'}) command('setglobal ro') - expected_combination({'readonly', 0, 1, 'global'}) + expected_combination({'readonly', 0, '', 0, 1, 'global', 'setglobal'}) command('set noro') - expected_combination({'readonly', 1, 0, 'global'}) + expected_combination({'readonly', 1, 1, 1, 0, 'global', 'set'}) end) describe('being set by setbufvar()', function() @@ -249,7 +587,7 @@ describe('au OptionSet', function() set_hook('backup') command('call setbufvar(1, "&backup", 1)') - expected_combination({'backup', 0, 1, 'local'}) + expected_combination({'backup', 0, 0, '', 1, 'local', 'setlocal'}) end) it('should trigger if the current buffer is different from the targetted buffer', function() @@ -259,7 +597,7 @@ describe('au OptionSet', function() local new_bufnr = buf.get_number(new_buffer) command('call setbufvar(' .. new_bufnr .. ', "&buftype", "nofile")') - expected_combination({'buftype', '', 'nofile', 'local', {bufnr = new_bufnr}}) + expected_combination({'buftype', '', '', '', 'nofile', 'local', 'setlocal', {bufnr = new_bufnr}}) end) end) @@ -275,7 +613,7 @@ describe('au OptionSet', function() set_hook('backup') command('call setwinvar(1, "&backup", 1)') - expected_combination({'backup', 0, 1, 'local'}) + expected_combination({'backup', 0, 0, '', 1, 'local', 'setlocal'}) end) it('should not trigger if the current window is different from the targetted window', function() @@ -295,7 +633,7 @@ describe('au OptionSet', function() nvim.set_option('autochdir', true) eq(true, nvim.get_option('autochdir')) - expected_combination({'autochdir', '0', '1', 'global'}) + expected_combination({'autochdir', 0, '', 0, 1, 'global', 'setglobal'}) end) it('should trigger if a number option be set globally', function() @@ -303,7 +641,7 @@ describe('au OptionSet', function() nvim.set_option('cmdheight', 5) eq(5, nvim.get_option('cmdheight')) - expected_combination({'cmdheight', 1, 5, 'global'}) + expected_combination({'cmdheight', 1, '', 1, 5, 'global', 'setglobal'}) end) it('should trigger if a string option be set globally', function() @@ -311,7 +649,7 @@ describe('au OptionSet', function() nvim.set_option('ambiwidth', 'double') eq('double', nvim.get_option('ambiwidth')) - expected_combination({'ambiwidth', 'single', 'double', 'global'}) + expected_combination({'ambiwidth', 'single', '', 'single', 'double', 'global', 'setglobal'}) 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/cmdline_spec.lua b/test/functional/legacy/cmdline_spec.lua index 9ebe9aeb91..d8d849271b 100644 --- a/test/functional/legacy/cmdline_spec.lua +++ b/test/functional/legacy/cmdline_spec.lua @@ -11,6 +11,15 @@ describe('cmdline', function() it('is cleared when switching tabs', function() local screen = Screen.new(30, 10) screen:attach() + screen:set_default_attr_ids { + [1] = {underline = true, background = Screen.colors.LightGrey}; + [2] = {bold = true}; + [3] = {reverse = true}; + [4] = {bold = true, foreground = Screen.colors.Blue1}; + } + -- TODO(bfredl): redraw with tabs is severly broken. fix it + feed_command [[ set display-=msgsep ]] + feed_command([[call setline(1, range(30))]]) screen:expect([[ ^0 | @@ -24,18 +33,61 @@ describe('cmdline', function() 8 | :call setline(1, range(30)) | ]]) - feed([[:tabnew<cr><C-w>-<C-w>-gtgt]]) - screen:expect([[ - + [No Name] [No Name] X| + + feed [[:tabnew<cr>]] + screen:expect{grid=[[ + {1: + [No Name] }{2: [No Name] }{3: }{1:X}| + ^ | + {4:~ }| + {4:~ }| + {4:~ }| + {4:~ }| + {4:~ }| + {4:~ }| + {4:~ }| + :tabnew | + ]]} + + feed [[<C-w>-<C-w>-]] + screen:expect{grid=[[ + {1: + [No Name] }{2: [No Name] }{3: }{1:X}| ^ | - ~ | - ~ | - ~ | - ~ | - ~ | + {4:~ }| + {4:~ }| + {4:~ }| + {4:~ }| + {4:~ }| + | + | + :tabnew | + ]]} + + feed [[gt]] + screen:expect{grid=[[ + {2: + [No Name] }{1: [No Name] }{3: }{1:X}| + ^0 | + 1 | + 2 | + 3 | + 4 | + 5 | 6 | 7 | | + ]]} + + feed [[gt]] + screen:expect([[ + {1: + [No Name] }{2: [No Name] }{3: }{1:X}| + ^ | + {4:~ }| + {4:~ }| + {4:~ }| + {4:~ }| + {4:~ }| + | + | + | ]]) end) diff --git a/test/functional/legacy/delete_spec.lua b/test/functional/legacy/delete_spec.lua index 141d9583e6..4ba4c8d356 100644 --- a/test/functional/legacy/delete_spec.lua +++ b/test/functional/legacy/delete_spec.lua @@ -25,25 +25,6 @@ describe('Test for delete()', function() eq(0, eval("isdirectory('Xdir1')")) eq(-1, eval("delete('Xdir1', 'd')")) end) - it('recursive delete', function() - command("call mkdir('Xdir1')") - command("call mkdir('Xdir1/subdir')") - command("call mkdir('Xdir1/empty')") - command('split Xdir1/Xfile') - command("call setline(1, ['a', 'b'])") - command('w') - command('w Xdir1/subdir/Xfile') - command('close') - - eq(1, eval("isdirectory('Xdir1')")) - eq(eval("['a', 'b']"), eval("readfile('Xdir1/Xfile')")) - eq(1, eval("isdirectory('Xdir1/subdir')")) - eq(eval("['a', 'b']"), eval("readfile('Xdir1/subdir/Xfile')")) - eq(1, eval("'Xdir1/empty'->isdirectory()")) - eq(0, eval("delete('Xdir1', 'rf')")) - eq(0, eval("isdirectory('Xdir1')")) - eq(-1, eval("delete('Xdir1', 'd')")) - end) it('symlink delete', function() source([[ @@ -80,45 +61,8 @@ describe('Test for delete()', function() eq(0, eval("delete('Xdir1', 'd')")) end) - it('symlink recursive delete', function() - source([[ - call mkdir('Xdir3') - call mkdir('Xdir3/subdir') - call mkdir('Xdir4') - split Xdir3/Xfile - call setline(1, ['a', 'b']) - w - w Xdir3/subdir/Xfile - w Xdir4/Xfile - close - if has('win32') - silent !mklink /j Xdir3\Xlink Xdir4 - else - silent !ln -s ../Xdir4 Xdir3/Xlink - endif - ]]) - - eq(1, eval("isdirectory('Xdir3')")) - eq(eval("['a', 'b']"), eval("readfile('Xdir3/Xfile')")) - eq(1, eval("isdirectory('Xdir3/subdir')")) - eq(eval("['a', 'b']"), eval("readfile('Xdir3/subdir/Xfile')")) - eq(1, eval("isdirectory('Xdir4')")) - eq(1, eval("isdirectory('Xdir3/Xlink')")) - eq(eval("['a', 'b']"), eval("readfile('Xdir4/Xfile')")) - - eq(0, eval("delete('Xdir3', 'rf')")) - eq(0, eval("isdirectory('Xdir3')")) - eq(-1, eval("delete('Xdir3', 'd')")) - -- symlink is deleted, not the directory it points to - eq(1, eval("isdirectory('Xdir4')")) - eq(eval("['a', 'b']"), eval("readfile('Xdir4/Xfile')")) - eq(0, eval("delete('Xdir4/Xfile')")) - eq(0, eval("delete('Xdir4', 'd')")) - end) - it('gives correct emsgs', function() eq('Vim(call):E474: Invalid argument', exc_exec("call delete('')")) - eq('Vim(call):E15: Invalid expression: 0', - exc_exec("call delete('foo', 0)")) + eq('Vim(call):E15: Invalid expression: 0', exc_exec("call delete('foo', 0)")) end) end) diff --git a/test/functional/legacy/display_spec.lua b/test/functional/legacy/display_spec.lua index 3fbbe96947..4c7915403c 100644 --- a/test/functional/legacy/display_spec.lua +++ b/test/functional/legacy/display_spec.lua @@ -2,23 +2,21 @@ local helpers = require('test.functional.helpers')(after_each) local Screen = require('test.functional.ui.screen') local clear = helpers.clear -local poke_eventloop = helpers.poke_eventloop +local exec = helpers.exec local feed = helpers.feed -local feed_command = helpers.feed_command +local command = helpers.command describe('display', function() - local screen + before_each(clear) - it('scroll when modified at topline', function() - clear() - screen = Screen.new(20, 4) + it('scroll when modified at topline vim-patch:8.2.1488', function() + local screen = Screen.new(20, 4) screen:attach() screen:set_default_attr_ids({ [1] = {bold = true}, }) - feed_command([[call setline(1, repeat('a', 21))]]) - poke_eventloop() + command([[call setline(1, repeat('a', 21))]]) feed('O') screen:expect([[ ^ | @@ -27,5 +25,79 @@ describe('display', function() {1:-- INSERT --} | ]]) end) -end) + it('scrolling when modified at topline in Visual mode vim-patch:8.2.4626', function() + local screen = Screen.new(60, 8) + screen:attach() + screen:set_default_attr_ids({ + [1] = {bold = true}, -- ModeMsg + [2] = {background = Screen.colors.LightGrey}, -- Visual + [3] = {background = Screen.colors.Grey, foreground = Screen.colors.DarkBlue}, -- SignColumn + }) + + exec([[ + set scrolloff=0 + call setline(1, repeat(['foo'], 10)) + call sign_define('foo', { 'text': '>' }) + call sign_place(1, 'bar', 'foo', bufnr(), { 'lnum': 2 }) + call sign_place(2, 'bar', 'foo', bufnr(), { 'lnum': 1 }) + autocmd CursorMoved * if getcurpos()[1] == 2 | call sign_unplace('bar', { 'id': 1 }) | endif + ]]) + feed('VG7kk') + screen:expect([[ + {3: }^f{2:oo} | + {3: }foo | + {3: }foo | + {3: }foo | + {3: }foo | + {3: }foo | + {3: }foo | + {1:-- VISUAL LINE --} | + ]]) + end) + + it('@@@ in the last line shows correctly in a narrow window vim-patch:8.2.4718', function() + local screen = Screen.new(60, 10) + screen:set_default_attr_ids({ + [1] = {bold = true, foreground = Screen.colors.Blue}, -- NonText + [2] = {bold = true, reverse = true}, -- StatusLine + [3] = {reverse = true}, -- VertSplit, StatusLineNC + }) + screen:attach() + exec([[ + call setline(1, ['aaa', 'b'->repeat(100)]) + set display=truncate + vsplit + 100wincmd < + ]]) + screen:expect([[ + ^a{3:│}aaa | + a{3:│}bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb| + a{3:│}bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb | + b{3:│}{1:~ }| + b{3:│}{1:~ }| + b{3:│}{1:~ }| + b{3:│}{1:~ }| + {1:@}{3:│}{1:~ }| + {2:< }{3:[No Name] [+] }| + | + ]]) + command('set display=lastline') + screen:expect_unchanged() + command('100wincmd >') + screen:expect([[ + ^aaa {3:│}a| + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb{3:│}a| + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb {3:│}a| + {1:~ }{3:│}b| + {1:~ }{3:│}b| + {1:~ }{3:│}b| + {1:~ }{3:│}b| + {1:~ }{3:│}{1:@}| + {2:[No Name] [+] }{3:<}| + | + ]]) + command('set display=truncate') + screen:expect_unchanged() + end) +end) diff --git a/test/functional/legacy/eval_spec.lua b/test/functional/legacy/eval_spec.lua index b5de5cd232..d3c0b4b938 100644 --- a/test/functional/legacy/eval_spec.lua +++ b/test/functional/legacy/eval_spec.lua @@ -46,7 +46,7 @@ describe('eval', function() command('AR "') command([[let @" = "abc\n"]]) source('AR "') - command([[let @" = "abc\<C-m>"]]) + command([[let @" = "abc\r"]]) command('AR "') command([[let @= = '"abc"']]) command('AR =') @@ -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/file_perm_spec.lua b/test/functional/legacy/file_perm_spec.lua deleted file mode 100644 index ccdbfe0534..0000000000 --- a/test/functional/legacy/file_perm_spec.lua +++ /dev/null @@ -1,43 +0,0 @@ --- Test getting and setting file permissions. -require('os') - -local helpers = require('test.functional.helpers')(after_each) -local clear, call, eq = helpers.clear, helpers.call, helpers.eq -local neq, exc_exec, eval = helpers.neq, helpers.exc_exec, helpers.eval - -describe('Test getting and setting file permissions', function() - local tempfile = helpers.tmpname() - - before_each(function() - os.remove(tempfile) - clear() - end) - - it('file permissions', function() - -- eval() is used to test VimL method syntax for setfperm() and getfperm() - eq('', call('getfperm', tempfile)) - eq(0, eval("'" .. tempfile .. "'->setfperm('r--------')")) - - call('writefile', {'one'}, tempfile) - eq(9, eval("len('" .. tempfile .. "'->getfperm())")) - - eq(1, call('setfperm', tempfile, 'rwx------')) - if helpers.is_os('win') then - eq('rw-rw-rw-', call('getfperm', tempfile)) - else - eq('rwx------', call('getfperm', tempfile)) - end - - eq(1, call('setfperm', tempfile, 'r--r--r--')) - eq('r--r--r--', call('getfperm', tempfile)) - - local err = exc_exec(('call setfperm("%s", "---")'):format(tempfile)) - neq(err:find('E475:'), nil) - - eq(1, call('setfperm', tempfile, 'rwx------')) - end) - - after_each(function() - os.remove(tempfile) - 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/fnamemodify_spec.lua b/test/functional/legacy/fnamemodify_spec.lua index 6a5538c26f..6262db3a2f 100644 --- a/test/functional/legacy/fnamemodify_spec.lua +++ b/test/functional/legacy/fnamemodify_spec.lua @@ -29,7 +29,7 @@ describe('filename modifiers', function() call assert_equal('test.out', fnamemodify('test.out', ':.')) call assert_equal('../testdir/a', fnamemodify('../testdir/a', ':.')) call assert_equal(fnamemodify(tmpdir, ':~').'/test.out', fnamemodify('test.out', ':~')) - call assert_equal('../testdir/a', fnamemodify('../testdir/a', ':~')) + call assert_equal(fnamemodify(tmpdir, ':~').'/../testdir/a', fnamemodify('../testdir/a', ':~')) call assert_equal('a', fnamemodify('../testdir/a', ':t')) call assert_equal('', fnamemodify('.', ':p:t')) call assert_equal('test.out', fnamemodify('test.out', ':p:t')) diff --git a/test/functional/legacy/listchars_spec.lua b/test/functional/legacy/listchars_spec.lua index dc6ccd3628..206e226767 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,126 @@ 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:set_default_attr_ids({ + [1] = {bold = true, foreground = Screen.colors.Blue}, -- NonText + [2] = {bold = true, reverse = true}, -- StatusLine + [3] = {reverse = true}, -- StatusLineNC, VertSplit + [4] = {background = Screen.colors.Grey, foreground = Screen.colors.DarkBlue}, -- FoldColumn, SignColumn + }) + 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([[ + {4: }aaa {3:│}{4: }a{1:>}{3:│}{4: }^aaa | + {4: } {3:│}{4: } {3:│}{4: } | + {4: }a {3:│}{4: }a {3:│}{4: }a | + {4: }aaaaaa {3:│}{4: }a{1:>}{3:│}{4: }aaaaaa | + {1:~ }{3:│}{1:~ }{3:│}{1:~ }| + {1:~ }{3:│}{1:~ }{3:│}{1:~ }| + {1:~ }{3:│}{1:~ }{3:│}{1:~ }| + {1:~ }{3:│}{1:~ }{3:│}{1:~ }| + {3:[No Name] [+] <[+] }{2:[No Name] [+] }| + | + ]]) + feed('<C-W>>') + screen:expect([[ + {4: }aaa {3:│}{4: }{1:>}{3:│}{4: }^aaa | + {4: } {3:│}{4: } {3:│}{4: } | + {4: }a {3:│}{4: }a{3:│}{4: }a | + {4: }aaaaaa {3:│}{4: }{1:>}{3:│}{4: }aaaaaa | + {1:~ }{3:│}{1:~ }{3:│}{1:~ }| + {1:~ }{3:│}{1:~ }{3:│}{1:~ }| + {1:~ }{3:│}{1:~ }{3:│}{1:~ }| + {1:~ }{3:│}{1:~ }{3:│}{1:~ }| + {3:[No Name] [+] <+] }{2:[No Name] [+] }| + | + ]]) + feed('<C-W>>') + screen:expect([[ + {4: }aaa {3:│}{4: }{3:│}{4: }^aaa | + {4: } {3:│}{4: }{3:│}{4: } | + {4: }a {3:│}{4: }{3:│}{4: }a | + {4: }aaaaaa {3:│}{4: }{3:│}{4: }aaaaaa | + {1:~ }{3:│}{1:~ }{3:│}{1:~ }| + {1:~ }{3:│}{1:~ }{3:│}{1:~ }| + {1:~ }{3:│}{1:~ }{3:│}{1:~ }| + {1:~ }{3:│}{1:~ }{3:│}{1:~ }| + {3:[No Name] [+] <] }{2:[No Name] [+] }| + | + ]]) + feed('<C-W>>') + screen:expect([[ + {4: }aaa {3:│}{4: }{3:│}{4: }^aaa | + {4: } {3:│}{4: }{3:│}{4: } | + {4: }a {3:│}{4: }{3:│}{4: }a | + {4: }aaaaaa {3:│}{4: }{3:│}{4: }aaaaaa | + {1:~ }{3:│}{1:~ }{3:│}{1:~ }| + {1:~ }{3:│}{1:~ }{3:│}{1:~ }| + {1:~ }{3:│}{1:~ }{3:│}{1:~ }| + {1:~ }{3:│}{1:~ }{3:│}{1:~ }| + {3:[No Name] [+] < }{2:[No Name] [+] }| + | + ]]) + feed('<C-W>>') + screen:expect([[ + {4: }aaa {3:│}{4: }{3:│}{4: }^aaa | + {4: } {3:│}{4: }{3:│}{4: } | + {4: }a {3:│}{4: }{3:│}{4: }a | + {4: }aaaaaa {3:│}{4: }{3:│}{4: }aaaaaa | + {1:~ }{3:│}{1:~}{3:│}{1:~ }| + {1:~ }{3:│}{1:~}{3:│}{1:~ }| + {1:~ }{3:│}{1:~}{3:│}{1:~ }| + {1:~ }{3:│}{1:~}{3:│}{1:~ }| + {3:[No Name] [+] < }{2:[No Name] [+] }| + | + ]]) + feed('<C-W>h') + feed_command('set nowrap foldcolumn=4') + screen:expect([[ + {4: }aaa {3:│}{4: }^aaa {3:│}{4: }aaa | + {4: } {3:│}{4: } {3:│}{4: } | + {4: }a {3:│}{4: }a {3:│}{4: }a | + {4: }aaaaaa {3:│}{4: }aaaaaa {3:│}{4: }aaaaaa | + {1:~ }{3:│}{1:~ }{3:│}{1:~ }| + {1:~ }{3:│}{1:~ }{3:│}{1:~ }| + {1:~ }{3:│}{1:~ }{3:│}{1:~ }| + {1:~ }{3:│}{1:~ }{3:│}{1:~ }| + {3:[No Name] [+] }{2:[No Name] [+] }{3:[No Name] [+] }| + :set nowrap foldcolumn=4 | + ]]) + feed('15<C-W><lt>') + screen:expect([[ + {4: }aaa {3:│}{4: }{3:│}{4: }aaa | + {4: } {3:│}{4: }{3:│}{4: } | + {4: }a {3:│}{4: }{3:│}{4: }a | + {4: }aaaaaa {3:│}{4: ^ }{3:│}{4: }aaaaaa | + {1:~ }{3:│}{1:~ }{3:│}{1:~ }| + {1:~ }{3:│}{1:~ }{3:│}{1:~ }| + {1:~ }{3:│}{1:~ }{3:│}{1:~ }| + {1:~ }{3:│}{1:~ }{3:│}{1:~ }| + {3:[No Name] [+] }{2:<[+] }{3:[No Name] [+] }| + :set nowrap foldcolumn=4 | + ]]) + feed('4<C-W><lt>') + screen:expect([[ + {4: }aaa {3:│}{4: }{3:│}{4: }aaa | + {4: } {3:│}{4: }{3:│}{4: } | + {4: }a {3:│}{4: }{3:│}{4: }a | + {4: }aaaaaa {3:│}{4:^ }{3:│}{4: }aaaaaa | + {1:~ }{3:│}{1:~}{3:│}{1:~ }| + {1:~ }{3:│}{1:~}{3:│}{1:~ }| + {1:~ }{3:│}{1:~}{3:│}{1:~ }| + {1:~ }{3:│}{1:~}{3:│}{1:~ }| + {3:[No Name] [+] }{2:< }{3:[No Name] [+] }| + :set nowrap foldcolumn=4 | + ]]) + end) end) diff --git a/test/functional/legacy/mapping_spec.lua b/test/functional/legacy/mapping_spec.lua index 92a757ca85..aa29698589 100644 --- a/test/functional/legacy/mapping_spec.lua +++ b/test/functional/legacy/mapping_spec.lua @@ -3,6 +3,8 @@ local helpers = require('test.functional.helpers')(after_each) local clear, feed, insert = helpers.clear, helpers.feed, helpers.insert local feed_command, expect, poke_eventloop = helpers.feed_command, helpers.expect, helpers.poke_eventloop +local command, eq, eval, meths = helpers.command, helpers.eq, helpers.eval, helpers.meths +local sleep = helpers.sleep describe('mapping', function() before_each(clear) @@ -126,4 +128,28 @@ describe('mapping', function() new line here ]]) end) + + it('<LeftDrag> mapping in Insert mode works correctly vim-patch:8.2.4692', function() + command('set mouse=a') + + command([[inoremap <LeftDrag> <LeftDrag><Cmd>let g:dragged = 1<CR>]]) + feed('i') + sleep(10) + meths.input_mouse('left', 'press', '', 0, 0, 0) + sleep(10) + meths.input_mouse('left', 'drag', '', 0, 0, 1) + sleep(10) + eq(1, eval('g:dragged')) + eq('v', eval('mode()')) + feed([[<C-\><C-N>]]) + + command([[inoremap <LeftDrag> <LeftDrag><C-\><C-N>]]) + feed('i') + sleep(10) + meths.input_mouse('left', 'press', '', 0, 0, 0) + sleep(10) + meths.input_mouse('left', 'drag', '', 0, 0, 1) + sleep(10) + eq('n', eval('mode()')) + end) end) diff --git a/test/functional/legacy/memory_usage_spec.lua b/test/functional/legacy/memory_usage_spec.lua index d86caca0e9..eec89aa919 100644 --- a/test/functional/legacy/memory_usage_spec.lua +++ b/test/functional/legacy/memory_usage_spec.lua @@ -10,6 +10,7 @@ local source = helpers.source local poke_eventloop = helpers.poke_eventloop local uname = helpers.uname local load_adjust = helpers.load_adjust +local write_file = helpers.write_file local isCI = helpers.isCI local function isasan() @@ -84,6 +85,12 @@ setmetatable(monitor_memory_usage, end}) describe('memory usage', function() + local tmpfile = 'X_memory_usage' + + after_each(function() + os.remove(tmpfile) + end) + local function check_result(tbl, status, result) if not status then print('') @@ -103,7 +110,7 @@ describe('memory usage', function() it('function capture vargs', function() local pid = eval('getpid()') local before = monitor_memory_usage(pid) - source([[ + write_file(tmpfile, [[ func s:f(...) let x = a:000 endfunc @@ -111,6 +118,8 @@ describe('memory usage', function() call s:f(0) endfor ]]) + -- TODO: check_result fails if command() is used here. Why? #16064 + feed_command('source '..tmpfile) poke_eventloop() local after = monitor_memory_usage(pid) -- Estimate the limit of max usage as 2x initial usage. @@ -136,7 +145,7 @@ describe('memory usage', function() it('function capture lvars', function() local pid = eval('getpid()') local before = monitor_memory_usage(pid) - local fname = source([[ + write_file(tmpfile, [[ if !exists('s:defined_func') func s:f() let x = l: @@ -147,10 +156,12 @@ describe('memory usage', function() call s:f() endfor ]]) + feed_command('source '..tmpfile) poke_eventloop() local after = monitor_memory_usage(pid) for _ = 1, 3 do - feed_command('so '..fname) + -- TODO: check_result fails if command() is used here. Why? #16064 + feed_command('source '..tmpfile) poke_eventloop() end local last = monitor_memory_usage(pid) @@ -195,10 +206,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/messages_spec.lua b/test/functional/legacy/messages_spec.lua new file mode 100644 index 0000000000..34807a099c --- /dev/null +++ b/test/functional/legacy/messages_spec.lua @@ -0,0 +1,71 @@ +local helpers = require('test.functional.helpers')(after_each) +local Screen = require('test.functional.ui.screen') +local clear = helpers.clear +local command = helpers.command +local exec = helpers.exec +local feed = helpers.feed + +before_each(clear) + +describe('messages', function() + it('more prompt with control characters can be quit vim-patch:8.2.1844', function() + local screen = Screen.new(40, 6) + screen:set_default_attr_ids({ + [1] = {foreground = Screen.colors.Blue}, -- SpecialKey + [2] = {bold = true, foreground = Screen.colors.SeaGreen}, -- MoreMsg + [3] = {bold = true, foreground = Screen.colors.Blue}, -- NonText + }) + screen:attach() + command('set more') + feed([[:echom range(9999)->join("\x01")<CR>]]) + screen:expect([[ + 0{1:^A}1{1:^A}2{1:^A}3{1:^A}4{1:^A}5{1:^A}6{1:^A}7{1:^A}8{1:^A}9{1:^A}10{1:^A}11{1:^A}12| + {1:^A}13{1:^A}14{1:^A}15{1:^A}16{1:^A}17{1:^A}18{1:^A}19{1:^A}20{1:^A}21{1:^A}22| + {1:^A}23{1:^A}24{1:^A}25{1:^A}26{1:^A}27{1:^A}28{1:^A}29{1:^A}30{1:^A}31{1:^A}32| + {1:^A}33{1:^A}34{1:^A}35{1:^A}36{1:^A}37{1:^A}38{1:^A}39{1:^A}40{1:^A}41{1:^A}42| + {1:^A}43{1:^A}44{1:^A}45{1:^A}46{1:^A}47{1:^A}48{1:^A}49{1:^A}50{1:^A}51{1:^A}52| + {2:-- More --}^ | + ]]) + feed('q') + screen:expect([[ + ^ | + {3:~ }| + {3:~ }| + {3:~ }| + {3:~ }| + | + ]]) + end) + + it('fileinfo does not overwrite echo message vim-patch:8.2.4156', function() + local screen = Screen.new(40, 6) + screen:set_default_attr_ids({ + [1] = {bold = true, foreground = Screen.colors.Blue}, -- NonText + }) + screen:attach() + exec([[ + set shortmess-=F + + file a.txt + + hide edit b.txt + call setline(1, "hi") + setlocal modified + + hide buffer a.txt + + autocmd CursorHold * buf b.txt | w | echo "'b' written" + ]]) + command('set updatetime=50') + feed('0$') + screen:expect([[ + ^hi | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + 'b' written | + ]]) + os.remove('b.txt') + end) +end) diff --git a/test/functional/legacy/options_spec.lua b/test/functional/legacy/options_spec.lua index 023cdd4ae1..bd14f3bc53 100644 --- a/test/functional/legacy/options_spec.lua +++ b/test/functional/legacy/options_spec.lua @@ -83,4 +83,9 @@ describe('set', function() Press ENTER or type command to continue^ | ]]) end) + + it('foldcolumn and signcolumn to empty string is disallowed', function() + matches('E474: Invalid argument: fdc=', exc_exec('set fdc=')) + matches('E474: Invalid argument: scl=', exc_exec('set scl=')) + 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/prompt_buffer_spec.lua b/test/functional/legacy/prompt_buffer_spec.lua index 513be807be..47eca19de3 100644 --- a/test/functional/legacy/prompt_buffer_spec.lua +++ b/test/functional/legacy/prompt_buffer_spec.lua @@ -1,9 +1,12 @@ local helpers = require('test.functional.helpers')(after_each) local Screen = require('test.functional.ui.screen') -local feed= helpers.feed +local feed = helpers.feed local source = helpers.source local clear = helpers.clear local feed_command = helpers.feed_command +local poke_eventloop = helpers.poke_eventloop +local meths = helpers.meths +local eq = helpers.eq describe('prompt buffer', function() local screen @@ -28,12 +31,17 @@ describe('prompt buffer', function() func TimerFunc(text) call append(line("$") - 1, 'Result: "' . a:text .'"') endfunc + + func SwitchWindows() + call timer_start(0, {-> execute("wincmd p|wincmd p", "")}) + endfunc ]]) feed_command("set noshowmode | set laststatus=0") feed_command("call setline(1, 'other buffer')") feed_command("new") feed_command("set buftype=prompt") feed_command("call prompt_setcallback(bufnr(''), function('TextEntered'))") + feed_command("eval bufnr('')->prompt_setprompt('cmd: ')") end) after_each(function() @@ -56,10 +64,10 @@ describe('prompt buffer', function() feed("i") feed("hello\n") screen:expect([[ - % hello | + cmd: hello | Command: "hello" | Result: "hello" | - % ^ | + cmd: ^ | [Prompt] [+] | other buffer | ~ | @@ -98,7 +106,7 @@ describe('prompt buffer', function() feed("i") feed("hello<BS><BS>") screen:expect([[ - % hel^ | + cmd: hel^ | ~ | ~ | ~ | @@ -111,7 +119,20 @@ describe('prompt buffer', function() ]]) feed("<Left><Left><Left><BS>-") screen:expect([[ - % -^hel | + cmd: -^hel | + ~ | + ~ | + ~ | + [Prompt] [+] | + other buffer | + ~ | + ~ | + ~ | + | + ]]) + feed("<C-O>lz") + screen:expect([[ + cmd: -hz^el | ~ | ~ | ~ | @@ -124,7 +145,7 @@ describe('prompt buffer', function() ]]) feed("<End>x") screen:expect([[ - % -helx^ | + cmd: -hzelx^ | ~ | ~ | ~ | @@ -150,4 +171,58 @@ describe('prompt buffer', function() ]]) end) + it('switch windows', function() + feed_command("set showmode") + feed("i") + screen:expect([[ + cmd: ^ | + ~ | + ~ | + ~ | + [Prompt] [+] | + other buffer | + ~ | + ~ | + ~ | + -- INSERT -- | + ]]) + feed("<C-O>:call SwitchWindows()<CR>") + poke_eventloop() + screen:expect([[ + cmd: ^ | + ~ | + ~ | + ~ | + [Prompt] [+] | + other buffer | + ~ | + ~ | + ~ | + -- INSERT -- | + ]]) + feed("<Esc>") + poke_eventloop() + screen:expect([[ + cmd:^ | + ~ | + ~ | + ~ | + [Prompt] [+] | + other buffer | + ~ | + ~ | + ~ | + | + ]]) + end) + + it('keeps insert mode after aucmd_restbuf in callback', function() + source [[ + let s:buf = nvim_create_buf(1, 1) + call timer_start(0, {-> nvim_buf_set_lines(s:buf, -1, -1, 0, ['walrus'])}) + startinsert + ]] + poke_eventloop() + eq({ mode = "i", blocking = false }, meths.get_mode()) + 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/searchpos_spec.lua b/test/functional/legacy/searchpos_spec.lua deleted file mode 100644 index 60f1edcd3f..0000000000 --- a/test/functional/legacy/searchpos_spec.lua +++ /dev/null @@ -1,35 +0,0 @@ -local helpers = require('test.functional.helpers')(after_each) -local call = helpers.call -local clear = helpers.clear -local command = helpers.command -local eq = helpers.eq -local eval = helpers.eval -local insert = helpers.insert - -describe('searchpos', function() - before_each(clear) - - it('is working', function() - insert([[ - 1a3 - 123xyz]]) - - call('cursor', 1, 1) - eq({1, 1, 2}, eval([[searchpos('\%(\([a-z]\)\|\_.\)\{-}xyz', 'pcW')]])) - call('cursor', 1, 2) - eq({2, 1, 1}, eval([[searchpos('\%(\([a-z]\)\|\_.\)\{-}xyz', 'pcW')]])) - - command('set cpo-=c') - call('cursor', 1, 2) - eq({1, 2, 2}, eval([[searchpos('\%(\([a-z]\)\|\_.\)\{-}xyz', 'pcW')]])) - call('cursor', 1, 3) - eq({1, 3, 1}, eval([[searchpos('\%(\([a-z]\)\|\_.\)\{-}xyz', 'pcW')]])) - - -- Now with \zs, first match is in column 0, "a" is matched. - call('cursor', 1, 3) - eq({2, 4, 2}, eval([[searchpos('\%(\([a-z]\)\|\_.\)\{-}\zsxyz', 'pcW')]])) - -- With z flag start at cursor column, don't see the "a". - call('cursor', 1, 3) - eq({2, 4, 1}, eval([[searchpos('\%(\([a-z]\)\|\_.\)\{-}\zsxyz', 'pcWz')]])) - end) -end) diff --git a/test/functional/legacy/set_spec.lua b/test/functional/legacy/set_spec.lua deleted file mode 100644 index deb268b1e8..0000000000 --- a/test/functional/legacy/set_spec.lua +++ /dev/null @@ -1,30 +0,0 @@ --- Tests for :set - -local helpers = require('test.functional.helpers')(after_each) -local clear, command, eval, eq = - helpers.clear, helpers.command, helpers.eval, helpers.eq - -describe(':set', function() - before_each(clear) - - it('handles backslash properly', function() - command('set iskeyword=a,b,c') - command('set iskeyword+=d') - eq('a,b,c,d', eval('&iskeyword')) - - command([[set iskeyword+=\\,e]]) - eq([[a,b,c,d,\,e]], eval('&iskeyword')) - - command('set iskeyword-=e') - eq([[a,b,c,d,\]], eval('&iskeyword')) - - command([[set iskeyword-=\]]) - eq('a,b,c,d', eval('&iskeyword')) - end) - - it('recognizes a trailing comma with +=', function() - command('set wildignore=*.png,') - command('set wildignore+=*.jpg') - eq('*.png,*.jpg', eval('&wildignore')) - 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/buffer_updates_spec.lua b/test/functional/lua/buffer_updates_spec.lua index c83a50b78b..cbd78ccd53 100644 --- a/test/functional/lua/buffer_updates_spec.lua +++ b/test/functional/lua/buffer_updates_spec.lua @@ -1104,6 +1104,15 @@ describe('lua: nvim_buf_attach on_bytes', function() check_events { } end) + it("works with accepting spell suggestions", function() + local check_events = setup_eventcheck(verify, {"hallo"}) + + feed("gg0z=4<cr><cr>") -- accepts 'Hello' + check_events { + { "test1", "bytes", 1, 3, 0, 0, 0, 0, 2, 2, 0, 2, 2 }; + } + end) + local function test_lockmarks(mode) local description = (mode ~= "") and mode or "(baseline)" it("test_lockmarks " .. description .. " %delete _", function() diff --git a/test/functional/lua/command_line_completion_spec.lua b/test/functional/lua/command_line_completion_spec.lua index 3ba7e1589f..3a5966755e 100644 --- a/test/functional/lua/command_line_completion_spec.lua +++ b/test/functional/lua/command_line_completion_spec.lua @@ -106,6 +106,14 @@ describe('nlua_expand_pat', function() ) end) + it('should work with lazy submodules of "vim" global', function() + eq({{ 'inspect' }, 4 }, + get_completions('vim.inspec')) + + eq({{ 'set' }, 11 }, + get_completions('vim.keymap.se')) + end) + it('should be able to interpolate globals', function() eq( {{ 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 6414483c0d..b58fad1cab 100644 --- a/test/functional/lua/diagnostic_spec.lua +++ b/test/functional/lua/diagnostic_spec.lua @@ -110,20 +110,35 @@ describe('vim.diagnostic', function() it('retrieves diagnostics from all buffers and namespaces', function() local result = exec_lua [[ - vim.diagnostic.set(diagnostic_ns, 1, { + local other_bufnr = vim.api.nvim_create_buf(true, false) + local lines = vim.api.nvim_buf_get_lines(diagnostic_bufnr, 0, -1, true) + vim.api.nvim_buf_set_lines(other_bufnr, 0, 1, false, lines) + + vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, { make_error('Diagnostic #1', 1, 1, 1, 1), make_error('Diagnostic #2', 2, 1, 2, 1), }) - vim.diagnostic.set(other_ns, 2, { + vim.diagnostic.set(other_ns, other_bufnr, { make_error('Diagnostic #3', 3, 1, 3, 1), }) return vim.diagnostic.get() ]] eq(3, #result) - eq(2, exec_lua([[return #vim.tbl_filter(function(d) return d.bufnr == 1 end, ...)]], result)) + eq(2, exec_lua([[return #vim.tbl_filter(function(d) return d.bufnr == diagnostic_bufnr end, ...)]], result)) eq('Diagnostic #1', result[1].message) end) + it('resolves buffer number 0 to the current buffer', function() + eq(2, exec_lua [[ + vim.api.nvim_set_current_buf(diagnostic_bufnr) + vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, { + make_error('Diagnostic #1', 1, 1, 1, 1), + make_error('Diagnostic #2', 2, 1, 2, 1), + }) + return #vim.diagnostic.get(0) + ]]) + end) + it('saves and count a single error', function() eq(1, exec_lua [[ vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, { @@ -193,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) @@ -240,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) @@ -339,6 +354,16 @@ describe('vim.diagnostic', function() eq(0, result[5]) eq(3, result[6]) end) + + it("doesn't error after bwipeout on buffer", function() + exec_lua [[ + vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, {{ lnum = 0, end_lnum = 0, col = 0, end_col = 0 }}) + vim.cmd("bwipeout! " .. diagnostic_bufnr) + + vim.diagnostic.show(diagnostic_ns) + vim.diagnostic.hide(diagnostic_ns) + ]] + end) end) describe('enable() and disable()', function() @@ -574,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) @@ -625,6 +650,15 @@ describe('vim.diagnostic', function() ]]) end) + + it("doesn't error after bwipeout called on buffer", function() + exec_lua [[ + vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, {{ lnum = 0, end_lnum = 0, col = 0, end_col = 0 }}) + vim.cmd("bwipeout! " .. diagnostic_bufnr) + + vim.diagnostic.reset(diagnostic_ns) + ]] + end) end) describe('get_next_pos()', function() @@ -682,6 +716,19 @@ describe('vim.diagnostic', function() return vim.diagnostic.get_prev_pos { namespace = diagnostic_ns } ]]) end) + + it('works with diagnostics past the end of the line #16349', function() + eq({4, 0}, exec_lua [[ + vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, { + make_error('Diagnostic #1', 3, 9001, 3, 9001), + make_error('Diagnostic #2', 4, 0, 4, 0), + }) + vim.api.nvim_win_set_buf(0, diagnostic_bufnr) + vim.api.nvim_win_set_cursor(0, {1, 1}) + vim.diagnostic.goto_next { float = false } + return vim.diagnostic.get_next_pos { namespace = diagnostic_ns } + ]]) + end) end) describe('get_prev_pos()', function() @@ -740,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) @@ -751,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 { @@ -773,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), }) @@ -878,7 +925,7 @@ describe('vim.diagnostic', function() ]] eq(1, exec_lua [[return count_diagnostics(diagnostic_bufnr, vim.diagnostic.severity.ERROR, diagnostic_ns)]]) - -- eq(1, exec_lua [[return count_extmarks(diagnostic_bufnr, diagnostic_ns)]]) + eq(1, exec_lua [[return count_extmarks(diagnostic_bufnr, diagnostic_ns)]]) end) it('allows filtering by severity', function() @@ -1072,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") @@ -1296,7 +1348,7 @@ describe('vim.diagnostic', function() } vim.api.nvim_win_set_buf(0, diagnostic_bufnr) vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, diagnostics) - local float_bufnr, winnr = vim.diagnostic.open_float(0, {header = "We're no strangers to love..."}) + local float_bufnr, winnr = vim.diagnostic.open_float({header = "We're no strangers to love..."}) local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false) vim.api.nvim_win_close(winnr, true) return lines @@ -1308,7 +1360,7 @@ describe('vim.diagnostic', function() } vim.api.nvim_win_set_buf(0, diagnostic_bufnr) vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, diagnostics) - local float_bufnr, winnr = vim.diagnostic.open_float(0, {header = {'You know the rules', 'Search'}}) + local float_bufnr, winnr = vim.diagnostic.open_float({header = {'You know the rules', 'Search'}}) local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false) vim.api.nvim_win_close(winnr, true) return lines @@ -1323,7 +1375,7 @@ describe('vim.diagnostic', function() } vim.api.nvim_win_set_buf(0, diagnostic_bufnr) vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, diagnostics) - local float_bufnr, winnr = vim.diagnostic.open_float(0, {header = false}) + local float_bufnr, winnr = vim.diagnostic.open_float({header = false, scope="buffer"}) local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false) vim.api.nvim_win_close(winnr, true) return lines @@ -1340,7 +1392,7 @@ describe('vim.diagnostic', function() vim.api.nvim_win_set_buf(0, diagnostic_bufnr) vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, diagnostics) vim.api.nvim_win_set_cursor(0, {2, 1}) - local float_bufnr, winnr = vim.diagnostic.open_float(0, {header=false, scope="line"}) + local float_bufnr, winnr = vim.diagnostic.open_float({header=false}) local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false) vim.api.nvim_win_close(winnr, true) return lines @@ -1355,7 +1407,7 @@ describe('vim.diagnostic', function() vim.api.nvim_win_set_buf(0, diagnostic_bufnr) vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, diagnostics) vim.api.nvim_win_set_cursor(0, {1, 1}) - local float_bufnr, winnr = vim.diagnostic.open_float(0, {header=false, scope="line", pos=1}) + local float_bufnr, winnr = vim.diagnostic.open_float({header=false, pos=1}) local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false) vim.api.nvim_win_close(winnr, true) return lines @@ -1372,7 +1424,7 @@ describe('vim.diagnostic', function() vim.api.nvim_win_set_buf(0, diagnostic_bufnr) vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, diagnostics) vim.api.nvim_win_set_cursor(0, {2, 2}) - local float_bufnr, winnr = vim.diagnostic.open_float(0, {header=false, scope="cursor"}) + local float_bufnr, winnr = vim.diagnostic.open_float({header=false, scope="cursor"}) local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false) vim.api.nvim_win_close(winnr, true) return lines @@ -1387,7 +1439,7 @@ describe('vim.diagnostic', function() vim.api.nvim_win_set_buf(0, diagnostic_bufnr) vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, diagnostics) vim.api.nvim_win_set_cursor(0, {1, 1}) - local float_bufnr, winnr = vim.diagnostic.open_float(0, {header=false, scope="cursor", pos={1,3}}) + local float_bufnr, winnr = vim.diagnostic.open_float({header=false, scope="cursor", pos={1,3}}) local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false) vim.api.nvim_win_close(winnr, true) return lines @@ -1402,7 +1454,7 @@ describe('vim.diagnostic', function() vim.api.nvim_win_set_buf(0, diagnostic_bufnr) vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, diagnostics) vim.api.nvim_win_set_cursor(0, {1, 1}) - local float_bufnr, winnr = vim.diagnostic.open_float(0, {header=false, scope="cursor", pos={0,first_line_len}}) + local float_bufnr, winnr = vim.diagnostic.open_float({header=false, scope="cursor", pos={0,first_line_len}}) local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false) vim.api.nvim_win_close(winnr, true) return lines @@ -1419,7 +1471,7 @@ describe('vim.diagnostic', function() } vim.api.nvim_win_set_buf(0, diagnostic_bufnr) vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, diagnostics) - local float_bufnr, winnr = vim.diagnostic.open_float(diagnostic_bufnr, {scope="line"}) + local float_bufnr, winnr = vim.diagnostic.open_float(diagnostic_bufnr) local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false) vim.api.nvim_win_close(winnr, true) return #lines @@ -1486,7 +1538,7 @@ describe('vim.diagnostic', function() } vim.api.nvim_win_set_buf(0, diagnostic_bufnr) vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, diagnostics) - local float_bufnr, winnr = vim.diagnostic.open_float(diagnostic_bufnr, {header = false, scope = "line", pos = 5}) + local float_bufnr, winnr = vim.diagnostic.open_float(diagnostic_bufnr, {header = false, pos = 5}) local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false) vim.api.nvim_win_close(winnr, true) return #lines @@ -1618,7 +1670,7 @@ describe('vim.diagnostic', function() } vim.api.nvim_win_set_buf(0, diagnostic_bufnr) vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, diagnostics) - local float_bufnr, winnr = vim.diagnostic.open_float(0, {header = false}) + local float_bufnr, winnr = vim.diagnostic.open_float({header = false, scope = "buffer"}) local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false) vim.api.nvim_win_close(winnr, true) return lines @@ -1631,7 +1683,7 @@ describe('vim.diagnostic', function() } vim.api.nvim_win_set_buf(0, diagnostic_bufnr) vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, diagnostics) - local float_bufnr, winnr = vim.diagnostic.open_float(0, {header = false, prefix = ""}) + local float_bufnr, winnr = vim.diagnostic.open_float({header = false, scope = "buffer", prefix = ""}) local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false) vim.api.nvim_win_close(winnr, true) return lines @@ -1640,11 +1692,11 @@ describe('vim.diagnostic', function() eq({'1. Syntax error', '2. Some warning'}, exec_lua [[ local diagnostics = { make_error("Syntax error", 0, 1, 0, 3), - make_warning("Some warning", 1, 1, 1, 3), + make_warning("Some warning", 0, 1, 0, 3), } vim.api.nvim_win_set_buf(0, diagnostic_bufnr) vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, diagnostics) - local float_bufnr, winnr = vim.diagnostic.open_float(0, { + local float_bufnr, winnr = vim.diagnostic.open_float({ header = false, prefix = function(_, i, total) -- Only show a number if there is more than one diagnostic @@ -1665,7 +1717,7 @@ describe('vim.diagnostic', function() } vim.api.nvim_win_set_buf(0, diagnostic_bufnr) vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, diagnostics) - local float_bufnr, winnr = vim.diagnostic.open_float(0, { + local float_bufnr, winnr = vim.diagnostic.open_float({ header = false, prefix = function(_, i, total) -- Only show a number if there is more than one diagnostic @@ -1681,7 +1733,21 @@ describe('vim.diagnostic', function() ]]) eq("Error executing lua: .../diagnostic.lua:0: prefix: expected 'string' or 'table' or 'function', got 42", - pcall_err(exec_lua, [[ vim.diagnostic.open_float(0, { prefix = 42 }) ]])) + pcall_err(exec_lua, [[ vim.diagnostic.open_float({ prefix = 42 }) ]])) + end) + + it('works with the old signature', function() + eq({'1. Syntax error'}, exec_lua [[ + local diagnostics = { + make_error("Syntax error", 0, 1, 0, 3), + } + vim.api.nvim_win_set_buf(0, diagnostic_bufnr) + vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, diagnostics) + local float_bufnr, winnr = vim.diagnostic.open_float(0, { header = false }) + local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false) + vim.api.nvim_win_close(winnr, true) + return lines + ]]) end) end) @@ -1871,5 +1937,27 @@ describe('vim.diagnostic', function() return {show_called, hide_called} ]]) end) + + it('triggers the autocommand when diagnostics are set', function() + eq(1, exec_lua [[ + vim.g.diagnostic_autocmd_triggered = 0 + vim.cmd('autocmd DiagnosticChanged * let g:diagnostic_autocmd_triggered = 1') + vim.api.nvim_buf_set_name(diagnostic_bufnr, "test | test") + vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, { + make_error('Diagnostic', 0, 0, 0, 0) + }) + return vim.g.diagnostic_autocmd_triggered + ]]) + end) + + it('triggers the autocommand when diagnostics are cleared', function() + eq(1, exec_lua [[ + vim.g.diagnostic_autocmd_triggered = 0 + vim.cmd('autocmd DiagnosticChanged * let g:diagnostic_autocmd_triggered = 1') + vim.api.nvim_buf_set_name(diagnostic_bufnr, "test | test") + vim.diagnostic.reset(diagnostic_ns, diagnostic_bufnr) + return vim.g.diagnostic_autocmd_triggered + ]]) + end) end) end) 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/overrides_spec.lua b/test/functional/lua/overrides_spec.lua index b0712ff366..9b51af1eec 100644 --- a/test/functional/lua/overrides_spec.lua +++ b/test/functional/lua/overrides_spec.lua @@ -61,6 +61,44 @@ describe('print', function() eq('Vim(lua):E5108: Error executing lua E5114: Error while converting print argument #2: <Unknown error: lua_tolstring returned NULL for tostring result>', pcall_err(command, 'lua print("foo", v_tblout, "bar")')) end) + it('coerces error values into strings', function() + write_file(fname, [[ + function string_error() error("my mistake") end + function number_error() error(1234) end + function nil_error() error(nil) end + function table_error() error({message = "my mistake"}) end + function custom_error() + local err = {message = "my mistake", code = 11234} + setmetatable(err, { + __tostring = function(t) + return "Internal Error [" .. t.code .. "] " .. t.message + end + }) + error(err) + end + function bad_custom_error() + local err = {message = "my mistake", code = 11234} + setmetatable(err, { + -- intentionally not a function, downstream programmer has made an mistake + __tostring = "Internal Error [" .. err.code .. "] " .. err.message + }) + error(err) + end + ]]) + eq('', exec_capture('luafile ' .. fname)) + eq('Vim(lua):E5108: Error executing lua Xtest-functional-lua-overrides-luafile:0: my mistake', + pcall_err(command, 'lua string_error()')) + eq('Vim(lua):E5108: Error executing lua Xtest-functional-lua-overrides-luafile:0: 1234', + pcall_err(command, 'lua number_error()')) + eq('Vim(lua):E5108: Error executing lua [NULL]', + pcall_err(command, 'lua nil_error()')) + eq('Vim(lua):E5108: Error executing lua [NULL]', + pcall_err(command, 'lua table_error()')) + eq('Vim(lua):E5108: Error executing lua Internal Error [11234] my mistake', + pcall_err(command, 'lua custom_error()')) + eq('Vim(lua):E5108: Error executing lua [NULL]', + pcall_err(command, 'lua bad_custom_error()')) + end) it('prints strings with NULs and NLs correctly', function() meths.set_option('more', true) eq('abc ^@ def\nghi^@^@^@jkl\nTEST\n\n\nT\n', diff --git a/test/functional/lua/spell_spec.lua b/test/functional/lua/spell_spec.lua new file mode 100644 index 0000000000..7e831f16a7 --- /dev/null +++ b/test/functional/lua/spell_spec.lua @@ -0,0 +1,53 @@ +local helpers = require('test.functional.helpers')(after_each) +local clear = helpers.clear +local exec_lua = helpers.exec_lua +local eq = helpers.eq +local pcall_err = helpers.pcall_err + +describe('vim.spell', function() + before_each(function() + clear() + end) + + describe('.check', function() + local check = function(x, exp) + return eq(exp, exec_lua("return vim.spell.check(...)", x)) + end + + it('can handle nil', function() + eq([[Error executing lua: [string "<nvim>"]:0: bad argument #1 to 'check' (expected string)]], + pcall_err(exec_lua, [[vim.spell.check(nil)]])) + end) + + it('can check spellings', function() + check('hello', {}) + + check( + 'helloi', + {{"helloi", "bad", 1}} + ) + + check( + 'hello therei', + {{"therei", "bad", 7}} + ) + + check( + 'hello. there', + {{"there", "caps", 8}} + ) + + check( + 'neovim cna chkc spellins. okay?', + { + {"neovim" , "bad" , 1}, + {"cna" , "bad" , 8}, + {"chkc" , "bad" , 12}, + {"spellins", "bad" , 17}, + {"okay" , "caps", 27} + } + ) + end) + + end) +end) diff --git a/test/functional/lua/thread_spec.lua b/test/functional/lua/thread_spec.lua new file mode 100644 index 0000000000..e183ce3a57 --- /dev/null +++ b/test/functional/lua/thread_spec.lua @@ -0,0 +1,408 @@ +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) + + it('vim.inspect', function() + exec_lua [[ + local thread = vim.loop.new_thread(function() + print(vim.inspect({1,2})) + end) + vim.loop.thread_join(thread) + ]] + + screen:expect([[ + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + { 1, 2 } | + ]]) + 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 81f1820986..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) @@ -143,8 +143,8 @@ describe('URI methods', function() end) it('uri_to_fname returns non-file scheme URI without authority unchanged', function() - eq('zipfile:/path/to/archive.zip%3A%3Afilename.txt', exec_lua [[ - return vim.uri_to_fname('zipfile:/path/to/archive.zip%3A%3Afilename.txt') + eq('zipfile:///path/to/archive.zip%3A%3Afilename.txt', exec_lua [[ + return vim.uri_to_fname('zipfile:///path/to/archive.zip%3A%3Afilename.txt') ]]) end) end) @@ -155,6 +155,12 @@ describe('URI methods', function() return pcall(vim.uri_to_fname, 'not_an_uri.txt') ]]) end) + + it('uri_to_fname should not treat comma as a scheme character', function() + eq(false, exec_lua [[ + return pcall(vim.uri_to_fname, 'foo,://bar') + ]]) + end) end) end) @@ -186,7 +192,7 @@ describe('URI methods', function() end) it('uri_to_bufnr & uri_from_bufnr returns original uri for non-file uris without authority', function() - local uri = 'zipfile:/path/to/archive.zip%3A%3Afilename.txt' + local uri = 'zipfile:///path/to/archive.zip%3A%3Afilename.txt' local test_case = string.format([[ local uri = '%s' return vim.uri_from_bufnr(vim.uri_to_bufnr(uri)) diff --git a/test/functional/lua/vim_spec.lua b/test/functional/lua/vim_spec.lua index 3832d27a22..1547f3244e 100644 --- a/test/functional/lua/vim_spec.lua +++ b/test/functional/lua/vim_spec.lua @@ -14,15 +14,18 @@ local feed = helpers.feed local pcall_err = helpers.pcall_err local exec_lua = helpers.exec_lua local matches = helpers.matches -local source = helpers.source +local exec = helpers.exec 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) describe('lua stdlib', function() + before_each(clear) -- İ: `tolower("İ")` is `i` which has length 1 while `İ` itself has -- length 2 (in bytes). -- Ⱥ: `tolower("Ⱥ")` is `ⱥ` which has length 2 while `Ⱥ` itself has @@ -131,9 +134,9 @@ describe('lua stdlib', function() eq(false, funcs.luaeval('vim.startswith("123", "2")')) eq(false, funcs.luaeval('vim.startswith("123", "1234")')) - eq("Error executing lua: vim/shared.lua:0: prefix: expected string, got nil", + matches("prefix: expected string, got nil", pcall_err(exec_lua, 'return vim.startswith("123", nil)')) - eq("Error executing lua: vim/shared.lua:0: s: expected string, got nil", + matches("s: expected string, got nil", pcall_err(exec_lua, 'return vim.startswith(nil, "123")')) end) @@ -147,9 +150,9 @@ describe('lua stdlib', function() eq(false, funcs.luaeval('vim.endswith("123", "2")')) eq(false, funcs.luaeval('vim.endswith("123", "1234")')) - eq("Error executing lua: vim/shared.lua:0: suffix: expected string, got nil", + matches("suffix: expected string, got nil", pcall_err(exec_lua, 'return vim.endswith("123", nil)')) - eq("Error executing lua: vim/shared.lua:0: s: expected string, got nil", + matches("s: expected string, got nil", pcall_err(exec_lua, 'return vim.endswith(nil, "123")')) end) @@ -220,9 +223,9 @@ describe('lua stdlib', function() eq({"yy","xx"}, exec_lua("return test_table")) -- Validates args. - eq('Error executing lua: vim.schedule: expected function', + matches('vim.schedule: expected function', pcall_err(exec_lua, "vim.schedule('stringly')")) - eq('Error executing lua: vim.schedule: expected function', + matches('vim.schedule: expected function', pcall_err(exec_lua, "vim.schedule()")) exec_lua([[ @@ -232,7 +235,7 @@ describe('lua stdlib', function() ]]) feed("<cr>") - eq('Error executing vim.schedule lua callback: [string "<nvim>"]:2: big failure\nvery async', remove_trace(eval("v:errmsg"))) + matches('big failure\nvery async', remove_trace(eval("v:errmsg"))) local screen = Screen.new(60,5) screen:set_default_attr_ids({ @@ -300,16 +303,16 @@ describe('lua stdlib', function() } for _, t in ipairs(loops) do - eq("Error executing lua: vim/shared.lua:0: Infinite loop detected", pcall_err(split, t[1], t[2])) + matches("Infinite loop detected", pcall_err(split, t[1], t[2])) end -- Validates args. eq(true, pcall(split, 'string', 'string')) - eq('Error executing lua: vim/shared.lua:0: s: expected string, got number', + matches('s: expected string, got number', pcall_err(split, 1, 'string')) - eq('Error executing lua: vim/shared.lua:0: sep: expected string, got number', + matches('sep: expected string, got number', pcall_err(split, 'string', 1)) - eq('Error executing lua: vim/shared.lua:0: kwargs: expected table, got number', + matches('kwargs: expected table, got number', pcall_err(split, 'string', 'string', 1)) end) @@ -330,7 +333,7 @@ describe('lua stdlib', function() end -- Validates args. - eq('Error executing lua: vim/shared.lua:0: s: expected string, got number', + matches('s: expected string, got number', pcall_err(trim, 2)) end) @@ -396,7 +399,21 @@ describe('lua stdlib', function() return t1.f() ~= t2.f() ]])) - eq('Error executing lua: vim/shared.lua:0: Cannot deepcopy object of type thread', + ok(exec_lua([[ + local t1 = {a = 5} + t1.self = t1 + local t2 = vim.deepcopy(t1) + return t2.self == t2 and t2.self ~= t1 + ]])) + + ok(exec_lua([[ + local mt = {mt=true} + local t1 = setmetatable({a = 5}, mt) + local t2 = vim.deepcopy(t1) + return getmetatable(t2) == mt + ]])) + + matches('Cannot deepcopy object of type thread', pcall_err(exec_lua, [[ local thread = coroutine.create(function () return 0 end) local t = {thr = thread} @@ -409,7 +426,7 @@ describe('lua stdlib', function() eq('foo%%%-bar', exec_lua([[return vim.pesc(vim.pesc('foo-bar'))]])) -- Validates args. - eq('Error executing lua: vim/shared.lua:0: s: expected string, got number', + matches('s: expected string, got number', pcall_err(exec_lua, [[return vim.pesc(2)]])) end) @@ -473,6 +490,12 @@ describe('lua stdlib', function() eq(false, exec_lua("return vim.tbl_isempty({a=1, b=2, c=3})")) end) + it('vim.tbl_get', function() + eq(true, exec_lua("return vim.tbl_get({ test = { nested_test = true }}, 'test', 'nested_test')")) + eq(NIL, exec_lua("return vim.tbl_get({}, 'missing_key')")) + eq(NIL, exec_lua("return vim.tbl_get({})")) + end) + it('vim.tbl_extend', function() ok(exec_lua([[ local a = {x = 1} @@ -534,19 +557,19 @@ describe('lua stdlib', function() return c.x.a == 1 and c.x.b == 2 and c.x.c == nil and count == 1 ]])) - eq('Error executing lua: vim/shared.lua:0: invalid "behavior": nil', + matches('invalid "behavior": nil', pcall_err(exec_lua, [[ return vim.tbl_extend() ]]) ) - eq('Error executing lua: vim/shared.lua:0: wrong number of arguments (given 1, expected at least 3)', + matches('wrong number of arguments %(given 1, expected at least 3%)', pcall_err(exec_lua, [[ return vim.tbl_extend("keep") ]]) ) - eq('Error executing lua: vim/shared.lua:0: wrong number of arguments (given 2, expected at least 3)', + matches('wrong number of arguments %(given 2, expected at least 3%)', pcall_err(exec_lua, [[ return vim.tbl_extend("keep", {}) ]]) @@ -647,19 +670,19 @@ describe('lua stdlib', function() return vim.tbl_deep_extend("force", a, b) ]]), {a = 123 }) - eq('Error executing lua: vim/shared.lua:0: invalid "behavior": nil', + matches('invalid "behavior": nil', pcall_err(exec_lua, [[ return vim.tbl_deep_extend() ]]) ) - eq('Error executing lua: vim/shared.lua:0: wrong number of arguments (given 1, expected at least 3)', + matches('wrong number of arguments %(given 1, expected at least 3%)', pcall_err(exec_lua, [[ return vim.tbl_deep_extend("keep") ]]) ) - eq('Error executing lua: vim/shared.lua:0: wrong number of arguments (given 2, expected at least 3)', + matches('wrong number of arguments %(given 2, expected at least 3%)', pcall_err(exec_lua, [[ return vim.tbl_deep_extend("keep", {}) ]]) @@ -692,7 +715,7 @@ describe('lua stdlib', function() it('vim.list_extend', function() eq({1,2,3}, exec_lua [[ return vim.list_extend({1}, {2,3}) ]]) - eq('Error executing lua: vim/shared.lua:0: src: expected table, got nil', + matches('src: expected table, got nil', pcall_err(exec_lua, [[ return vim.list_extend({1}, nil) ]])) eq({1,2}, exec_lua [[ return vim.list_extend({1}, {2;a=1}) ]]) eq(true, exec_lua [[ local a = {1} return vim.list_extend(a, {2;a=1}) == a ]]) @@ -716,7 +739,7 @@ describe('lua stdlib', function() assert(vim.deep_equal(a, { A = 1; [1] = 'A'; })) vim.tbl_add_reverse_lookup(a) ]] - matches('^Error executing lua: vim/shared%.lua:0: The reverse lookup found an existing value for "[1A]" while processing key "[1A]"$', + matches('The reverse lookup found an existing value for "[1A]" while processing key "[1A]"$', pcall_err(exec_lua, code)) end) @@ -726,7 +749,7 @@ describe('lua stdlib', function() -- compat: nvim_call_function uses "special" value for vimL float eq(false, exec_lua([[return vim.api.nvim_call_function('sin', {0.0}) == 0.0 ]])) - source([[ + exec([[ func! FooFunc(test) let g:test = a:test return {} @@ -754,10 +777,16 @@ describe('lua stdlib', function() -- error handling eq({false, 'Vim:E897: List or Blob required'}, exec_lua([[return {pcall(vim.fn.add, "aa", "bb")}]])) + + -- conversion between LuaRef and Vim Funcref + eq(true, exec_lua([[ + local x = vim.fn.VarArg(function() return 'foo' end, function() return 'bar' end) + return #x == 2 and x[1]() == 'foo' and x[2]() == 'bar' + ]])) end) it('vim.fn should error when calling API function', function() - eq('Error executing lua: vim.lua:0: Tried to call API function with vim.fn: use vim.api.nvim_get_current_line instead', + matches('Tried to call API function with vim.fn: use vim.api.nvim_get_current_line instead', pcall_err(exec_lua, "vim.fn.nvim_get_current_line()")) end) @@ -892,38 +921,41 @@ 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'} }}") - eq('Error executing lua: [string "<nvim>"]:0: opt[1]: expected table, got number', + matches('expected table, got number', pcall_err(exec_lua, "vim.validate{ 1, 'x' }")) - eq('Error executing lua: [string "<nvim>"]:0: invalid type name: x', + matches('invalid type name: x', pcall_err(exec_lua, "vim.validate{ arg1={ 1, 'x' }}")) - eq('Error executing lua: [string "<nvim>"]:0: invalid type name: 1', + matches('invalid type name: 1', pcall_err(exec_lua, "vim.validate{ arg1={ 1, 1 }}")) - eq('Error executing lua: [string "<nvim>"]:0: invalid type name: nil', + matches('invalid type name: nil', pcall_err(exec_lua, "vim.validate{ arg1={ 1 }}")) -- Validated parameters are required by default. - eq('Error executing lua: [string "<nvim>"]:0: arg1: expected string, got nil', + matches('arg1: expected string, got nil', pcall_err(exec_lua, "vim.validate{ arg1={ nil, 's' }}")) -- Explicitly required. - eq('Error executing lua: [string "<nvim>"]:0: arg1: expected string, got nil', + matches('arg1: expected string, got nil', pcall_err(exec_lua, "vim.validate{ arg1={ nil, 's', false }}")) - eq('Error executing lua: [string "<nvim>"]:0: arg1: expected table, got number', + matches('arg1: expected table, got number', pcall_err(exec_lua, "vim.validate{arg1={1, 't'}}")) - eq('Error executing lua: [string "<nvim>"]:0: arg2: expected string, got number', + matches('arg2: expected string, got number', pcall_err(exec_lua, "vim.validate{arg1={{}, 't'}, arg2={1, 's'}}")) - eq('Error executing lua: [string "<nvim>"]:0: arg2: expected string, got nil', + matches('arg2: expected string, got nil', pcall_err(exec_lua, "vim.validate{arg1={{}, 't'}, arg2={nil, 's'}}")) - eq('Error executing lua: [string "<nvim>"]:0: arg2: expected string, got nil', + matches('arg2: expected string, got nil', pcall_err(exec_lua, "vim.validate{arg1={{}, 't'}, arg2={nil, 's'}}")) - eq('Error executing lua: [string "<nvim>"]:0: arg1: expected even number, got 3', + matches('arg1: expected even number, got 3', pcall_err(exec_lua, "vim.validate{arg1={3, function(a) return a == 1 end, 'even number'}}")) - eq('Error executing lua: [string "<nvim>"]:0: arg1: expected ?, got 3', + 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. - eq('Error executing lua: [string "<nvim>"]:0: arg1: expected ?, got 3. Info: TEST_MSG', + matches('arg1: expected %?, got 3. Info: TEST_MSG', pcall_err(exec_lua, "vim.validate{arg1={3, function(a) return a == 1, 'TEST_MSG' end}}")) end) @@ -968,8 +1000,79 @@ describe('lua stdlib', function() ]] eq(NIL, funcs.luaeval "vim.g.to_delete") - matches([[^Error executing lua: .*: attempt to index .* nil value]], + matches([[attempt to index .* nil value]], pcall_err(exec_lua, 'return vim.g[0].testing')) + + exec_lua [[ + local counter = 0 + local function add_counter() counter = counter + 1 end + local function get_counter() return counter end + vim.g.AddCounter = add_counter + vim.g.GetCounter = get_counter + vim.g.funcs = {add = add_counter, get = get_counter} + ]] + + 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([[vim.g.funcs.add()]]) + eq(5, exec_lua([[return vim.g.funcs.get()]])) + exec_lua([[vim.api.nvim_get_var('funcs').add()]]) + eq(6, exec_lua([[return vim.api.nvim_get_var('funcs').get()]])) + + exec_lua [[ + local counter = 0 + local function add_counter() counter = counter + 1 end + local function get_counter() return counter end + vim.api.nvim_set_var('AddCounter', add_counter) + vim.api.nvim_set_var('GetCounter', get_counter) + vim.api.nvim_set_var('funcs', {add = add_counter, get = get_counter}) + ]] + + 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([[vim.g.funcs.add()]]) + eq(5, exec_lua([[return vim.g.funcs.get()]])) + exec_lua([[vim.api.nvim_get_var('funcs').add()]]) + eq(6, exec_lua([[return vim.api.nvim_get_var('funcs').get()]])) + + exec([[ + function Test() + endfunction + function s:Test() + endfunction + let g:Unknown_func = function('Test') + let g:Unknown_script_func = function('s:Test') + ]]) + eq(NIL, exec_lua([[return vim.g.Unknown_func]])) + eq(NIL, exec_lua([[return vim.g.Unknown_script_func]])) + + -- 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() @@ -995,7 +1098,7 @@ describe('lua stdlib', function() return {vim.b.nonexistant == vim.NIL, vim.b.nullvar == vim.NIL} ]]) - matches([[^Error executing lua: .*: attempt to index .* nil value]], + matches([[attempt to index .* nil value]], pcall_err(exec_lua, 'return vim.b[BUF][0].testing')) eq({hello="world"}, funcs.luaeval "vim.b.to_delete") @@ -1005,6 +1108,63 @@ describe('lua stdlib', function() eq(NIL, funcs.luaeval "vim.b.to_delete") exec_lua [[ + local counter = 0 + local function add_counter() counter = counter + 1 end + local function get_counter() return counter end + vim.b.AddCounter = add_counter + vim.b.GetCounter = get_counter + vim.b.funcs = {add = add_counter, get = get_counter} + ]] + + 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.b.funcs.add()]]) + eq(5, exec_lua([[return vim.b.funcs.get()]])) + exec_lua([[vim.api.nvim_buf_get_var(0, 'funcs').add()]]) + eq(6, exec_lua([[return vim.api.nvim_buf_get_var(0, 'funcs').get()]])) + + exec_lua [[ + local counter = 0 + local function add_counter() counter = counter + 1 end + local function get_counter() return counter end + vim.api.nvim_buf_set_var(0, 'AddCounter', add_counter) + vim.api.nvim_buf_set_var(0, 'GetCounter', get_counter) + vim.api.nvim_buf_set_var(0, 'funcs', {add = add_counter, get = get_counter}) + ]] + + 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.b.funcs.add()]]) + eq(5, exec_lua([[return vim.b.funcs.get()]])) + exec_lua([[vim.api.nvim_buf_get_var(0, 'funcs').add()]]) + eq(6, exec_lua([[return vim.api.nvim_buf_get_var(0, 'funcs').get()]])) + + exec([[ + function Test() + endfunction + function s:Test() + endfunction + let b:Unknown_func = function('Test') + let b:Unknown_script_func = function('s:Test') + ]]) + eq(NIL, exec_lua([[return vim.b.Unknown_func]])) + eq(NIL, exec_lua([[return vim.b.Unknown_script_func]])) + + exec_lua [[ vim.cmd "vnew" ]] @@ -1032,7 +1192,7 @@ describe('lua stdlib', function() eq(NIL, funcs.luaeval "vim.w.nonexistant") eq(NIL, funcs.luaeval "vim.w[WIN].nonexistant") - matches([[^Error executing lua: .*: attempt to index .* nil value]], + matches([[attempt to index .* nil value]], pcall_err(exec_lua, 'return vim.w[WIN][0].testing')) eq({hello="world"}, funcs.luaeval "vim.w.to_delete") @@ -1042,6 +1202,63 @@ describe('lua stdlib', function() eq(NIL, funcs.luaeval "vim.w.to_delete") exec_lua [[ + local counter = 0 + local function add_counter() counter = counter + 1 end + local function get_counter() return counter end + vim.w.AddCounter = add_counter + vim.w.GetCounter = get_counter + vim.w.funcs = {add = add_counter, get = get_counter} + ]] + + 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.w.funcs.add()]]) + eq(5, exec_lua([[return vim.w.funcs.get()]])) + exec_lua([[vim.api.nvim_win_get_var(0, 'funcs').add()]]) + eq(6, exec_lua([[return vim.api.nvim_win_get_var(0, 'funcs').get()]])) + + exec_lua [[ + local counter = 0 + local function add_counter() counter = counter + 1 end + local function get_counter() return counter end + vim.api.nvim_win_set_var(0, 'AddCounter', add_counter) + vim.api.nvim_win_set_var(0, 'GetCounter', get_counter) + vim.api.nvim_win_set_var(0, 'funcs', {add = add_counter, get = get_counter}) + ]] + + 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.w.funcs.add()]]) + eq(5, exec_lua([[return vim.w.funcs.get()]])) + exec_lua([[vim.api.nvim_win_get_var(0, 'funcs').add()]]) + eq(6, exec_lua([[return vim.api.nvim_win_get_var(0, 'funcs').get()]])) + + exec([[ + function Test() + endfunction + function s:Test() + endfunction + let w:Unknown_func = function('Test') + let w:Unknown_script_func = function('s:Test') + ]]) + eq(NIL, exec_lua([[return vim.w.Unknown_func]])) + eq(NIL, exec_lua([[return vim.w.Unknown_script_func]])) + + exec_lua [[ vim.cmd "vnew" ]] @@ -1064,7 +1281,7 @@ describe('lua stdlib', function() eq(123, funcs.luaeval "vim.t[0].other") eq(NIL, funcs.luaeval "vim.t[0].nonexistant") - matches([[^Error executing lua: .*: attempt to index .* nil value]], + matches([[attempt to index .* nil value]], pcall_err(exec_lua, 'return vim.t[0][0].testing')) eq({hello="world"}, funcs.luaeval "vim.t.to_delete") @@ -1074,6 +1291,52 @@ describe('lua stdlib', function() eq(NIL, funcs.luaeval "vim.t.to_delete") exec_lua [[ + local counter = 0 + local function add_counter() counter = counter + 1 end + local function get_counter() return counter end + vim.t.AddCounter = add_counter + vim.t.GetCounter = get_counter + vim.t.funcs = {add = add_counter, get = get_counter} + ]] + + 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.t.funcs.add()]]) + eq(5, exec_lua([[return vim.t.funcs.get()]])) + exec_lua([[vim.api.nvim_tabpage_get_var(0, 'funcs').add()]]) + eq(6, exec_lua([[return vim.api.nvim_tabpage_get_var(0, 'funcs').get()]])) + + exec_lua [[ + local counter = 0 + local function add_counter() counter = counter + 1 end + local function get_counter() return counter end + vim.api.nvim_tabpage_set_var(0, 'AddCounter', add_counter) + vim.api.nvim_tabpage_set_var(0, 'GetCounter', get_counter) + vim.api.nvim_tabpage_set_var(0, 'funcs', {add = add_counter, get = get_counter}) + ]] + + 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.t.funcs.add()]]) + eq(5, exec_lua([[return vim.t.funcs.get()]])) + exec_lua([[vim.api.nvim_tabpage_get_var(0, 'funcs').add()]]) + eq(6, exec_lua([[return vim.api.nvim_tabpage_get_var(0, 'funcs').get()]])) + + exec_lua [[ vim.cmd "tabnew" ]] @@ -1094,7 +1357,7 @@ describe('lua stdlib', function() eq(funcs.luaeval "vim.api.nvim_get_vvar('progpath')", funcs.luaeval "vim.v.progpath") eq(false, funcs.luaeval "vim.v['false']") eq(NIL, funcs.luaeval "vim.v.null") - matches([[^Error executing lua: .*: attempt to index .* nil value]], + matches([[attempt to index .* nil value]], pcall_err(exec_lua, 'return vim.v[0].progpath')) end) @@ -1114,9 +1377,9 @@ describe('lua stdlib', function() ]] eq('', funcs.luaeval "vim.bo.filetype") eq(true, funcs.luaeval "vim.bo[BUF].modifiable") - matches("^Error executing lua: .*: Invalid option name: 'nosuchopt'$", + matches("Invalid option name: 'nosuchopt'$", pcall_err(exec_lua, 'return vim.bo.nosuchopt')) - matches("^Error executing lua: .*: Expected lua string$", + matches("Expected lua string$", pcall_err(exec_lua, 'return vim.bo[0][0].autoread')) end) @@ -1133,9 +1396,9 @@ describe('lua stdlib', function() eq(0, funcs.luaeval "vim.wo.cole") eq(0, funcs.luaeval "vim.wo[0].cole") eq(0, funcs.luaeval "vim.wo[1001].cole") - matches("^Error executing lua: .*: Invalid option name: 'notanopt'$", + matches("Invalid option name: 'notanopt'$", pcall_err(exec_lua, 'return vim.wo.notanopt')) - matches("^Error executing lua: .*: Expected lua string$", + matches("Expected lua string$", pcall_err(exec_lua, 'return vim.wo[0][0].list')) eq(2, funcs.luaeval "vim.wo[1000].cole") exec_lua [[ @@ -1226,7 +1489,7 @@ describe('lua stdlib', function() vim.opt.makeprg = "global-local" table.insert(result, vim.api.nvim_get_option('makeprg')) - table.insert(result, (pcall(vim.api.nvim_buf_get_option, 0, 'makeprg'))) + table.insert(result, vim.api.nvim_buf_get_option(0, 'makeprg')) vim.opt_local.mp = "only-local" table.insert(result, vim.api.nvim_get_option('makeprg')) @@ -1244,7 +1507,7 @@ describe('lua stdlib', function() -- Set -> global & local eq("global-local", result[1]) - eq(false, result[2]) + eq("", result[2]) -- Setlocal -> only local eq("global-local", result[3]) @@ -1254,9 +1517,9 @@ describe('lua stdlib', function() eq("only-global", result[5]) eq("only-local", result[6]) - -- set -> doesn't override previously set value + -- Set -> sets global value and resets local value eq("global-local", result[7]) - eq("only-local", result[8]) + eq("", result[8]) end) it('should allow you to retrieve window opts even if they have not been set', function() @@ -2186,6 +2449,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 [[ @@ -2227,6 +2524,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() @@ -2255,12 +2563,111 @@ 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) + +describe('lua: builtin modules', function() + local function do_tests() + eq(2, exec_lua[[return vim.tbl_count {x=1,y=2}]]) + eq('{ 10, "spam" }', exec_lua[[return vim.inspect {10, 'spam'}]]) + end + + it('works', function() + clear() + do_tests() + end) + + it('works when disabled', function() + clear('--luamod-dev') + do_tests() + end) + + it('works without runtime', function() + clear{env={VIMRUNTIME='fixtures/a'}} + do_tests() + end) + + + it('does not work when disabled without runtime', function() + clear{args={'--luamod-dev'}, env={VIMRUNTIME='fixtures/a'}} + -- error checking could be better here. just check that --luamod-dev + -- does anything at all by breaking with missing runtime.. + eq(nil, exec_lua[[return vim.tbl_count {x=1,y=2}]]) end) end) describe('lua: require("mod") from packages', function() before_each(function() - command('set rtp+=test/functional/fixtures pp+=test/functional/fixtures') + clear('--cmd', 'set rtp+=test/functional/fixtures pp+=test/functional/fixtures') end) it('propagates syntax error', function() @@ -2281,3 +2688,84 @@ 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() + before_each(clear) + + 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/options/autochdir_spec.lua b/test/functional/options/autochdir_spec.lua index 2fce0a5ed9..74959a8e76 100644 --- a/test/functional/options/autochdir_spec.lua +++ b/test/functional/options/autochdir_spec.lua @@ -1,7 +1,9 @@ +local lfs = require('lfs') local helpers = require('test.functional.helpers')(after_each) local clear = helpers.clear local eq = helpers.eq -local getcwd = helpers.funcs.getcwd +local funcs = helpers.funcs +local command = helpers.command describe("'autochdir'", function() it('given on the shell gets processed properly', function() @@ -9,11 +11,34 @@ describe("'autochdir'", function() -- By default 'autochdir' is off, thus getcwd() returns the repo root. clear(targetdir..'/tty-test.c') - local rootdir = getcwd() + local rootdir = funcs.getcwd() local expected = rootdir .. '/' .. targetdir -- With 'autochdir' on, we should get the directory of tty-test.c. clear('--cmd', 'set autochdir', targetdir..'/tty-test.c') - eq(helpers.iswin() and expected:gsub('/', '\\') or expected, getcwd()) + eq(helpers.iswin() and expected:gsub('/', '\\') or expected, funcs.getcwd()) + end) + + it('is not overwritten by getwinvar() call #17609',function() + local curdir = string.gsub(lfs.currentdir(), '\\', '/') + local dir_a = curdir..'/Xtest-functional-options-autochdir.dir_a' + local dir_b = curdir..'/Xtest-functional-options-autochdir.dir_b' + lfs.mkdir(dir_a) + lfs.mkdir(dir_b) + clear() + command('set shellslash') + command('set autochdir') + command('edit '..dir_a..'/file1') + eq(dir_a, funcs.getcwd()) + command('lcd '..dir_b) + eq(dir_b, funcs.getcwd()) + command('botright vnew ../file2') + eq(curdir, funcs.getcwd()) + command('wincmd w') + eq(dir_a, funcs.getcwd()) + funcs.getwinvar(2, 'foo') + eq(dir_a, funcs.getcwd()) + helpers.rmdir(dir_a) + helpers.rmdir(dir_b) end) end) diff --git a/test/functional/options/chars_spec.lua b/test/functional/options/chars_spec.lua index 5439ca3dba..a082204980 100644 --- a/test/functional/options/chars_spec.lua +++ b/test/functional/options/chars_spec.lua @@ -67,36 +67,52 @@ describe("'fillchars'", function() shouldfail('eob:xy') -- two ascii chars shouldfail('eob:\255', 'eob:<ff>') -- invalid UTF-8 end) - it('has global value', function() - screen:try_resize(50, 5) - insert("foo\nbar") - command('set laststatus=0') - command('1,2fold') - command('vsplit') - command('set fillchars=fold:x') - screen:expect([[ - ^+-- 2 lines: fooxxxxxxxx│+-- 2 lines: fooxxxxxxx| - ~ │~ | - ~ │~ | - ~ │~ | - | - ]]) - end) - it('has local window value', function() - screen:try_resize(50, 5) - insert("foo\nbar") - command('set laststatus=0') - command('1,2fold') - command('vsplit') - command('setl fillchars=fold:x') - screen:expect([[ - ^+-- 2 lines: fooxxxxxxxx│+-- 2 lines: foo·······| - ~ │~ | - ~ │~ | - ~ │~ | - | - ]]) - end) + end) + it('has global value', function() + screen:try_resize(50, 5) + insert("foo\nbar") + command('set laststatus=0') + command('1,2fold') + command('vsplit') + command('set fillchars=fold:x') + screen:expect([[ + ^+-- 2 lines: fooxxxxxxxx│+-- 2 lines: fooxxxxxxx| + ~ │~ | + ~ │~ | + ~ │~ | + | + ]]) + end) + it('has window-local value', function() + screen:try_resize(50, 5) + insert("foo\nbar") + command('set laststatus=0') + command('1,2fold') + command('vsplit') + command('setl fillchars=fold:x') + screen:expect([[ + ^+-- 2 lines: fooxxxxxxxx│+-- 2 lines: foo·······| + ~ │~ | + ~ │~ | + ~ │~ | + | + ]]) + end) + it('using :set clears window-local value', function() + screen:try_resize(50, 5) + insert("foo\nbar") + command('set laststatus=0') + command('setl fillchars=fold:x') + command('1,2fold') + command('vsplit') + command('set fillchars&') + screen:expect([[ + ^+-- 2 lines: foo········│+-- 2 lines: fooxxxxxxx| + ~ │~ | + ~ │~ | + ~ │~ | + | + ]]) end) end) @@ -122,7 +138,7 @@ describe("'listchars'", function() | ]]) end) - it('has value local to window', function() + it('has window-local value', function() feed('i<tab><tab><tab><esc>') command('set list laststatus=0') command('setl listchars=tab:<->') @@ -136,4 +152,18 @@ describe("'listchars'", function() | ]]) end) + it('using :set clears window-local value', function() + feed('i<tab><tab><tab><esc>') + command('set list laststatus=0') + command('setl listchars=tab:<->') + command('vsplit') + command('set listchars=tab:>-,eol:$') + screen:expect([[ + >------->-------^>-------$│<------><------><------>| + ~ │~ | + ~ │~ | + ~ │~ | + | + ]]) + end) end) diff --git a/test/functional/plugin/health_spec.lua b/test/functional/plugin/health_spec.lua index b567b3e20c..a9bd76ce24 100644 --- a/test/functional/plugin/health_spec.lua +++ b/test/functional/plugin/health_spec.lua @@ -153,10 +153,14 @@ 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: - function health#check, line 24]]) + function health#check, line 20]]) eq(expected, received) end) @@ -167,11 +171,21 @@ describe('health.vim', function() broken: health#broken#check ======================================================================== - ERROR: Failed to run healthcheck for "broken" plugin. Exception: - function health#check[24]..health#broken#check, line 1 + function health#check[20]..health#broken#check, line 1 caused an error ]]) 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) @@ -186,7 +200,7 @@ describe('health.vim', function() test_plug.submodule_failed: require("test_plug.submodule_failed.health").check() ======================================================================== - ERROR: Failed to run healthcheck for "test_plug.submodule_failed" plugin. Exception: - function health#check, line 24]]) + function health#check, line 20]]) eq(expected, received) end) @@ -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/diagnostic_spec.lua b/test/functional/plugin/lsp/diagnostic_spec.lua index 1269a2350c..83d794b620 100644 --- a/test/functional/plugin/lsp/diagnostic_spec.lua +++ b/test/functional/plugin/lsp/diagnostic_spec.lua @@ -110,6 +110,7 @@ describe('vim.lsp.diagnostic', function() } ]] eq({code = 42, tags = {"foo", "bar"}, data = "Hello world"}, result[1].user_data.lsp) + eq(42, result[1].code) eq(42, result[2].code) eq({"foo", "bar"}, result[2].tags) eq("Hello world", result[2].data) diff --git a/test/functional/plugin/lsp/incremental_sync_spec.lua b/test/functional/plugin/lsp/incremental_sync_spec.lua index 5dd34e7665..4985da9cd7 100644 --- a/test/functional/plugin/lsp/incremental_sync_spec.lua +++ b/test/functional/plugin/lsp/incremental_sync_spec.lua @@ -164,6 +164,239 @@ describe('incremental synchronization', function() } test_edit({"a"}, {"rb"}, expected_text_changes, 'utf-16', '\n') end) + it('deleting a line', function() + local expected_text_changes = { + { + range = { + ['start'] = { + character = 0, + line = 0 + }, + ['end'] = { + character = 0, + line = 1 + } + }, + rangeLength = 12, + text = '' + } + } + test_edit({"hello world"}, {"dd"}, expected_text_changes, 'utf-16', '\n') + end) + it('deleting an empty line', function() + local expected_text_changes = { + { + range = { + ['start'] = { + character = 0, + line = 1 + }, + ['end'] = { + character = 0, + line = 2 + } + }, + rangeLength = 1, + text = '' + } + } + test_edit({"hello world", ""}, {"jdd"}, expected_text_changes, 'utf-16', '\n') + end) + it('adding a line', function() + local expected_text_changes = { + { + range = { + ['start'] = { + character = 11, + line = 0, + }, + ['end'] = { + character = 0, + line = 1 + } + }, + rangeLength = 1, + text = '\nhello world\n' + } + } + test_edit({"hello world"}, {"yyp"}, expected_text_changes, 'utf-16', '\n') + end) + it('adding an empty line', function() + local expected_text_changes = { + { + range = { + ['start'] = { + character = 11, + line = 0 + }, + ['end'] = { + character = 0, + line = 1 + } + }, + rangeLength = 1, + text = '\n\n' + } + } + test_edit({"hello world"}, {"o"}, expected_text_changes, 'utf-16', '\n') + end) + it('adding a line to an empty buffer', function() + local expected_text_changes = { + { + range = { + ['start'] = { + character = 0, + line = 0 + }, + ['end'] = { + character = 0, + line = 1 + } + }, + rangeLength = 1, + text = '\n\n' + } + } + test_edit({""}, {"o"}, expected_text_changes, 'utf-16', '\n') + end) + it('insert a line above the current line', function() + local expected_text_changes = { + { + range = { + ['start'] = { + character = 0, + line = 0 + }, + ['end'] = { + character = 0, + line = 0 + } + }, + rangeLength = 0, + text = '\n' + } + } + test_edit({""}, {"O"}, expected_text_changes, 'utf-16', '\n') + end) + end) + describe('multi line edit', function() + it('deletion and insertion', function() + local expected_text_changes = { + -- delete "_fsda" from end of line 1 + { + range = { + ['start'] = { + character = 4, + line = 1 + }, + ['end'] = { + character = 9, + line = 1 + } + }, + rangeLength = 5, + text = '' + }, + -- delete "hello world\n" from line 2 + { + range = { + ['start'] = { + character = 0, + line = 2 + }, + ['end'] = { + character = 0, + line = 3 + } + }, + rangeLength = 12, + text = '' + }, + -- delete "1234" from beginning of line 2 + { + range = { + ['start'] = { + character = 0, + line = 2 + }, + ['end'] = { + character = 4, + line = 2 + } + }, + rangeLength = 4, + text = '' + }, + -- add " asdf" to end of line 1 + { + range = { + ['start'] = { + character = 4, + line = 1 + }, + ['end'] = { + character = 4, + line = 1 + } + }, + rangeLength = 0, + text = ' asdf' + }, + -- delete " asdf\n" from line 2 + { + range = { + ['start'] = { + character = 0, + line = 2 + }, + ['end'] = { + character = 0, + line = 3 + } + }, + rangeLength = 6, + text = '' + }, + -- undo entire deletion + { + range = { + ['start'] = { + character = 4, + line = 1 + }, + ['end'] = { + character = 9, + line = 1 + } + }, + rangeLength = 5, + text = "_fdsa\nhello world\n1234 asdf" + }, + -- redo entire deletion + { + range = { + ['start'] = { + character = 4, + line = 1 + }, + ['end'] = { + character = 9, + line = 3 + } + }, + rangeLength = 27, + text = ' asdf' + }, + } + local original_lines = { + "\\begin{document}", + "test_fdsa", + "hello world", + "1234 asdf", + "\\end{document}" + } + test_edit(original_lines, {"jf_vejjbhhdu<C-R>"}, expected_text_changes, 'utf-16', '\n') + end) end) describe('multi-operation edits', function() @@ -297,6 +530,80 @@ describe('incremental synchronization', function() } test_edit({"🔥"}, {"x"}, expected_text_changes, 'utf-16', '\n') end) + it('replacing a multibyte character with matching prefix', function() + local expected_text_changes = { + { + range = { + ['start'] = { + character = 0, + line = 1 + }, + ['end'] = { + character = 1, + line = 1 + } + }, + rangeLength = 1, + text = '⟩' + } + } + -- ⟨ is e29fa8, ⟩ is e29fa9 + local original_lines = { + "\\begin{document}", + "⟨", + "\\end{document}", + } + test_edit(original_lines, {"jr⟩"}, expected_text_changes, 'utf-16', '\n') + end) + it('replacing a multibyte character with matching suffix', function() + local expected_text_changes = { + { + range = { + ['start'] = { + character = 0, + line = 1 + }, + ['end'] = { + character = 1, + line = 1 + } + }, + rangeLength = 1, + text = 'ḟ' + } + } + -- ฟ is e0b89f, ḟ is e1b89f + local original_lines = { + "\\begin{document}", + "ฟ", + "\\end{document}", + } + test_edit(original_lines, {"jrḟ"}, expected_text_changes, 'utf-16', '\n') + end) + it('inserting before a multibyte character', function() + local expected_text_changes = { + { + range = { + ['start'] = { + character = 0, + line = 1 + }, + ['end'] = { + character = 0, + line = 1 + } + }, + rangeLength = 0, + text = ' ' + } + } + local original_lines = { + "\\begin{document}", + "→", + "\\end{document}", + } + test_edit(original_lines, {"ji "}, expected_text_changes, 'utf-16', '\n') + end) it('deleting a multibyte character from a long line', function() local expected_text_changes = { { diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index 228fc06e9b..6cda9af0f4 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -66,12 +66,18 @@ local function fake_lsp_server_setup(test_name, timeout_ms, options) end end; }); - root_dir = vim.loop.cwd(); + workspace_folders = {{ + uri = 'file://' .. vim.loop.cwd(), + name = 'test_folder', + }}; 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; @@ -153,7 +159,10 @@ describe('LSP', function() "-c", string.format("lua TEST_NAME = %q", test_name), "-c", "luafile "..fixture_filename; }; - root_dir = vim.loop.cwd(); + workspace_folders = {{ + uri = 'file://' .. vim.loop.cwd(), + name = 'test_folder', + }}; } end TEST_CLIENT1 = test__start_client() @@ -295,6 +304,43 @@ describe('LSP', function() } end) + it('should detach buffer in response to nvim_buf_detach', function() + local expected_handlers = { + {NIL, {}, {method="shutdown", client_id=1}}; + {NIL, {}, {method="finish", client_id=1}}; + } + local client + test_rpc_server { + test_name = "basic_finish"; + on_setup = function() + exec_lua [[ + BUFFER = vim.api.nvim_create_buf(false, true) + ]] + eq(true, exec_lua("return lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID)")) + eq(true, exec_lua("return lsp.buf_is_attached(BUFFER, TEST_RPC_CLIENT_ID)")) + exec_lua [[ + vim.api.nvim_command(BUFFER.."bwipeout") + ]] + end; + on_init = function(_client) + client = _client + client.notify('finish') + 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) + eq(table.remove(expected_handlers), {err, result, ctx}, "expected handler") + if ctx.method == 'finish' then + exec_lua("return lsp.buf_detach_client(BUFFER, TEST_RPC_CLIENT_ID)") + eq(false, exec_lua("return lsp.buf_is_attached(BUFFER, TEST_RPC_CLIENT_ID)")) + client.stop() + end + end; + } + end) + it('client should return settings via workspace/configuration handler', function() local expected_handlers = { {NIL, {}, {method="shutdown", client_id=1}}; @@ -883,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) @@ -1192,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'; @@ -1212,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'; @@ -1236,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'; @@ -1253,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'; @@ -1266,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'; @@ -1276,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() @@ -1283,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'; @@ -1300,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'; ''; @@ -1317,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)) @@ -1329,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'; @@ -1344,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'; @@ -1360,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'; @@ -1376,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) @@ -1418,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'; @@ -1430,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'; @@ -1443,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 @@ -1469,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) @@ -1553,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)) @@ -1575,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)) @@ -1592,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() @@ -1610,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) @@ -1630,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) @@ -1651,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) @@ -1812,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) @@ -1842,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) @@ -2146,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('.')]], @@ -2220,19 +2352,38 @@ 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) + local function test_tabstop(tabsize, shiftwidth) exec_lua(string.format([[ - vim.api.nvim_buf_set_option(0, 'softtabstop', %d) + vim.api.nvim_buf_set_option(0, 'shiftwidth', %d) vim.api.nvim_buf_set_option(0, 'tabstop', 2) - vim.api.nvim_buf_set_option(0, 'shiftwidth', 3) - ]], softtabstop)) + ]], shiftwidth)) eq(tabsize, exec_lua('return vim.lsp.util.get_effective_tabstop()')) end - it('with softtabstop = 1', function() test_tabstop(1, 1) end) - it('with softtabstop = 0', function() test_tabstop(2, 0) end) - it('with softtabstop = -1', function() test_tabstop(3, -1) end) + it('with shiftwidth = 1', function() test_tabstop(1, 1) end) + it('with shiftwidth = 0', function() test_tabstop(2, 0) end) end) describe('vim.lsp.buf.outgoing_calls', function() diff --git a/test/functional/plugin/matchparen_spec.lua b/test/functional/plugin/matchparen_spec.lua index 13e1283e2c..2670734c1a 100644 --- a/test/functional/plugin/matchparen_spec.lua +++ b/test/functional/plugin/matchparen_spec.lua @@ -27,7 +27,7 @@ describe('matchparen', function() feed('{<cr>') feed('}') - -- critical part: up + cr should result in an empty line inbetween the + -- critical part: up + cr should result in an empty line in between the -- brackets... if the bug is there, the empty line will be before the '{' feed('<up>') feed('<cr>') 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/clipboard_spec.lua b/test/functional/provider/clipboard_spec.lua index 986db96a18..5bdfec574e 100644 --- a/test/functional/provider/clipboard_spec.lua +++ b/test/functional/provider/clipboard_spec.lua @@ -50,29 +50,33 @@ local function basic_register_test(noblock) text, stuff and some more some some text, stuff and some more]]) - -- deleting a word to named ("a) updates "1 (and not "-) + -- deleting a word to named ("a) doesn't update "1 or "- feed('gg"adwj"1P^"-P') expect([[ , stuff and some more - some textsome some text, stuff and some more]]) + some some random text + some some text, stuff and some more]]) -- deleting a line does update "" feed('ggdd""P') expect([[ , stuff and some more - some textsome some text, stuff and some more]]) + some some random text + some some text, stuff and some more]]) feed('ggw<c-v>jwyggP') if noblock then expect([[ stuf - me t + me s , stuff and some more - some textsome some text, stuff and some more]]) + some some random text + some some text, stuff and some more]]) else expect([[ stuf, stuff and some more - me tsome textsome some text, stuff and some more]]) + me ssome some random text + some some text, stuff and some more]]) end -- pasting in visual does unnamed delete of visual selection 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..d9c44c3315 100644 --- a/test/functional/provider/python3_spec.lua +++ b/test/functional/provider/python3_spec.lua @@ -8,6 +8,8 @@ local source = helpers.source local missing_provider = helpers.missing_provider local matches = helpers.matches local pcall_err = helpers.pcall_err +local funcs = helpers.funcs +local dedent = helpers.dedent do clear() @@ -48,7 +50,12 @@ describe('python3 provider', function() local very_long_symbol = string.rep('a', 1200) feed_command(':silent! py3 print('..very_long_symbol..' b)') -- Error message will contain this (last) line. - eq('Error invoking \'python_execute\' on channel 3 (python3-script-host):\n File "<string>", line 1\n print('..very_long_symbol..' b)\n '..string.rep(' ',1200)..' ^\nSyntaxError: invalid syntax', eval('v:errmsg')) + matches(string.format(dedent([[ + ^Error invoking 'python_execute' on channel 3 %%(python3%%-script%%-host%%): + File "<string>", line 1 + print%%(%s b%%) + %%C* + SyntaxError: invalid syntax%%C*$]]), very_long_symbol), eval('v:errmsg')) end) it('python3_execute with nested commands', function() @@ -93,16 +100,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 +151,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 7dcca231ee..1cef771f0d 100644 --- a/test/functional/terminal/buffer_spec.lua +++ b/test/functional/terminal/buffer_spec.lua @@ -6,9 +6,11 @@ local poke_eventloop = helpers.poke_eventloop local eval, feed_command, source = helpers.eval, helpers.feed_command, helpers.source local eq, neq = helpers.eq, helpers.neq local write_file = helpers.write_file -local command= helpers.command +local command = helpers.command local exc_exec = helpers.exc_exec local matches = helpers.matches +local exec_lua = helpers.exec_lua +local sleep = helpers.sleep describe(':terminal buffer', function() local screen @@ -256,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') @@ -290,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) @@ -328,3 +330,37 @@ describe('No heap-buffer-overflow when', function() assert_alive() end) end) + +describe('on_lines does not emit out-of-bounds line indexes when', function() + before_each(function() + clear() + exec_lua([[ + function _G.register_callback(bufnr) + _G.cb_error = '' + vim.api.nvim_buf_attach(bufnr, false, { + on_lines = function(_, bufnr, _, firstline, _, _) + local status, msg = pcall(vim.api.nvim_buf_get_offset, bufnr, firstline) + if not status then + _G.cb_error = msg + end + end + }) + end + ]]) + end) + + it('creating a terminal buffer #16394', function() + 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]])) + end) + + it('deleting a terminal buffer #16394', function() + feed_command('terminal') + sleep(500) + feed_command('lua _G.register_callback(0)') + feed_command('bdelete!') + eq('', exec_lua([[return _G.cb_error]])) + end) +end) diff --git a/test/functional/terminal/channel_spec.lua b/test/functional/terminal/channel_spec.lua index 7d37dcccc8..b5f3c2bd31 100644 --- a/test/functional/terminal/channel_spec.lua +++ b/test/functional/terminal/channel_spec.lua @@ -1,49 +1,94 @@ local helpers = require('test.functional.helpers')(after_each) +local Screen = require('test.functional.ui.screen') local clear = helpers.clear local eq = helpers.eq +local eval = helpers.eval local command = helpers.command -local exc_exec = helpers.exc_exec +local pcall_err = helpers.pcall_err local feed = helpers.feed -local sleep = helpers.sleep local poke_eventloop = helpers.poke_eventloop -describe('associated channel is closed and later freed for terminal', function() - before_each(clear) +describe('terminal channel is closed and later released if', function() + local screen + + before_each(function() + clear() + screen = Screen.new() + screen:attach() + end) it('opened by nvim_open_term() and deleted by :bdelete!', function() command([[let id = nvim_open_term(0, {})]]) - -- channel hasn't been freed yet - eq("Vim(call):Can't send data to closed stream", exc_exec([[bdelete! | call chansend(id, 'test')]])) - -- process free_channel_event + local chans = eval('len(nvim_list_chans())') + -- channel hasn't been released yet + eq("Vim(call):Can't send data to closed stream", + pcall_err(command, [[bdelete! | call chansend(id, 'test')]])) + -- channel has been released after one main loop iteration + eq(chans - 1, eval('len(nvim_list_chans())')) + end) + + it('opened by nvim_open_term(), closed by chanclose(), and deleted by pressing a key', function() + command('let id = nvim_open_term(0, {})') + local chans = eval('len(nvim_list_chans())') + -- channel has been closed but not released + eq("Vim(call):Can't send data to closed stream", + pcall_err(command, [[call chanclose(id) | call chansend(id, 'test')]])) + screen:expect({any='%[Terminal closed%]'}) + eq(chans, eval('len(nvim_list_chans())')) + -- delete terminal + feed('i<CR>') + -- need to first process input poke_eventloop() - -- channel has been freed - eq("Vim(call):E900: Invalid channel id", exc_exec([[call chansend(id, 'test')]])) + -- channel has been released after another main loop iteration + eq(chans - 1, eval('len(nvim_list_chans())')) + end) + + it('opened by nvim_open_term(), closed by chanclose(), and deleted by :bdelete', function() + command('let id = nvim_open_term(0, {})') + local chans = eval('len(nvim_list_chans())') + -- channel has been closed but not released + eq("Vim(call):Can't send data to closed stream", + pcall_err(command, [[call chanclose(id) | call chansend(id, 'test')]])) + screen:expect({any='%[Terminal closed%]'}) + eq(chans, eval('len(nvim_list_chans())')) + -- channel still hasn't been released yet + eq("Vim(call):Can't send data to closed stream", + pcall_err(command, [[bdelete | call chansend(id, 'test')]])) + -- channel has been released after one main loop iteration + eq(chans - 1, eval('len(nvim_list_chans())')) end) it('opened by termopen(), exited, and deleted by pressing a key', function() command([[let id = termopen('echo')]]) - sleep(500) - -- process has exited - eq("Vim(call):Can't send data to closed stream", exc_exec([[call chansend(id, 'test')]])) + local chans = eval('len(nvim_list_chans())') + -- wait for process to exit + screen:expect({any='%[Process exited 0%]'}) + -- process has exited but channel has't been released + eq("Vim(call):Can't send data to closed stream", + pcall_err(command, [[call chansend(id, 'test')]])) + eq(chans, eval('len(nvim_list_chans())')) -- delete terminal feed('i<CR>') - -- process term_delayed_free and free_channel_event + -- need to first process input poke_eventloop() - -- channel has been freed - eq("Vim(call):E900: Invalid channel id", exc_exec([[call chansend(id, 'test')]])) + -- channel has been released after another main loop iteration + eq(chans - 1, eval('len(nvim_list_chans())')) end) -- This indirectly covers #16264 it('opened by termopen(), exited, and deleted by :bdelete', function() command([[let id = termopen('echo')]]) - sleep(500) - -- process has exited - eq("Vim(call):Can't send data to closed stream", exc_exec([[call chansend(id, 'test')]])) - -- channel hasn't been freed yet - eq("Vim(call):Can't send data to closed stream", exc_exec([[bdelete | call chansend(id, 'test')]])) - -- process term_delayed_free and free_channel_event - poke_eventloop() - -- channel has been freed - eq("Vim(call):E900: Invalid channel id", exc_exec([[call chansend(id, 'test')]])) + local chans = eval('len(nvim_list_chans())') + -- wait for process to exit + screen:expect({any='%[Process exited 0%]'}) + -- process has exited but channel hasn't been released + eq("Vim(call):Can't send data to closed stream", + pcall_err(command, [[call chansend(id, 'test')]])) + eq(chans, eval('len(nvim_list_chans())')) + -- channel still hasn't been released yet + eq("Vim(call):Can't send data to closed stream", + pcall_err(command, [[bdelete | call chansend(id, 'test')]])) + -- channel has been released after one main loop iteration + eq(chans - 1, eval('len(nvim_list_chans())')) end) end) diff --git a/test/functional/terminal/cursor_spec.lua b/test/functional/terminal/cursor_spec.lua index 8d70ebf679..3b905f1f56 100644 --- a/test/functional/terminal/cursor_spec.lua +++ b/test/functional/terminal/cursor_spec.lua @@ -3,6 +3,8 @@ local Screen = require('test.functional.ui.screen') local thelpers = require('test.functional.terminal.helpers') local feed, clear, nvim = helpers.feed, helpers.clear, helpers.nvim local nvim_dir, command = helpers.nvim_dir, helpers.command +local nvim_prog = helpers.nvim_prog +local eq, eval = helpers.eq, helpers.eval local feed_command = helpers.feed_command local hide_cursor = thelpers.hide_cursor local show_cursor = thelpers.show_cursor @@ -85,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 | @@ -173,3 +176,707 @@ describe('cursor with customized highlighting', function() end) 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) + screen = thelpers.screen_setup(0, '["'..nvim_prog + ..[[", "-u", "NONE", "-i", "NONE", "-E", "--cmd", "let @r = ']]..str..[['", ]] + -- <Left> and <Right> don't always work + ..[["--cmd", "cnoremap <C-X> <Left>", "--cmd", "cnoremap <C-O> <Right>"]]..']', 70) + screen:expect([[ + | + | + | + | + Entering Ex mode. Type "visual" to go to Normal mode. | + :{1: } | + {3:-- TERMINAL --} | + ]]) + end + + before_each(clear) + + describe('in a line with no multibyte characters or trailing spaces,', function() + before_each(function() + setup_ex_register('aaaaaaaa') + end) + + it('at the end', function() + feed('<C-R>r') + screen:expect([[ + | + | + | + | + Entering Ex mode. Type "visual" to go to Normal mode. | + :aaaaaaaa{1: } | + {3:-- TERMINAL --} | + ]]) + eq({6, 9}, eval('nvim_win_get_cursor(0)')) + feed([[<C-\><C-N>]]) + screen:expect([[ + | + | + | + | + Entering Ex mode. Type "visual" to go to Normal mode. | + :aaaaaaa^a{2: } | + | + ]]) + eq({6, 8}, eval('nvim_win_get_cursor(0)')) + end) + + it('near the end', function() + feed('<C-R>r<C-X><C-X>') + screen:expect([[ + | + | + | + | + Entering Ex mode. Type "visual" to go to Normal mode. | + :aaaaaa{1:a}a | + {3:-- TERMINAL --} | + ]]) + eq({6, 7}, eval('nvim_win_get_cursor(0)')) + feed([[<C-\><C-N>]]) + screen:expect([[ + | + | + | + | + Entering Ex mode. Type "visual" to go to Normal mode. | + :aaaaa^a{2:a}a | + | + ]]) + eq({6, 6}, eval('nvim_win_get_cursor(0)')) + end) + + it('near the start', function() + feed('<C-R>r<C-B><C-O>') + screen:expect([[ + | + | + | + | + Entering Ex mode. Type "visual" to go to Normal mode. | + :a{1:a}aaaaaa | + {3:-- TERMINAL --} | + ]]) + eq({6, 2}, eval('nvim_win_get_cursor(0)')) + feed([[<C-\><C-N>]]) + screen:expect([[ + | + | + | + | + Entering Ex mode. Type "visual" to go to Normal mode. | + :^a{2:a}aaaaaa | + | + ]]) + eq({6, 1}, eval('nvim_win_get_cursor(0)')) + end) + end) + + describe('in a line with single-cell multibyte characters and no trailing spaces,', function() + before_each(function() + setup_ex_register('µµµµµµµµ') + end) + + it('at the end', function() + feed('<C-R>r') + screen:expect([[ + | + | + | + | + Entering Ex mode. Type "visual" to go to Normal mode. | + :µµµµµµµµ{1: } | + {3:-- TERMINAL --} | + ]]) + eq({6, 17}, eval('nvim_win_get_cursor(0)')) + feed([[<C-\><C-N>]]) + screen:expect([[ + | + | + | + | + Entering Ex mode. Type "visual" to go to Normal mode. | + :µµµµµµµ^µ{2: } | + | + ]]) + eq({6, 15}, eval('nvim_win_get_cursor(0)')) + end) + + it('near the end', function() + feed('<C-R>r<C-X><C-X>') + screen:expect([[ + | + | + | + | + Entering Ex mode. Type "visual" to go to Normal mode. | + :µµµµµµ{1:µ}µ | + {3:-- TERMINAL --} | + ]]) + eq({6, 13}, eval('nvim_win_get_cursor(0)')) + feed([[<C-\><C-N>]]) + screen:expect([[ + | + | + | + | + Entering Ex mode. Type "visual" to go to Normal mode. | + :µµµµµ^µ{2:µ}µ | + | + ]]) + eq({6, 11}, eval('nvim_win_get_cursor(0)')) + end) + + it('near the start', function() + feed('<C-R>r<C-B><C-O>') + screen:expect([[ + | + | + | + | + Entering Ex mode. Type "visual" to go to Normal mode. | + :µ{1:µ}µµµµµµ | + {3:-- TERMINAL --} | + ]]) + eq({6, 3}, eval('nvim_win_get_cursor(0)')) + feed([[<C-\><C-N>]]) + screen:expect([[ + | + | + | + | + Entering Ex mode. Type "visual" to go to Normal mode. | + :^µ{2:µ}µµµµµµ | + | + ]]) + eq({6, 1}, eval('nvim_win_get_cursor(0)')) + end) + end) + + describe('in a line with single-cell composed multibyte characters and no trailing spaces,', function() + if helpers.pending_win32(pending) then return end -- These tests fail on Windows. Encoding problem? + + before_each(function() + setup_ex_register('µ̳µ̳µ̳µ̳µ̳µ̳µ̳µ̳') + end) + + it('at the end', function() + feed('<C-R>r') + screen:expect([[ + | + | + | + | + Entering Ex mode. Type "visual" to go to Normal mode. | + :µ̳µ̳µ̳µ̳µ̳µ̳µ̳µ̳{1: } | + {3:-- TERMINAL --} | + ]]) + eq({6, 33}, eval('nvim_win_get_cursor(0)')) + feed([[<C-\><C-N>]]) + screen:expect([[ + | + | + | + | + Entering Ex mode. Type "visual" to go to Normal mode. | + :µ̳µ̳µ̳µ̳µ̳µ̳µ̳^µ̳{2: } | + | + ]]) + eq({6, 29}, eval('nvim_win_get_cursor(0)')) + end) + + it('near the end', function() + feed('<C-R>r<C-X><C-X>') + screen:expect([[ + | + | + | + | + Entering Ex mode. Type "visual" to go to Normal mode. | + :µ̳µ̳µ̳µ̳µ̳µ̳{1:µ̳}µ̳ | + {3:-- TERMINAL --} | + ]]) + eq({6, 25}, eval('nvim_win_get_cursor(0)')) + feed([[<C-\><C-N>]]) + screen:expect([[ + | + | + | + | + Entering Ex mode. Type "visual" to go to Normal mode. | + :µ̳µ̳µ̳µ̳µ̳^µ̳{2:µ̳}µ̳ | + | + ]]) + eq({6, 21}, eval('nvim_win_get_cursor(0)')) + end) + + it('near the start', function() + feed('<C-R>r<C-B><C-O>') + screen:expect([[ + | + | + | + | + Entering Ex mode. Type "visual" to go to Normal mode. | + :µ̳{1:µ̳}µ̳µ̳µ̳µ̳µ̳µ̳ | + {3:-- TERMINAL --} | + ]]) + eq({6, 5}, eval('nvim_win_get_cursor(0)')) + feed([[<C-\><C-N>]]) + screen:expect([[ + | + | + | + | + Entering Ex mode. Type "visual" to go to Normal mode. | + :^µ̳{2:µ̳}µ̳µ̳µ̳µ̳µ̳µ̳ | + | + ]]) + eq({6, 1}, eval('nvim_win_get_cursor(0)')) + end) + end) + + describe('in a line with double-cell multibyte characters and no trailing spaces,', function() + if helpers.pending_win32(pending) then return end -- These tests fail on Windows. Encoding problem? + + before_each(function() + setup_ex_register('哦哦哦哦哦哦哦哦') + end) + + it('at the end', function() + feed('<C-R>r') + screen:expect([[ + | + | + | + | + Entering Ex mode. Type "visual" to go to Normal mode. | + :哦哦哦哦哦哦哦哦{1: } | + {3:-- TERMINAL --} | + ]]) + eq({6, 25}, eval('nvim_win_get_cursor(0)')) + feed([[<C-\><C-N>]]) + screen:expect([[ + | + | + | + | + Entering Ex mode. Type "visual" to go to Normal mode. | + :哦哦哦哦哦哦哦^哦{2: } | + | + ]]) + eq({6, 22}, eval('nvim_win_get_cursor(0)')) + end) + + it('near the end', function() + feed('<C-R>r<C-X><C-X>') + screen:expect([[ + | + | + | + | + Entering Ex mode. Type "visual" to go to Normal mode. | + :哦哦哦哦哦哦{1:哦}哦 | + {3:-- TERMINAL --} | + ]]) + eq({6, 19}, eval('nvim_win_get_cursor(0)')) + feed([[<C-\><C-N>]]) + screen:expect([[ + | + | + | + | + Entering Ex mode. Type "visual" to go to Normal mode. | + :哦哦哦哦哦^哦{2:哦}哦 | + | + ]]) + eq({6, 16}, eval('nvim_win_get_cursor(0)')) + end) + + it('near the start', function() + feed('<C-R>r<C-B><C-O>') + screen:expect([[ + | + | + | + | + Entering Ex mode. Type "visual" to go to Normal mode. | + :哦{1:哦}哦哦哦哦哦哦 | + {3:-- TERMINAL --} | + ]]) + eq({6, 4}, eval('nvim_win_get_cursor(0)')) + feed([[<C-\><C-N>]]) + screen:expect([[ + | + | + | + | + Entering Ex mode. Type "visual" to go to Normal mode. | + :^哦{2:哦}哦哦哦哦哦哦 | + | + ]]) + eq({6, 1}, eval('nvim_win_get_cursor(0)')) + end) + end) +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) + screen = thelpers.screen_setup(0, '["'..nvim_prog + ..[[", "-u", "NONE", "-i", "NONE", "-E", "--cmd", "let @r = ']]..str..[['", ]] + -- <Left> and <Right> don't always work + ..[["--cmd", "cnoremap <C-X> <Left>", "--cmd", "cnoremap <C-O> <Right>"]]..']', 70) + screen:expect([[ + {7: 1 } | + {7: 2 } | + {7: 3 } | + {7: 4 } | + {7: 5 }Entering Ex mode. Type "visual" to go to Normal mode. | + {7: 6 }:{1: } | + {3:-- TERMINAL --} | + ]]) + end + + before_each(function() + clear() + command('set number') + end) + + describe('in a line with no multibyte characters or trailing spaces,', function() + before_each(function() + setup_ex_register('aaaaaaaa') + end) + + it('at the end', function() + feed('<C-R>r') + screen:expect([[ + {7: 1 } | + {7: 2 } | + {7: 3 } | + {7: 4 } | + {7: 5 }Entering Ex mode. Type "visual" to go to Normal mode. | + {7: 6 }:aaaaaaaa{1: } | + {3:-- TERMINAL --} | + ]]) + eq({6, 9}, eval('nvim_win_get_cursor(0)')) + feed([[<C-\><C-N>]]) + screen:expect([[ + {7: 1 } | + {7: 2 } | + {7: 3 } | + {7: 4 } | + {7: 5 }Entering Ex mode. Type "visual" to go to Normal mode. | + {7: 6 }:aaaaaaa^a{2: } | + | + ]]) + eq({6, 8}, eval('nvim_win_get_cursor(0)')) + end) + + it('near the end', function() + feed('<C-R>r<C-X><C-X>') + screen:expect([[ + {7: 1 } | + {7: 2 } | + {7: 3 } | + {7: 4 } | + {7: 5 }Entering Ex mode. Type "visual" to go to Normal mode. | + {7: 6 }:aaaaaa{1:a}a | + {3:-- TERMINAL --} | + ]]) + eq({6, 7}, eval('nvim_win_get_cursor(0)')) + feed([[<C-\><C-N>]]) + screen:expect([[ + {7: 1 } | + {7: 2 } | + {7: 3 } | + {7: 4 } | + {7: 5 }Entering Ex mode. Type "visual" to go to Normal mode. | + {7: 6 }:aaaaa^a{2:a}a | + | + ]]) + eq({6, 6}, eval('nvim_win_get_cursor(0)')) + end) + + it('near the start', function() + feed('<C-R>r<C-B><C-O>') + screen:expect([[ + {7: 1 } | + {7: 2 } | + {7: 3 } | + {7: 4 } | + {7: 5 }Entering Ex mode. Type "visual" to go to Normal mode. | + {7: 6 }:a{1:a}aaaaaa | + {3:-- TERMINAL --} | + ]]) + eq({6, 2}, eval('nvim_win_get_cursor(0)')) + feed([[<C-\><C-N>]]) + screen:expect([[ + {7: 1 } | + {7: 2 } | + {7: 3 } | + {7: 4 } | + {7: 5 }Entering Ex mode. Type "visual" to go to Normal mode. | + {7: 6 }:^a{2:a}aaaaaa | + | + ]]) + eq({6, 1}, eval('nvim_win_get_cursor(0)')) + end) + end) + + describe('in a line with single-cell multibyte characters and no trailing spaces,', function() + before_each(function() + setup_ex_register('µµµµµµµµ') + end) + + it('at the end', function() + feed('<C-R>r') + screen:expect([[ + {7: 1 } | + {7: 2 } | + {7: 3 } | + {7: 4 } | + {7: 5 }Entering Ex mode. Type "visual" to go to Normal mode. | + {7: 6 }:µµµµµµµµ{1: } | + {3:-- TERMINAL --} | + ]]) + eq({6, 17}, eval('nvim_win_get_cursor(0)')) + feed([[<C-\><C-N>]]) + screen:expect([[ + {7: 1 } | + {7: 2 } | + {7: 3 } | + {7: 4 } | + {7: 5 }Entering Ex mode. Type "visual" to go to Normal mode. | + {7: 6 }:µµµµµµµ^µ{2: } | + | + ]]) + eq({6, 15}, eval('nvim_win_get_cursor(0)')) + end) + + it('near the end', function() + feed('<C-R>r<C-X><C-X>') + screen:expect([[ + {7: 1 } | + {7: 2 } | + {7: 3 } | + {7: 4 } | + {7: 5 }Entering Ex mode. Type "visual" to go to Normal mode. | + {7: 6 }:µµµµµµ{1:µ}µ | + {3:-- TERMINAL --} | + ]]) + eq({6, 13}, eval('nvim_win_get_cursor(0)')) + feed([[<C-\><C-N>]]) + screen:expect([[ + {7: 1 } | + {7: 2 } | + {7: 3 } | + {7: 4 } | + {7: 5 }Entering Ex mode. Type "visual" to go to Normal mode. | + {7: 6 }:µµµµµ^µ{2:µ}µ | + | + ]]) + eq({6, 11}, eval('nvim_win_get_cursor(0)')) + end) + + it('near the start', function() + feed('<C-R>r<C-B><C-O>') + screen:expect([[ + {7: 1 } | + {7: 2 } | + {7: 3 } | + {7: 4 } | + {7: 5 }Entering Ex mode. Type "visual" to go to Normal mode. | + {7: 6 }:µ{1:µ}µµµµµµ | + {3:-- TERMINAL --} | + ]]) + eq({6, 3}, eval('nvim_win_get_cursor(0)')) + feed([[<C-\><C-N>]]) + screen:expect([[ + {7: 1 } | + {7: 2 } | + {7: 3 } | + {7: 4 } | + {7: 5 }Entering Ex mode. Type "visual" to go to Normal mode. | + {7: 6 }:^µ{2:µ}µµµµµµ | + | + ]]) + eq({6, 1}, eval('nvim_win_get_cursor(0)')) + end) + end) + + describe('in a line with single-cell composed multibyte characters and no trailing spaces,', function() + if helpers.pending_win32(pending) then return end -- These tests fail on Windows. Encoding problem? + + before_each(function() + setup_ex_register('µ̳µ̳µ̳µ̳µ̳µ̳µ̳µ̳') + end) + + it('at the end', function() + feed('<C-R>r') + screen:expect([[ + {7: 1 } | + {7: 2 } | + {7: 3 } | + {7: 4 } | + {7: 5 }Entering Ex mode. Type "visual" to go to Normal mode. | + {7: 6 }:µ̳µ̳µ̳µ̳µ̳µ̳µ̳µ̳{1: } | + {3:-- TERMINAL --} | + ]]) + eq({6, 33}, eval('nvim_win_get_cursor(0)')) + feed([[<C-\><C-N>]]) + screen:expect([[ + {7: 1 } | + {7: 2 } | + {7: 3 } | + {7: 4 } | + {7: 5 }Entering Ex mode. Type "visual" to go to Normal mode. | + {7: 6 }:µ̳µ̳µ̳µ̳µ̳µ̳µ̳^µ̳{2: } | + | + ]]) + eq({6, 29}, eval('nvim_win_get_cursor(0)')) + end) + + it('near the end', function() + feed('<C-R>r<C-X><C-X>') + screen:expect([[ + {7: 1 } | + {7: 2 } | + {7: 3 } | + {7: 4 } | + {7: 5 }Entering Ex mode. Type "visual" to go to Normal mode. | + {7: 6 }:µ̳µ̳µ̳µ̳µ̳µ̳{1:µ̳}µ̳ | + {3:-- TERMINAL --} | + ]]) + eq({6, 25}, eval('nvim_win_get_cursor(0)')) + feed([[<C-\><C-N>]]) + screen:expect([[ + {7: 1 } | + {7: 2 } | + {7: 3 } | + {7: 4 } | + {7: 5 }Entering Ex mode. Type "visual" to go to Normal mode. | + {7: 6 }:µ̳µ̳µ̳µ̳µ̳^µ̳{2:µ̳}µ̳ | + | + ]]) + eq({6, 21}, eval('nvim_win_get_cursor(0)')) + end) + + it('near the start', function() + feed('<C-R>r<C-B><C-O>') + screen:expect([[ + {7: 1 } | + {7: 2 } | + {7: 3 } | + {7: 4 } | + {7: 5 }Entering Ex mode. Type "visual" to go to Normal mode. | + {7: 6 }:µ̳{1:µ̳}µ̳µ̳µ̳µ̳µ̳µ̳ | + {3:-- TERMINAL --} | + ]]) + eq({6, 5}, eval('nvim_win_get_cursor(0)')) + feed([[<C-\><C-N>]]) + screen:expect([[ + {7: 1 } | + {7: 2 } | + {7: 3 } | + {7: 4 } | + {7: 5 }Entering Ex mode. Type "visual" to go to Normal mode. | + {7: 6 }:^µ̳{2:µ̳}µ̳µ̳µ̳µ̳µ̳µ̳ | + | + ]]) + eq({6, 1}, eval('nvim_win_get_cursor(0)')) + end) + end) + + describe('in a line with double-cell multibyte characters and no trailing spaces,', function() + if helpers.pending_win32(pending) then return end -- These tests fail on Windows. Encoding problem? + + before_each(function() + setup_ex_register('哦哦哦哦哦哦哦哦') + end) + + it('at the end', function() + feed('<C-R>r') + screen:expect([[ + {7: 1 } | + {7: 2 } | + {7: 3 } | + {7: 4 } | + {7: 5 }Entering Ex mode. Type "visual" to go to Normal mode. | + {7: 6 }:哦哦哦哦哦哦哦哦{1: } | + {3:-- TERMINAL --} | + ]]) + eq({6, 25}, eval('nvim_win_get_cursor(0)')) + feed([[<C-\><C-N>]]) + screen:expect([[ + {7: 1 } | + {7: 2 } | + {7: 3 } | + {7: 4 } | + {7: 5 }Entering Ex mode. Type "visual" to go to Normal mode. | + {7: 6 }:哦哦哦哦哦哦哦^哦{2: } | + | + ]]) + eq({6, 22}, eval('nvim_win_get_cursor(0)')) + end) + + it('near the end', function() + feed('<C-R>r<C-X><C-X>') + screen:expect([[ + {7: 1 } | + {7: 2 } | + {7: 3 } | + {7: 4 } | + {7: 5 }Entering Ex mode. Type "visual" to go to Normal mode. | + {7: 6 }:哦哦哦哦哦哦{1:哦}哦 | + {3:-- TERMINAL --} | + ]]) + eq({6, 19}, eval('nvim_win_get_cursor(0)')) + feed([[<C-\><C-N>]]) + screen:expect([[ + {7: 1 } | + {7: 2 } | + {7: 3 } | + {7: 4 } | + {7: 5 }Entering Ex mode. Type "visual" to go to Normal mode. | + {7: 6 }:哦哦哦哦哦^哦{2:哦}哦 | + | + ]]) + eq({6, 16}, eval('nvim_win_get_cursor(0)')) + end) + + it('near the start', function() + feed('<C-R>r<C-B><C-O>') + screen:expect([[ + {7: 1 } | + {7: 2 } | + {7: 3 } | + {7: 4 } | + {7: 5 }Entering Ex mode. Type "visual" to go to Normal mode. | + {7: 6 }:哦{1:哦}哦哦哦哦哦哦 | + {3:-- TERMINAL --} | + ]]) + eq({6, 4}, eval('nvim_win_get_cursor(0)')) + feed([[<C-\><C-N>]]) + screen:expect([[ + {7: 1 } | + {7: 2 } | + {7: 3 } | + {7: 4 } | + {7: 5 }Entering Ex mode. Type "visual" to go to Normal mode. | + {7: 6 }:^哦{2:哦}哦哦哦哦哦哦 | + | + ]]) + eq({6, 1}, eval('nvim_win_get_cursor(0)')) + end) + end) +end) 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/helpers.lua b/test/functional/terminal/helpers.lua index d909888613..51ecae663a 100644 --- a/test/functional/terminal/helpers.lua +++ b/test/functional/terminal/helpers.lua @@ -7,7 +7,9 @@ local nvim_dir = helpers.nvim_dir local feed_command, nvim = helpers.feed_command, helpers.nvim local function feed_data(data) - nvim('set_var', 'term_data', data) + -- A string containing NUL bytes is not converted to a Blob when + -- calling nvim_set_var() API, so convert it using Lua instead. + nvim('exec_lua', 'vim.g.term_data = ...', {data}) nvim('command', 'call jobsend(b:terminal_job_id, term_data)') end @@ -94,7 +96,7 @@ local function screen_setup(extra_rows, command, cols, opts) table.insert(expected, '{3:-- TERMINAL --}' .. ((' '):rep(cols - 14))) screen:expect(table.concat(expected, '|\n')..'|') else - -- This eval also acts as a wait(). + -- This eval also acts as a poke_eventloop(). if 0 == nvim('eval', "exists('b:terminal_job_id')") then error("terminal job failed to start") end diff --git a/test/functional/terminal/highlight_spec.lua b/test/functional/terminal/highlight_spec.lua index 8d3f0218af..ab4b4e9147 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({ @@ -304,10 +305,17 @@ describe('synIDattr()', function() eq('79', eval('synIDattr(hlID("Keyword"), "fg")')) end) - it('returns "1" if group has "strikethrough" attribute', function() - eq('', eval('synIDattr(hlID("Normal"), "strikethrough")')) - eq('1', eval('synIDattr(hlID("Keyword"), "strikethrough")')) - eq('1', eval('synIDattr(hlID("Keyword"), "strikethrough", "gui")')) + it('returns "1" if group has given highlight attribute', function() + local hl_attrs = { + 'underline', 'underlineline', 'undercurl', 'underdot', 'underdash', 'strikethrough' + } + for _,hl_attr in ipairs(hl_attrs) do + local context = 'using ' .. hl_attr .. ' attr' + command('highlight Keyword cterm=' .. hl_attr .. ' gui=' .. hl_attr) + eq('', eval('synIDattr(hlID("Normal"), "'.. hl_attr .. '")'), context) + eq('1', eval('synIDattr(hlID("Keyword"), "' .. hl_attr .. '")'), context) + eq('1', eval('synIDattr(hlID("Keyword"), "' .. hl_attr .. '", "gui")'), context) + end end) end) diff --git a/test/functional/terminal/mouse_spec.lua b/test/functional/terminal/mouse_spec.lua index 3d8441b93c..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-\\>') @@ -45,6 +65,12 @@ describe(':terminal mouse', function() eq('nt', eval('mode(1)')) end) + it('does not leave terminal mode on left-release', function() + if helpers.pending_win32(pending) then return end + feed('<LeftRelease>') + eq('t', eval('mode(1)')) + end) + describe('with mouse events enabled by the program', function() before_each(function() thelpers.enable_mouse() @@ -60,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([[ @@ -72,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() @@ -88,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 b932c58430..d1cfc7e91b 100644 --- a/test/functional/terminal/scrollback_spec.lua +++ b/test/functional/terminal/scrollback_spec.lua @@ -12,6 +12,8 @@ local curbufmeths = helpers.curbufmeths local nvim = helpers.nvim local feed_data = thelpers.feed_data local pcall_err = helpers.pcall_err +local exec_lua = helpers.exec_lua +local assert_alive = helpers.assert_alive describe(':terminal scrollback', function() local screen @@ -343,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}) @@ -527,3 +530,72 @@ describe("'scrollback' option", function() end) end) + +describe("pending scrollback line handling", function() + local screen + + before_each(function() + clear() + screen = Screen.new(30, 7) + screen:attach() + screen:set_default_attr_ids { + [1] = {foreground = Screen.colors.Brown}, + [2] = {reverse = true}, + [3] = {bold = true}, + } + end) + + it("does not crash after setting 'number' #14891", function() + exec_lua [[ + local a = vim.api + local buf = a.nvim_create_buf(true, true) + local chan = a.nvim_open_term(buf, {}) + a.nvim_win_set_option(0, "number", true) + a.nvim_chan_send(chan, ("a\n"):rep(11) .. "a") + a.nvim_win_set_buf(0, buf) + ]] + screen:expect [[ + {1: 1 }^a | + {1: 2 } a | + {1: 3 } a | + {1: 4 } a | + {1: 5 } a | + {1: 6 } a | + | + ]] + feed('G') + screen:expect [[ + {1: 7 } a | + {1: 8 } a | + {1: 9 } a | + {1: 10 } a | + {1: 11 } a | + {1: 12 } ^a | + | + ]] + assert_alive() + 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) + a.nvim_buf_call(bufnr, function() + vim.fn.termopen({"echo", ("hi\n"):rep(11)}) + end) + a.nvim_win_set_buf(0, bufnr) + vim.cmd("startinsert") + ]] + screen:expect [[ + hi | + hi | + hi | + | + | + [Process exited 0]{2: } | + {3:-- TERMINAL --} | + ]] + assert_alive() + end) +end) diff --git a/test/functional/terminal/tui_spec.lua b/test/functional/terminal/tui_spec.lua index 6b9586b4de..8c6cba4def 100644 --- a/test/functional/terminal/tui_spec.lua +++ b/test/functional/terminal/tui_spec.lua @@ -214,7 +214,7 @@ describe('TUI', function() ]]) end) - it('interprets ESC+key as ALT chord', function() + it('interprets ESC+key as ALT chord in i_CTRL-V', function() -- Vim represents ALT/META by setting the "high bit" of the modified key: -- ALT+j inserts "ê". Nvim does not (#3982). feed_data('i\022\027j') @@ -229,6 +229,38 @@ describe('TUI', function() ]]) end) + it('interprets <Esc>[27u as <Esc>', function() + feed_command('nnoremap <M-;> <Nop>') + feed_command('nnoremap <Esc> AESC<Esc>') + feed_command('nnoremap ; Asemicolon<Esc>') + feed_data('\027[27u;') + screen:expect([[ + ESCsemicolo{1:n} | + {4:~ }| + {4:~ }| + {4:~ }| + {5:[No Name] [+] }| + | + {3:-- TERMINAL --} | + ]]) + -- <Esc>; should be recognized as <M-;> when <M-;> is mapped + feed_data('\027;') + screen:expect_unchanged() + end) + + it('interprets <Esc><Nul> as <M-C-Space> #17198', function() + feed_data('i\022\027\000') + screen:expect([[ + <M-C-Space>{1: } | + {4:~ }| + {4:~ }| + {4:~ }| + {5:[No Name] [+] }| + {3:-- INSERT --} | + {3:-- TERMINAL --} | + ]]) + end) + it('accepts ASCII control sequences', function() feed_data('i') feed_data('\022\007') -- ctrl+g @@ -271,7 +303,7 @@ describe('TUI', function() {3:-- TERMINAL --} | ]]) feed_data('\027[201~') -- End paste. - feed_data('\027\000') -- ESC: go to Normal mode. + feed_data('\027[27u') -- ESC: go to Normal mode. wait_for_mode('n') screen:expect([[ "pasted from termina{1:l}" | @@ -323,7 +355,7 @@ describe('TUI', function() feed_data('just paste it™') feed_data('\027[201~') screen:expect{grid=[[ - thisjust paste it™{1:3} is here | + thisjust paste it{1:™}3 is here | | {4:~ }| {4:~ }| @@ -379,7 +411,7 @@ describe('TUI', function() end) it('paste: normal-mode (+CRLF #10872)', function() - feed_data(':set ruler') + feed_data(':set ruler | echo') wait_for_mode('c') feed_data('\n') wait_for_mode('n') @@ -423,13 +455,13 @@ describe('TUI', function() expect_child_buf_lines(expected_crlf) feed_data('u') expect_child_buf_lines({''}) + feed_data(':echo') + wait_for_mode('c') + feed_data('\n') + wait_for_mode('n') -- CRLF input feed_data('\027[200~'..table.concat(expected_lf,'\r\n')..'\027[201~') - screen:expect{ - grid=expected_grid1:gsub( - ':set ruler *', - '3 fewer lines; before #1 0 seconds ago '), - attr_ids=expected_attr} + screen:expect{grid=expected_grid1, attr_ids=expected_attr} expect_child_buf_lines(expected_crlf) end) @@ -453,7 +485,7 @@ describe('TUI', function() {3:-- TERMINAL --} | ]]} -- Dot-repeat/redo. - feed_data('\027\000') + feed_data('\027[27u') wait_for_mode('n') feed_data('.') screen:expect{grid=[[ @@ -499,7 +531,7 @@ describe('TUI', function() vim.paste = function(lines, phase) error("fake fail") end ]], {}) -- Prepare something for dot-repeat/redo. - feed_data('ifoo\n\027\000') + feed_data('ifoo\n\027[27u') wait_for_mode('n') screen:expect{grid=[[ foo | @@ -541,7 +573,7 @@ describe('TUI', function() {3:-- TERMINAL --} | ]]} -- Editor should still work after failed/drained paste. - feed_data('ityped input...\027\000') + feed_data('ityped input...\027[27u') screen:expect{grid=[[ foo | foo | @@ -575,23 +607,23 @@ describe('TUI', function() vim.paste = function(lines, phase) return false end ]], {}) feed_data('\027[200~line A\nline B\n\027[201~') - feed_data('ifoo\n\027\000') + feed_data('ifoo\n\027[27u') expect_child_buf_lines({'foo',''}) end) it("paste: 'nomodifiable' buffer", function() 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~') screen:expect{grid=[[ | {4:~ }| {5: }| - {8:paste: Error executing lua: vim.lua:243: Vim:E21: }| - {8:Cannot make changes, 'modifiable' is off} | + {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 --} | ]]} @@ -669,7 +701,7 @@ describe('TUI', function() {3:-- INSERT --} | {3:-- TERMINAL --} | ]]) - feed_data('\027\000') -- ESC: go to Normal mode. + feed_data('\027[27u') -- ESC: go to Normal mode. wait_for_mode('n') -- Dot-repeat/redo. feed_data('.') @@ -677,8 +709,8 @@ describe('TUI', function() item 2997 | item 2998 | item 2999 | - item 3000 en{1:d} | - {5:[No Name] [+] 3000,13 Bot}| + item 3000 en{1:d}d | + {5:[No Name] [+] 5999,13 Bot}| | {3:-- TERMINAL --} | ]]) @@ -765,6 +797,44 @@ describe('TUI', function() ]]) end) + it('paste: streamed paste with isolated "stop paste" code', function() + child_session:request('nvim_exec_lua', [[ + _G.paste_phases = {} + vim.paste = (function(overridden) + return function(lines, phase) + table.insert(_G.paste_phases, phase) + overridden(lines, phase) + end + end)(vim.paste) + ]], {}) + feed_data('i') + feed_data('\027[200~pasted') -- phase 1 + screen:expect([[ + pasted{1: } | + {4:~ }| + {4:~ }| + {4:~ }| + {5:[No Name] [+] }| + {3:-- INSERT --} | + {3:-- TERMINAL --} | + ]]) + feed_data(' from terminal') -- phase 2 + screen:expect([[ + pasted from terminal{1: } | + {4:~ }| + {4:~ }| + {4:~ }| + {5:[No Name] [+] }| + {3:-- INSERT --} | + {3:-- TERMINAL --} | + ]]) + -- Send isolated "stop paste" sequence. + feed_data('\027[201~') -- phase 3 + screen:expect_unchanged() + local _, rv = child_session:request('nvim_exec_lua', [[return _G.paste_phases]], {}) + eq({1, 2, 3}, rv) + end) + it('allows termguicolors to be set at runtime', function() screen:set_option('rgb', true) screen:set_default_attr_ids({ 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..5ec0a8a060 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,131 @@ 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("supports conceal attribute", function() + if pending_c_parser(pending) then return end + insert(hl_text) + + -- conceal can be empty or a single cchar. + exec_lua [=[ + vim.opt.cole = 2 + local parser = vim.treesitter.get_parser(0, "c") + test_hl = vim.treesitter.highlighter.new(parser, {queries = {c = [[ + ("static" @keyword + (set! conceal "R")) + + ((identifier) @Identifier + (set! conceal "") + (eq? @Identifier "lstate")) + ]]}}) + ]=] + + screen:expect{grid=[[ + /// Schedule Lua callback on main loop's event queue | + {4:R} int nlua_schedule(lua_State *const ) | + { | + if (lua_type(, 1) != LUA_TFUNCTION | + || != ) { | + lua_pushliteral(, "vim.schedule: expected function"); | + return lua_error(); | + } | + | + LuaRef cb = nlua_ref(, 1); | + | + multiqueue_put(main_loop.events, nlua_schedule_event, | + 1, (void *)(ptrdiff_t)cb); | + return 0; | + ^} | + {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 ffaa4141c4..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) @@ -227,15 +248,71 @@ void ui_refresh(void) }, res) end) + it('supports getting text of multiline node', function() + if pending_c_parser(pending) then return end + insert(test_text) + local res = exec_lua([[ + local parser = vim.treesitter.get_parser(0, "c") + local tree = parser:parse()[1] + return vim.treesitter.get_node_text(tree:root(), 0) + ]]) + eq(test_text, res) + + local res2 = exec_lua([[ + local parser = vim.treesitter.get_parser(0, "c") + local root = parser:parse()[1]:root() + return vim.treesitter.get_node_text(root:child(0):child(0), 0) + ]]) + eq('void', res2) + end) + + it('support getting text where start of node is past EOF', function() + local text = [[ +def run + a = <<~E +end]] + insert(text) + local result = exec_lua([[ + local fake_node = {} + function fake_node:start() + return 3, 0, 23 + end + function fake_node:end_() + return 3, 0, 23 + end + return vim.treesitter.get_node_text(fake_node, 0) == nil + ]]) + 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;') local res = exec_lua([[ - cquery = vim.treesitter.parse_query("c", '((_) @plus (vim-match? @plus "^\\\\+$"))'.. - '((_) @times (vim-match? @times "^\\\\*$"))'.. - '((_) @paren (vim-match? @paren "^\\\\($"))'.. - '((_) @escape (vim-match? @escape "^\\\\\\\\n$"))'.. - '((_) @string (vim-match? @string "^\\"\\\\\\\\n\\"$"))') + cquery = vim.treesitter.parse_query("c", '([_] @plus (#vim-match? @plus "^\\\\+$"))'.. + '([_] @times (#vim-match? @times "^\\\\*$"))'.. + '([_] @paren (#vim-match? @paren "^\\\\($"))'.. + '([_] @escape (#vim-match? @escape "^\\\\\\\\n$"))'.. + '([_] @string (#vim-match? @string "^\\"\\\\\\\\n\\"$"))') parser = vim.treesitter.get_parser(0, "c") tree = parser:parse()[1] res = {} @@ -321,7 +398,7 @@ void ui_refresh(void) insert('char* astring = "Hello World!";') local res = exec_lua([[ - cquery = vim.treesitter.parse_query("c", '((_) @quote (vim-match? @quote "^\\"$")) ((_) @quote (lua-match? @quote "^\\"$"))') + cquery = vim.treesitter.parse_query("c", '([_] @quote (#vim-match? @quote "^\\"$")) ([_] @quote (#lua-match? @quote "^\\"$"))') parser = vim.treesitter.get_parser(0, "c") tree = parser:parse()[1] res = {} @@ -608,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/cmdline_spec.lua b/test/functional/ui/cmdline_spec.lua index ad23402ff9..3b273fd229 100644 --- a/test/functional/ui/cmdline_spec.lua +++ b/test/functional/ui/cmdline_spec.lua @@ -4,6 +4,7 @@ local clear, feed = helpers.clear, helpers.feed local source = helpers.source local command = helpers.command local assert_alive = helpers.assert_alive +local uname = helpers.uname local function new_screen(opt) local screen = Screen.new(25, 5) @@ -824,7 +825,7 @@ describe('cmdline redraw', function() end) it('with <Cmd>', function() - if 'openbsd' == helpers.uname() then + if string.find(uname(), 'bsd') then pending('FIXME #10804') end command('cmap a <Cmd>call sin(0)<CR>') -- no-op diff --git a/test/functional/ui/cursor_spec.lua b/test/functional/ui/cursor_spec.lua index 9c035c728b..4c51547e2c 100644 --- a/test/functional/ui/cursor_spec.lua +++ b/test/functional/ui/cursor_spec.lua @@ -212,10 +212,10 @@ describe('ui/cursor', function() if m.blinkwait then m.blinkwait = 700 end end if m.hl_id then - m.hl_id = 58 + m.hl_id = 61 m.attr = {background = Screen.colors.DarkGray} end - if m.id_lm then m.id_lm = 59 end + if m.id_lm then m.id_lm = 62 end end -- Assert the new expectation. diff --git a/test/functional/ui/decorations_spec.lua b/test/functional/ui/decorations_spec.lua index dce6384b9b..74eb5d5b8e 100644 --- a/test/functional/ui/decorations_spec.lua +++ b/test/functional/ui/decorations_spec.lua @@ -437,6 +437,7 @@ describe('extmark decorations', function() [22] = {foreground = tonumber('0xb20000'), background = tonumber('0xf13f3f')}; [23] = {foreground = Screen.colors.Magenta1, background = Screen.colors.LightGrey}; [24] = {bold = true}; + [25] = {background = Screen.colors.LightRed}; } ns = meths.create_namespace 'test' @@ -456,6 +457,31 @@ for _,item in ipairs(items) do end end]] + it('empty virtual text at eol should not break colorcolumn #17860', function() + insert(example_text) + feed('gg') + command('set colorcolumn=40') + screen:expect([[ + ^for _,item in ipairs(items) do {25: } | + local text, hl_id_cell, count = unp{25:a}ck(item) | + if hl_id_cell ~= nil then {25: } | + hl_id = hl_id_cell {25: } | + end {25: } | + for _ = 1, (count or 1) do {25: } | + local cell = line[colpos] {25: } | + cell.text = text {25: } | + cell.hl_id = hl_id {25: } | + colpos = colpos+1 {25: } | + end {25: } | + end {25: } | + {1:~ }| + {1:~ }| + | + ]]) + meths.buf_set_extmark(0, ns, 4, 0, { virt_text={{''}}, virt_text_pos='eol'}) + screen:expect_unchanged() + end) + it('can have virtual text of overlay position', function() insert(example_text) feed 'gg' @@ -471,7 +497,9 @@ end]] -- can "float" beyond end of line meths.buf_set_extmark(0, ns, 5, 28, { virt_text={{'loopy', 'ErrorMsg'}}, virt_text_pos='overlay'}) -- bound check: right edge of window - meths.buf_set_extmark(0, ns, 2, 26, { virt_text={{'bork bork bork ' }, {'bork bork bork', 'ErrorMsg'}}, virt_text_pos='overlay'}) + meths.buf_set_extmark(0, ns, 2, 26, { virt_text={{'bork bork bork '}, {'bork bork bork', 'ErrorMsg'}}, virt_text_pos='overlay'}) + -- empty virt_text should not change anything + meths.buf_set_extmark(0, ns, 6, 16, { virt_text={{''}}, virt_text_pos='overlay'}) screen:expect{grid=[[ ^for _,item in ipairs(items) do | @@ -621,6 +649,8 @@ end]] meths.buf_set_extmark(0, ns, 2, 10, { virt_text={{'Much', 'ErrorMsg'}}, virt_text_win_col=31, hl_mode='blend'}) meths.buf_set_extmark(0, ns, 3, 15, { virt_text={{'Error', 'ErrorMsg'}}, virt_text_win_col=31, hl_mode='blend'}) meths.buf_set_extmark(0, ns, 7, 21, { virt_text={{'-', 'NonText'}}, virt_text_win_col=4, hl_mode='blend'}) + -- empty virt_text should not change anything + meths.buf_set_extmark(0, ns, 8, 0, { virt_text={{''}}, virt_text_win_col=14, hl_mode='blend'}) screen:expect{grid=[[ ^for _,item in ipairs(items) do | @@ -1031,6 +1061,69 @@ if (h->n_buckets < new_n_buckets) { // expand | ]]} + screen:try_resize(50, 11) + feed('gg') + screen:expect{grid=[[ + ^if (h->n_buckets < new_n_buckets) { // expand | + khkey_t *new_keys = (khkey_t *)krealloc((void *)| + h->keys, new_n_buckets * sizeof(khkey_t)); | + h->keys = new_keys; | + if (kh_is_map && val_size) { | + char *new_vals = krealloc( h->vals_buf, new_n_| + buckets * val_size); | + h->vals_buf = new_vals; | + } | + } | + | + ]]} + + feed('G<C-E>') + screen:expect{grid=[[ + khkey_t *new_keys = (khkey_t *)krealloc((void *)| + h->keys, new_n_buckets * sizeof(khkey_t)); | + h->keys = new_keys; | + if (kh_is_map && val_size) { | + char *new_vals = krealloc( h->vals_buf, new_n_| + buckets * val_size); | + h->vals_buf = new_vals; | + } | + ^} | + Grugg | + | + ]]} + + feed('gg') + screen:expect{grid=[[ + ^if (h->n_buckets < new_n_buckets) { // expand | + khkey_t *new_keys = (khkey_t *)krealloc((void *)| + h->keys, new_n_buckets * sizeof(khkey_t)); | + h->keys = new_keys; | + if (kh_is_map && val_size) { | + char *new_vals = krealloc( h->vals_buf, new_n_| + buckets * val_size); | + h->vals_buf = new_vals; | + } | + } | + | + ]]} + + screen:try_resize(50, 12) + feed('G') + screen:expect{grid=[[ + if (h->n_buckets < new_n_buckets) { // expand | + khkey_t *new_keys = (khkey_t *)krealloc((void *)| + h->keys, new_n_buckets * sizeof(khkey_t)); | + h->keys = new_keys; | + if (kh_is_map && val_size) { | + char *new_vals = krealloc( h->vals_buf, new_n_| + buckets * val_size); | + h->vals_buf = new_vals; | + } | + ^} | + Grugg | + | + ]]} + meths.buf_del_extmark(0, ns, id) screen:expect{grid=[[ if (h->n_buckets < new_n_buckets) { // expand | @@ -1048,6 +1141,27 @@ if (h->n_buckets < new_n_buckets) { // expand ]]} end) + it('does not cause syntax ml_get error at the end of a buffer #17816', function() + command([[syntax region foo keepend start='^foo' end='^$']]) + command('syntax sync minlines=100') + insert('foo') + meths.buf_set_extmark(0, ns, 0, 0, {virt_lines = {{{'bar', 'Comment'}}}}) + screen:expect([[ + fo^o | + {6:bar} | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]) + end) + it('works with a block scrolling up', function() screen:try_resize(30, 7) insert("aa\nbb\ncc\ndd\nee\nff\ngg\nhh") @@ -1299,3 +1413,372 @@ if (h->n_buckets < new_n_buckets) { // expand end) end) + +describe('decorations: signs', function() + local screen, ns + before_each(function() + clear() + screen = Screen.new(50, 10) + screen:attach() + screen:set_default_attr_ids { + [1] = {foreground = Screen.colors.Blue4, background = Screen.colors.Grey}; + [2] = {foreground = Screen.colors.Blue1, bold = true}; + [3] = {background = Screen.colors.Yellow1, foreground = Screen.colors.Blue1}; + } + + ns = meths.create_namespace 'test' + meths.win_set_option(0, 'signcolumn', 'auto:9') + end) + + local example_text = [[ +l1 +l2 +l3 +l4 +l5 +]] + + it('can add a single sign (no end row)', function() + insert(example_text) + feed 'gg' + + meths.buf_set_extmark(0, ns, 1, -1, {sign_text='S'}) + + screen:expect{grid=[[ + {1: }^l1 | + S l2 | + {1: }l3 | + {1: }l4 | + {1: }l5 | + {1: } | + {2:~ }| + {2:~ }| + {2:~ }| + | + ]]} + + end) + + it('can add a single sign (with end row)', function() + insert(example_text) + feed 'gg' + + meths.buf_set_extmark(0, ns, 1, -1, {sign_text='S', end_row=1}) + + screen:expect{grid=[[ + {1: }^l1 | + S l2 | + {1: }l3 | + {1: }l4 | + {1: }l5 | + {1: } | + {2:~ }| + {2:~ }| + {2:~ }| + | + ]]} + + end) + + it('can add multiple signs (single extmark)', function() + pending('TODO(lewis6991): Support ranged signs') + insert(example_text) + feed 'gg' + + meths.buf_set_extmark(0, ns, 1, -1, {sign_text='S', end_row = 2}) + + screen:expect{grid=[[ + {1: }^l1 | + S l2 | + S l3 | + {1: }l4 | + {1: }l5 | + {1: } | + {2:~ }| + {2:~ }| + {2:~ }| + | + ]]} + + end) + + it('can add multiple signs (multiple extmarks)', function() + pending('TODO(lewis6991): Support ranged signs') + insert(example_text) + feed'gg' + + meths.buf_set_extmark(0, ns, 1, -1, {sign_text='S1'}) + meths.buf_set_extmark(0, ns, 3, -1, {sign_text='S2', end_row = 4}) + + screen:expect{grid=[[ + {1: }^l1 | + S1l2 | + {1: }l3 | + S2l4 | + S2l5 | + {1: } | + {2:~ }| + {2:~ }| + {2:~ }| + | + ]]} + + end) + + it('can add multiple signs (multiple extmarks) 2', function() + insert(example_text) + feed 'gg' + + meths.buf_set_extmark(0, ns, 1, -1, {sign_text='S1'}) + meths.buf_set_extmark(0, ns, 1, -1, {sign_text='S2'}) + + screen:expect{grid=[[ + {1: }^l1 | + S2S1l2 | + {1: }l3 | + {1: }l4 | + {1: }l5 | + {1: } | + {2:~ }| + {2:~ }| + {2:~ }| + | + ]]} + + -- TODO(lewis6991): Support ranged signs + -- meths.buf_set_extmark(1, ns, 1, -1, {sign_text='S3', end_row = 2}) + + -- screen:expect{grid=[[ + -- {1: }^l1 | + -- S3S2S1l2 | + -- S3{1: }l3 | + -- {1: }l4 | + -- {1: }l5 | + -- {1: } | + -- {2:~ }| + -- {2:~ }| + -- {2:~ }| + -- | + -- ]]} + + end) + + it('can add multiple signs (multiple extmarks) 3', function() + pending('TODO(lewis6991): Support ranged signs') + + insert(example_text) + feed 'gg' + + meths.buf_set_extmark(0, ns, 1, -1, {sign_text='S1', end_row=2}) + meths.buf_set_extmark(0, ns, 2, -1, {sign_text='S2', end_row=3}) + + screen:expect{grid=[[ + {1: }^l1 | + S1{1: }l2 | + S2S1l3 | + S2{1: }l4 | + {1: }l5 | + {1: } | + {2:~ }| + {2:~ }| + {2:~ }| + | + ]]} + end) + + it('can add multiple signs (multiple extmarks) 4', function() + insert(example_text) + feed 'gg' + + meths.buf_set_extmark(0, ns, 0, -1, {sign_text='S1', end_row=0}) + meths.buf_set_extmark(0, ns, 1, -1, {sign_text='S2', end_row=1}) + + screen:expect{grid=[[ + S1^l1 | + S2l2 | + {1: }l3 | + {1: }l4 | + {1: }l5 | + {1: } | + {2:~ }| + {2:~ }| + {2:~ }| + | + ]]} + end) + + it('works with old signs', function() + insert(example_text) + feed 'gg' + + helpers.command('sign define Oldsign text=x') + helpers.command([[exe 'sign place 42 line=2 name=Oldsign buffer=' . bufnr('')]]) + + meths.buf_set_extmark(0, ns, 0, -1, {sign_text='S1'}) + meths.buf_set_extmark(0, ns, 1, -1, {sign_text='S2'}) + meths.buf_set_extmark(0, ns, 0, -1, {sign_text='S4'}) + meths.buf_set_extmark(0, ns, 2, -1, {sign_text='S5'}) + + screen:expect{grid=[[ + S4S1^l1 | + S2x l2 | + S5{1: }l3 | + {1: }l4 | + {1: }l5 | + {1: } | + {2:~ }| + {2:~ }| + {2:~ }| + | + ]]} + end) + + it('works with old signs (with range)', function() + pending('TODO(lewis6991): Support ranged signs') + insert(example_text) + feed 'gg' + + helpers.command('sign define Oldsign text=x') + helpers.command([[exe 'sign place 42 line=2 name=Oldsign buffer=' . bufnr('')]]) + + meths.buf_set_extmark(0, ns, 0, -1, {sign_text='S1'}) + meths.buf_set_extmark(0, ns, 1, -1, {sign_text='S2'}) + meths.buf_set_extmark(0, ns, 0, -1, {sign_text='S3', end_row = 4}) + meths.buf_set_extmark(0, ns, 0, -1, {sign_text='S4'}) + meths.buf_set_extmark(0, ns, 2, -1, {sign_text='S5'}) + + screen:expect{grid=[[ + S3S4S1^l1 | + S2S3x l2 | + S5S3{1: }l3 | + S3{1: }l4 | + S3{1: }l5 | + {1: } | + {2:~ }| + {2:~ }| + {2:~ }| + | + ]]} + end) + + it('can add a ranged sign (with start out of view)', function() + pending('TODO(lewis6991): Support ranged signs') + + insert(example_text) + command 'set signcolumn=yes:2' + feed 'gg' + feed '2<C-e>' + + meths.buf_set_extmark(0, ns, 1, -1, {sign_text='X', end_row=3}) + + screen:expect{grid=[[ + X {1: }^l3 | + X {1: }l4 | + {1: }l5 | + {1: } | + {2:~ }| + {2:~ }| + {2:~ }| + {2:~ }| + {2:~ }| + | + ]]} + + end) + + it('can add lots of signs', function() + screen:try_resize(40, 10) + command 'normal 10oa b c d e f g h' + + for i = 1, 10 do + meths.buf_set_extmark(0, ns, i, 0, { end_col = 1, hl_group='Todo' }) + meths.buf_set_extmark(0, ns, i, 2, { end_col = 3, hl_group='Todo' }) + meths.buf_set_extmark(0, ns, i, 4, { end_col = 5, hl_group='Todo' }) + meths.buf_set_extmark(0, ns, i, 6, { end_col = 7, hl_group='Todo' }) + meths.buf_set_extmark(0, ns, i, 8, { end_col = 9, hl_group='Todo' }) + meths.buf_set_extmark(0, ns, i, 10, { end_col = 11, hl_group='Todo' }) + meths.buf_set_extmark(0, ns, i, 12, { end_col = 13, hl_group='Todo' }) + meths.buf_set_extmark(0, ns, i, 14, { end_col = 15, hl_group='Todo' }) + meths.buf_set_extmark(0, ns, i, -1, { sign_text='W' }) + meths.buf_set_extmark(0, ns, i, -1, { sign_text='X' }) + meths.buf_set_extmark(0, ns, i, -1, { sign_text='Y' }) + meths.buf_set_extmark(0, ns, i, -1, { sign_text='Z' }) + end + + screen:expect{grid=[[ + X Y Z W {3:a} {3:b} {3:c} {3:d} {3:e} {3:f} {3:g} {3:h} | + X Y Z W {3:a} {3:b} {3:c} {3:d} {3:e} {3:f} {3:g} {3:h} | + X Y Z W {3:a} {3:b} {3:c} {3:d} {3:e} {3:f} {3:g} {3:h} | + X Y Z W {3:a} {3:b} {3:c} {3:d} {3:e} {3:f} {3:g} {3:h} | + X Y Z W {3:a} {3:b} {3:c} {3:d} {3:e} {3:f} {3:g} {3:h} | + X Y Z W {3:a} {3:b} {3:c} {3:d} {3:e} {3:f} {3:g} {3:h} | + X Y Z W {3:a} {3:b} {3:c} {3:d} {3:e} {3:f} {3:g} {3:h} | + X Y Z W {3:a} {3:b} {3:c} {3:d} {3:e} {3:f} {3:g} {3:h} | + X Y Z W {3:a} {3:b} {3:c} {3:d} {3:e} {3:f} {3:g} {3:^h} | + | + ]]} + end) + +end) + +describe('decorations: virt_text', function() + local screen + + before_each(function() + clear() + screen = Screen.new(50, 10) + screen:attach() + screen:set_default_attr_ids { + [1] = {foreground = Screen.colors.Brown}; + [2] = {foreground = Screen.colors.Fuchsia}; + [3] = {bold = true, foreground = Screen.colors.Blue1}; + } + end) + + it('avoids regression in #17638', function() + exec_lua[[ + vim.wo.number = true + vim.wo.relativenumber = true + ]] + + command 'normal 4ohello' + command 'normal aVIRTUAL' + + local ns = meths.create_namespace('test') + + meths.buf_set_extmark(0, ns, 2, 0, { + virt_text = {{"hello", "String"}}, + virt_text_win_col = 20, + }) + + screen:expect{grid=[[ + {1: 4 } | + {1: 3 }hello | + {1: 2 }hello {2:hello} | + {1: 1 }hello | + {1:5 }helloVIRTUA^L | + {3:~ }| + {3:~ }| + {3:~ }| + {3:~ }| + | + ]]} + + -- Trigger a screen update + feed('k') + + screen:expect{grid=[[ + {1: 3 } | + {1: 2 }hello | + {1: 1 }hello {2:hello} | + {1:4 }hell^o | + {1: 1 }helloVIRTUAL | + {3:~ }| + {3:~ }| + {3:~ }| + {3:~ }| + | + ]]} + end) + +end) diff --git a/test/functional/ui/diff_spec.lua b/test/functional/ui/diff_spec.lua index 13949b0756..6f67dea2be 100644 --- a/test/functional/ui/diff_spec.lua +++ b/test/functional/ui/diff_spec.lua @@ -6,7 +6,7 @@ local clear = helpers.clear local command = helpers.command local insert = helpers.insert local write_file = helpers.write_file -local source = helpers.source +local exec = helpers.exec describe('Diff mode screen', function() local fname = 'Xtest-functional-diff-screen-1' @@ -186,6 +186,19 @@ describe('Diff mode screen', function() {7:<onal-diff-screen-1 }{3:<l-diff-screen-1.2 }| :set diffopt+=internal | ]]) + + screen:try_resize(40, 9) + screen:expect([[ + {1:+ }{5:^+-- 4 lines: 1···}{3:│}{1:+ }{5:+-- 4 lines: 1··}| + {1: }5 {3:│}{1: }5 | + {1: }6 {3:│}{1: }6 | + {1: }7 {3:│}{1: }7 | + {1: }8 {3:│}{1: }8 | + {1: }9 {3:│}{1: }9 | + {1: }10 {3:│}{1: }10 | + {7:<onal-diff-screen-1 }{3:<l-diff-screen-1.2 }| + | + ]]) end) it('Add a line at the end of file 1', function() @@ -232,6 +245,19 @@ describe('Diff mode screen', function() {7:<onal-diff-screen-1 }{3:<l-diff-screen-1.2 }| :set diffopt+=internal | ]]) + + screen:try_resize(40, 9) + screen:expect([[ + {1:+ }{5:^+-- 4 lines: 1···}{3:│}{1:+ }{5:+-- 4 lines: 1··}| + {1: }5 {3:│}{1: }5 | + {1: }6 {3:│}{1: }6 | + {1: }7 {3:│}{1: }7 | + {1: }8 {3:│}{1: }8 | + {1: }9 {3:│}{1: }9 | + {1: }10 {3:│}{1: }10 | + {7:<onal-diff-screen-1 }{3:<l-diff-screen-1.2 }| + | + ]]) end) it('Add a line in the middle of file 2, remove on at the end of file 1', function() @@ -1049,10 +1075,8 @@ it('diff updates line numbers below filler lines', function() [9] = {background = Screen.colors.LightMagenta}, [10] = {bold = true, foreground = Screen.colors.Brown}, [11] = {foreground = Screen.colors.Brown}, - [12] = {foreground = Screen.colors.Brown, bold = true, background = Screen.colors.Red}; - [13] = {background = Screen.colors.Gray90}; }) - source([[ + exec([[ call setline(1, ['a', 'a', 'a', 'y', 'b', 'b', 'b', 'b', 'b']) vnew call setline(1, ['a', 'a', 'a', 'x', 'x', 'x', 'b', 'b', 'b', 'b', 'b']) @@ -1109,24 +1133,6 @@ it('diff updates line numbers below filler lines', function() {3:[No Name] [+] }{7:[No Name] [+] }| | ]]) - command("set signcolumn number tgc cursorline cursorlineopt=number,line") - command("hi CursorLineNr guibg=red") - screen:expect{grid=[[ - {1: }a {3:│}{11: 2 }a | - {1: }a {3:│}{11: 1 }a | - {1: }a {3:│}{12:3 }{13:^a }| - {1: }{8:x}{9: }{3:│}{11: 1 }{8:y}{9: }| - {1: }{4:x }{3:│}{11: }{2:----------------}| - {1: }{4:x }{3:│}{11: }{2:----------------}| - {1: }b {3:│}{11: 2 }b | - {1: }b {3:│}{11: 3 }b | - {1: }b {3:│}{11: 4 }b | - {1: }b {3:│}{11: 5 }b | - {1: }b {3:│}{11: 6 }b | - {6:~ }{3:│}{6:~ }| - {3:[No Name] [+] }{7:[No Name] [+] }| - signcolumn=auto | - ]]} end) it('Align the filler lines when changing text in diff mode', function() @@ -1143,7 +1149,7 @@ it('Align the filler lines when changing text in diff mode', function() [7] = {foreground = Screen.colors.Blue1, bold = true}; [8] = {reverse = true, bold = true}; }) - source([[ + exec([[ call setline(1, range(1, 15)) vnew call setline(1, range(9, 15)) @@ -1220,3 +1226,130 @@ it('Align the filler lines when changing text in diff mode', function() | ]]} end) + +it('diff mode works properly if file contains NUL bytes vim-patch:8.2.3925', function() + clear() + local screen = Screen.new(40, 20) + screen:set_default_attr_ids({ + [1] = {foreground = Screen.colors.DarkBlue, background = Screen.colors.Gray}; + [2] = {reverse = true}; + [3] = {background = Screen.colors.LightBlue}; + [4] = {background = Screen.colors.LightMagenta}; + [5] = {background = Screen.colors.Red, bold = true}; + [6] = {foreground = Screen.colors.Blue, bold = true}; + [7] = {background = Screen.colors.Red, foreground = Screen.colors.Blue, bold = true}; + [8] = {reverse = true, bold = true}; + }) + screen:attach() + exec([[ + call setline(1, ['a', 'b', "c\n", 'd', 'e', 'f', 'g']) + vnew + call setline(1, ['A', 'b', 'c', 'd', 'E', 'f', 'g']) + windo diffthis + wincmd p + norm! gg0 + redraw! + ]]) + + -- Test using internal diff + screen:expect([[ + {1: }{5:^A}{4: }{2:│}{1: }{5:a}{4: }| + {1: }b {2:│}{1: }b | + {1: }{4:c }{2:│}{1: }{4:c}{7:^@}{4: }| + {1: }d {2:│}{1: }d | + {1: }{5:E}{4: }{2:│}{1: }{5:e}{4: }| + {1: }f {2:│}{1: }f | + {1: }g {2:│}{1: }g | + {6:~ }{2:│}{6:~ }| + {6:~ }{2:│}{6:~ }| + {6:~ }{2:│}{6:~ }| + {6:~ }{2:│}{6:~ }| + {6:~ }{2:│}{6:~ }| + {6:~ }{2:│}{6:~ }| + {6:~ }{2:│}{6:~ }| + {6:~ }{2:│}{6:~ }| + {6:~ }{2:│}{6:~ }| + {6:~ }{2:│}{6:~ }| + {6:~ }{2:│}{6:~ }| + {8:[No Name] [+] }{2:[No Name] [+] }| + | + ]]) + + -- Test using internal diff and case folding + command('set diffopt+=icase') + feed('<C-L>') + screen:expect([[ + {1: }^A {2:│}{1: }a | + {1: }b {2:│}{1: }b | + {1: }{4:c }{2:│}{1: }{4:c}{7:^@}{4: }| + {1: }d {2:│}{1: }d | + {1: }E {2:│}{1: }e | + {1: }f {2:│}{1: }f | + {1: }g {2:│}{1: }g | + {6:~ }{2:│}{6:~ }| + {6:~ }{2:│}{6:~ }| + {6:~ }{2:│}{6:~ }| + {6:~ }{2:│}{6:~ }| + {6:~ }{2:│}{6:~ }| + {6:~ }{2:│}{6:~ }| + {6:~ }{2:│}{6:~ }| + {6:~ }{2:│}{6:~ }| + {6:~ }{2:│}{6:~ }| + {6:~ }{2:│}{6:~ }| + {6:~ }{2:│}{6:~ }| + {8:[No Name] [+] }{2:[No Name] [+] }| + | + ]]) + + -- Test using external diff + command('set diffopt=filler') + feed('<C-L>') + screen:expect([[ + {1: }{5:^A}{4: }{2:│}{1: }{5:a}{4: }| + {1: }b {2:│}{1: }b | + {1: }{4:c }{2:│}{1: }{4:c}{7:^@}{4: }| + {1: }d {2:│}{1: }d | + {1: }{5:E}{4: }{2:│}{1: }{5:e}{4: }| + {1: }f {2:│}{1: }f | + {1: }g {2:│}{1: }g | + {6:~ }{2:│}{6:~ }| + {6:~ }{2:│}{6:~ }| + {6:~ }{2:│}{6:~ }| + {6:~ }{2:│}{6:~ }| + {6:~ }{2:│}{6:~ }| + {6:~ }{2:│}{6:~ }| + {6:~ }{2:│}{6:~ }| + {6:~ }{2:│}{6:~ }| + {6:~ }{2:│}{6:~ }| + {6:~ }{2:│}{6:~ }| + {6:~ }{2:│}{6:~ }| + {8:[No Name] [+] }{2:[No Name] [+] }| + | + ]]) + + -- Test using external diff and case folding + command('set diffopt+=filler,icase') + feed('<C-L>') + screen:expect([[ + {1: }^A {2:│}{1: }a | + {1: }b {2:│}{1: }b | + {1: }{4:c }{2:│}{1: }{4:c}{7:^@}{4: }| + {1: }d {2:│}{1: }d | + {1: }E {2:│}{1: }e | + {1: }f {2:│}{1: }f | + {1: }g {2:│}{1: }g | + {6:~ }{2:│}{6:~ }| + {6:~ }{2:│}{6:~ }| + {6:~ }{2:│}{6:~ }| + {6:~ }{2:│}{6:~ }| + {6:~ }{2:│}{6:~ }| + {6:~ }{2:│}{6:~ }| + {6:~ }{2:│}{6:~ }| + {6:~ }{2:│}{6:~ }| + {6:~ }{2:│}{6:~ }| + {6:~ }{2:│}{6:~ }| + {6:~ }{2:│}{6:~ }| + {8:[No Name] [+] }{2:[No Name] [+] }| + | + ]]) +end) diff --git a/test/functional/ui/float_spec.lua b/test/functional/ui/float_spec.lua index 5f29261b17..359d1f206a 100644 --- a/test/functional/ui/float_spec.lua +++ b/test/functional/ui/float_spec.lua @@ -8,6 +8,7 @@ local command, feed_command = helpers.command, helpers.feed_command local eval = helpers.eval local eq = helpers.eq local neq = helpers.neq +local expect = helpers.expect local exec_lua = helpers.exec_lua local insert = helpers.insert local meths = helpers.meths @@ -16,6 +17,7 @@ local funcs = helpers.funcs local run = helpers.run local pcall_err = helpers.pcall_err local tbl_contains = global_helpers.tbl_contains +local curbuf, curwin, curtab = helpers.curbuf, helpers.curwin, helpers.curtab describe('float window', function() before_each(function() @@ -417,6 +419,287 @@ describe('float window', function() eq(winids, eval('winids')) end) + describe('with only one tabpage,', function() + local float_opts = {relative = 'editor', row = 1, col = 1, width = 1, height = 1} + local old_buf, old_win + before_each(function() + insert('foo') + old_buf = curbuf().id + old_win = curwin().id + end) + describe('closing the last non-floating window gives E444', function() + before_each(function() + meths.open_win(old_buf, true, float_opts) + end) + it('if called from non-floating window', function() + meths.set_current_win(old_win) + eq('Vim:E444: Cannot close last window', + pcall_err(meths.win_close, old_win, false)) + end) + it('if called from floating window', function() + eq('Vim:E444: Cannot close last window', + pcall_err(meths.win_close, old_win, false)) + end) + end) + describe("deleting the last non-floating window's buffer", function() + describe('leaves one window with an empty buffer when there is only one buffer', function() + local same_buf_float + before_each(function() + same_buf_float = meths.open_win(old_buf, false, float_opts).id + end) + after_each(function() + eq(old_win, curwin().id) + expect('') + eq(1, #meths.list_wins()) + end) + it('if called from non-floating window', function() + meths.buf_delete(old_buf, {force = true}) + end) + it('if called from floating window', function() + meths.set_current_win(same_buf_float) + command('autocmd WinLeave * let g:win_leave = nvim_get_current_win()') + command('autocmd WinEnter * let g:win_enter = nvim_get_current_win()') + meths.buf_delete(old_buf, {force = true}) + eq(same_buf_float, eval('g:win_leave')) + eq(old_win, eval('g:win_enter')) + end) + end) + describe('closes other windows with that buffer when there are other buffers', function() + local same_buf_float, other_buf, other_buf_float + before_each(function() + same_buf_float = meths.open_win(old_buf, false, float_opts).id + other_buf = meths.create_buf(true, false).id + other_buf_float = meths.open_win(other_buf, true, float_opts).id + insert('bar') + meths.set_current_win(old_win) + end) + after_each(function() + eq(other_buf, curbuf().id) + expect('bar') + eq(2, #meths.list_wins()) + end) + it('if called from non-floating window', function() + meths.buf_delete(old_buf, {force = true}) + eq(old_win, curwin().id) + end) + it('if called from floating window with the same buffer', function() + meths.set_current_win(same_buf_float) + command('autocmd WinLeave * let g:win_leave = nvim_get_current_win()') + command('autocmd WinEnter * let g:win_enter = nvim_get_current_win()') + meths.buf_delete(old_buf, {force = true}) + eq(same_buf_float, eval('g:win_leave')) + eq(old_win, eval('g:win_enter')) + eq(old_win, curwin().id) + end) + -- TODO: this case is too hard to deal with + pending('if called from floating window with another buffer', function() + meths.set_current_win(other_buf_float) + meths.buf_delete(old_buf, {force = true}) + end) + end) + describe('creates an empty buffer when there is only one listed buffer', function() + local same_buf_float, unlisted_buf_float + before_each(function() + same_buf_float = meths.open_win(old_buf, false, float_opts).id + local unlisted_buf = meths.create_buf(true, false).id + unlisted_buf_float = meths.open_win(unlisted_buf, true, float_opts).id + insert('unlisted') + command('set nobuflisted') + meths.set_current_win(old_win) + end) + after_each(function() + expect('') + eq(2, #meths.list_wins()) + end) + it('if called from non-floating window', function() + meths.buf_delete(old_buf, {force = true}) + eq(old_win, curwin().id) + end) + it('if called from floating window with the same buffer', function() + meths.set_current_win(same_buf_float) + command('autocmd WinLeave * let g:win_leave = nvim_get_current_win()') + command('autocmd WinEnter * let g:win_enter = nvim_get_current_win()') + meths.buf_delete(old_buf, {force = true}) + eq(same_buf_float, eval('g:win_leave')) + eq(old_win, eval('g:win_enter')) + eq(old_win, curwin().id) + end) + -- TODO: this case is too hard to deal with + pending('if called from floating window with an unlisted buffer', function() + meths.set_current_win(unlisted_buf_float) + meths.buf_delete(old_buf, {force = true}) + end) + end) + end) + describe('with splits, deleting the last listed buffer creates an empty buffer', function() + describe('when a non-floating window has an unlisted buffer', function() + local same_buf_float + before_each(function() + command('botright vnew') + insert('unlisted') + command('set nobuflisted') + meths.set_current_win(old_win) + same_buf_float = meths.open_win(old_buf, false, float_opts).id + end) + after_each(function() + expect('') + eq(2, #meths.list_wins()) + end) + it('if called from non-floating window with the deleted buffer', function() + meths.buf_delete(old_buf, {force = true}) + eq(old_win, curwin().id) + end) + it('if called from floating window with the deleted buffer', function() + meths.set_current_win(same_buf_float) + meths.buf_delete(old_buf, {force = true}) + eq(same_buf_float, curwin().id) + end) + end) + end) + end) + + describe('with mulitple tabpages but only one listed buffer,', function() + local float_opts = {relative = 'editor', row = 1, col = 1, width = 1, height = 1} + local unlisted_buf, old_buf, old_win + before_each(function() + insert('unlisted') + command('set nobuflisted') + unlisted_buf = curbuf().id + command('tabnew') + insert('foo') + old_buf = curbuf().id + old_win = curwin().id + end) + describe('without splits, deleting the last listed buffer creates an empty buffer', function() + local same_buf_float + before_each(function() + meths.set_current_win(old_win) + same_buf_float = meths.open_win(old_buf, false, float_opts).id + end) + after_each(function() + expect('') + eq(2, #meths.list_wins()) + eq(2, #meths.list_tabpages()) + end) + it('if called from non-floating window', function() + meths.buf_delete(old_buf, {force = true}) + eq(old_win, curwin().id) + end) + it('if called from floating window with the same buffer', function() + meths.set_current_win(same_buf_float) + command('autocmd WinLeave * let g:win_leave = nvim_get_current_win()') + command('autocmd WinEnter * let g:win_enter = nvim_get_current_win()') + meths.buf_delete(old_buf, {force = true}) + eq(same_buf_float, eval('g:win_leave')) + eq(old_win, eval('g:win_enter')) + eq(old_win, curwin().id) + end) + end) + describe('with splits, deleting the last listed buffer creates an empty buffer', function() + local same_buf_float + before_each(function() + command('botright vsplit') + meths.set_current_buf(unlisted_buf) + meths.set_current_win(old_win) + same_buf_float = meths.open_win(old_buf, false, float_opts).id + end) + after_each(function() + expect('') + eq(3, #meths.list_wins()) + eq(2, #meths.list_tabpages()) + end) + it('if called from non-floating window with the deleted buffer', function() + meths.buf_delete(old_buf, {force = true}) + eq(old_win, curwin().id) + end) + it('if called from floating window with the deleted buffer', function() + meths.set_current_win(same_buf_float) + meths.buf_delete(old_buf, {force = true}) + eq(same_buf_float, curwin().id) + end) + end) + end) + + describe('with multiple tabpages and multiple listed buffers,', function() + local float_opts = {relative = 'editor', row = 1, col = 1, width = 1, height = 1} + local old_tabpage, old_buf, old_win + before_each(function() + old_tabpage = curtab().id + insert('oldtab') + command('tabnew') + old_buf = curbuf().id + old_win = curwin().id + end) + describe('closing the last non-floating window', function() + describe('closes the tabpage when all floating windows are closeable', function() + local same_buf_float + before_each(function() + same_buf_float = meths.open_win(old_buf, false, float_opts).id + end) + after_each(function() + eq(old_tabpage, curtab().id) + expect('oldtab') + eq(1, #meths.list_tabpages()) + end) + it('if called from non-floating window', function() + meths.win_close(old_win, false) + end) + it('if called from floating window', function() + meths.set_current_win(same_buf_float) + meths.win_close(old_win, false) + end) + end) + describe('gives E5601 when there are non-closeable floating windows', function() + local other_buf_float + before_each(function() + command('set nohidden') + local other_buf = meths.create_buf(true, false).id + other_buf_float = meths.open_win(other_buf, true, float_opts).id + insert('foo') + meths.set_current_win(old_win) + end) + it('if called from non-floating window', function() + eq('Vim:E5601: Cannot close window, only floating window would remain', + pcall_err(meths.win_close, old_win, false)) + end) + it('if called from floating window', function() + meths.set_current_win(other_buf_float) + eq('Vim:E5601: Cannot close window, only floating window would remain', + pcall_err(meths.win_close, old_win, false)) + end) + end) + end) + describe("deleting the last non-floating window's buffer", function() + describe('closes the tabpage when all floating windows are closeable', function() + local same_buf_float, other_buf, other_buf_float + before_each(function() + same_buf_float = meths.open_win(old_buf, false, float_opts).id + other_buf = meths.create_buf(true, false).id + other_buf_float = meths.open_win(other_buf, true, float_opts).id + meths.set_current_win(old_win) + end) + after_each(function() + eq(old_tabpage, curtab().id) + expect('oldtab') + eq(1, #meths.list_tabpages()) + end) + it('if called from non-floating window', function() + meths.buf_delete(old_buf, {force = false}) + end) + it('if called from floating window with the same buffer', function() + meths.set_current_win(same_buf_float) + meths.buf_delete(old_buf, {force = false}) + end) + -- TODO: this case is too hard to deal with + pending('if called from floating window with another buffer', function() + meths.set_current_win(other_buf_float) + meths.buf_delete(old_buf, {force = false}) + end) + end) + -- TODO: what to do when there are non-closeable floating windows? + end) + end) + local function with_ext_multigrid(multigrid) local screen before_each(function() @@ -5574,7 +5857,7 @@ describe('float window', function() [2:----------------------------------------]| [2:----------------------------------------]| [2:----------------------------------------]| - {5:[No Name] [+] }| + [2:----------------------------------------]| [3:----------------------------------------]| ## grid 2 x | @@ -5582,6 +5865,7 @@ describe('float window', function() {0:~ }| {0:~ }| {0:~ }| + {0:~ }| ## grid 3 :quit | ## grid 4 @@ -5597,7 +5881,7 @@ describe('float window', function() {0:~ }{1:^y }{0: }| {0:~ }{2:~ }{0: }| {0:~ }| - {5:[No Name] [+] }| + {0:~ }| :quit | ]]) end diff --git a/test/functional/ui/fold_spec.lua b/test/functional/ui/fold_spec.lua index 249686234c..bc52696418 100644 --- a/test/functional/ui/fold_spec.lua +++ b/test/functional/ui/fold_spec.lua @@ -41,6 +41,7 @@ describe("folded lines", function() [9] = {bold = true, foreground = Screen.colors.Brown}, [10] = {background = Screen.colors.LightGrey, underline = true}, [11] = {bold=true}, + [12] = {background = Screen.colors.Grey90}, }) end) @@ -84,6 +85,117 @@ describe("folded lines", function() end end) + it("highlights with CursorLineFold when 'cursorline' is set", function() + command("set cursorline foldcolumn=2 foldmethod=marker") + command("hi link CursorLineFold Search") + insert(content1) + feed("zf3j") + if multigrid then + screen:expect([[ + ## grid 1 + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [3:---------------------------------------------]| + ## grid 2 + {7: }This is a | + {7: }valid English | + {7: }sentence composed by | + {7: }an exhausted developer | + {7: }in his cave. | + {6: }{12:^ }| + {1:~ }| + ## grid 3 + | + ]]) + else + screen:expect([[ + {7: }This is a | + {7: }valid English | + {7: }sentence composed by | + {7: }an exhausted developer | + {7: }in his cave. | + {6: }{12:^ }| + {1:~ }| + | + ]]) + end + feed("k") + if multigrid then + screen:expect([[ + ## grid 1 + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [3:---------------------------------------------]| + ## grid 2 + {7: }This is a | + {7: }valid English | + {7: }sentence composed by | + {7: }an exhausted developer | + {6: }{12:^in his cave. }| + {7: } | + {1:~ }| + ## grid 3 + | + ]]) + else + screen:expect([[ + {7: }This is a | + {7: }valid English | + {7: }sentence composed by | + {7: }an exhausted developer | + {6: }{12:^in his cave. }| + {7: } | + {1:~ }| + | + ]]) + end + command("set cursorlineopt=line") + if multigrid then + screen:expect([[ + ## grid 1 + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [3:---------------------------------------------]| + ## grid 2 + {7: }This is a | + {7: }valid English | + {7: }sentence composed by | + {7: }an exhausted developer | + {7: }{12:^in his cave. }| + {7: } | + {1:~ }| + ## grid 3 + | + ]]) + else + screen:expect([[ + {7: }This is a | + {7: }valid English | + {7: }sentence composed by | + {7: }an exhausted developer | + {7: }{12:^in his cave. }| + {7: } | + {1:~ }| + | + ]]) + end + end) + it("highlighting with relative line numbers", function() command("set relativenumber cursorline cursorlineopt=number foldmethod=marker") feed_command("set foldcolumn=2") @@ -1560,7 +1672,7 @@ describe("folded lines", function() end -- relax the maximum fdc thus fdc should expand to - -- accomodate the current number of folds + -- accommodate the current number of folds command("set foldcolumn=auto:4") if multigrid then screen:expect([[ diff --git a/test/functional/ui/global_statusline_spec.lua b/test/functional/ui/global_statusline_spec.lua new file mode 100644 index 0000000000..f6821ec589 --- /dev/null +++ b/test/functional/ui/global_statusline_spec.lua @@ -0,0 +1,260 @@ +local helpers = require('test.functional.helpers')(after_each) +local Screen = require('test.functional.ui.screen') +local clear, command, feed = helpers.clear, helpers.command, helpers.feed +local eq, funcs, meths = helpers.eq, helpers.funcs, helpers.meths + +describe('global statusline', function() + local screen + + before_each(function() + clear() + screen = Screen.new(60, 16) + screen:attach() + command('set laststatus=3') + command('set ruler') + end) + + it('works', function() + screen:expect{grid=[[ + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {2:[No Name] 0,0-1 All}| + | + ]], attr_ids={ + [1] = {bold = true, foreground = Screen.colors.Blue1}; + [2] = {bold = true, reverse = true}; + }} + + feed('i<CR><CR>') + screen:expect{grid=[[ + | + | + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {2:[No Name] [+] 3,1 All}| + {3:-- INSERT --} | + ]], attr_ids={ + [1] = {bold = true, foreground = Screen.colors.Blue}; + [2] = {bold = true, reverse = true}; + [3] = {bold = true}; + }} + end) + + it('works with splits', function() + command('vsplit | split | vsplit | vsplit | wincmd l | split | 2wincmd l | split') + screen:expect{grid=[[ + {1:│} {1:│} {1:│}^ | + {2:~ }{1:│}{2:~ }{1:│}{2:~}{1:│}{2:~ }| + {2:~ }{1:│}{2:~ }{1:│}{2:~}{1:│}{2:~ }| + {2:~ }{1:│}{2:~ }{1:│}{2:~}{1:│}{2:~ }| + {2:~ }{1:├────────────────┤}{2:~}{1:│}{2:~ }| + {2:~ }{1:│} {1:│}{2:~}{1:│}{2:~ }| + {2:~ }{1:│}{2:~ }{1:│}{2:~}{1:│}{2:~ }| + {2:~ }{1:│}{2:~ }{1:│}{2:~}{1:├────────────────────}| + {2:~ }{1:│}{2:~ }{1:│}{2:~}{1:│} | + {1:────────────────────┴────────────────┴─┤}{2:~ }| + {1:│}{2:~ }| + {2:~ }{1:│}{2:~ }| + {2:~ }{1:│}{2:~ }| + {2:~ }{1:│}{2:~ }| + {3:[No Name] 0,0-1 All}| + | + ]], attr_ids={ + [1] = {reverse = true}; + [2] = {bold = true, foreground = Screen.colors.Blue1}; + [3] = {bold = true, reverse = true}; + }} + end) + + it('works when switching between values of laststatus', function() + command('set laststatus=1') + screen:expect{grid=[[ + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + 0,0-1 All | + ]], attr_ids={ + [1] = {foreground = Screen.colors.Blue, bold = true}; + }} + + command('set laststatus=3') + screen:expect{grid=[[ + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {2:[No Name] 0,0-1 All}| + | + ]], attr_ids={ + [1] = {foreground = Screen.colors.Blue, bold = true}; + [2] = {reverse = true, bold = true}; + }} + + command('vsplit | split | vsplit | vsplit | wincmd l | split | 2wincmd l | split') + command('set laststatus=2') + screen:expect{grid=[[ + {1:│} {1:│} {1:│}^ | + {2:~ }{1:│}{2:~ }{1:│}{2:~}{1:│}{2:~ }| + {2:~ }{1:│}{2:~ }{1:│}{2:~}{1:│}{2:~ }| + {2:~ }{1:│}{2:~ }{1:│}{2:~}{1:│}{2:~ }| + {2:~ }{1:│< Name] 0,0-1 │}{2:~}{1:│}{2:~ }| + {2:~ }{1:│} {1:│}{2:~}{1:│}{2:~ }| + {2:~ }{1:│}{2:~ }{1:│}{2:~}{1:│}{2:~ }| + {2:~ }{1:│}{2:~ }{1:│}{2:~}{1:│}{3:<No Name] 0,0-1 All}| + {2:~ }{1:│}{2:~ }{1:│}{2:~}{1:│} | + {1:<No Name] 0,0-1 All < Name] 0,0-1 <│}{2:~ }| + {1:│}{2:~ }| + {2:~ }{1:│}{2:~ }| + {2:~ }{1:│}{2:~ }| + {2:~ }{1:│}{2:~ }| + {1:[No Name] 0,0-1 All <No Name] 0,0-1 All}| + | + ]], attr_ids={ + [1] = {reverse = true}; + [2] = {foreground = Screen.colors.Blue, bold = true}; + [3] = {reverse = true, bold = true}; + }} + + command('set laststatus=3') + screen:expect{grid=[[ + {1:│} {1:│} {1:│}^ | + {2:~ }{1:│}{2:~ }{1:│}{2:~}{1:│}{2:~ }| + {2:~ }{1:│}{2:~ }{1:│}{2:~}{1:│}{2:~ }| + {2:~ }{1:│}{2:~ }{1:│}{2:~}{1:│}{2:~ }| + {2:~ }{1:├────────────────┤}{2:~}{1:│}{2:~ }| + {2:~ }{1:│} {1:│}{2:~}{1:│}{2:~ }| + {2:~ }{1:│}{2:~ }{1:│}{2:~}{1:│}{2:~ }| + {2:~ }{1:│}{2:~ }{1:│}{2:~}{1:├────────────────────}| + {2:~ }{1:│}{2:~ }{1:│}{2:~}{1:│} | + {1:────────────────────┴────────────────┴─┤}{2:~ }| + {1:│}{2:~ }| + {2:~ }{1:│}{2:~ }| + {2:~ }{1:│}{2:~ }| + {2:~ }{1:│}{2:~ }| + {3:[No Name] 0,0-1 All}| + | + ]], attr_ids={ + [1] = {reverse = true}; + [2] = {foreground = Screen.colors.Blue, bold = true}; + [3] = {reverse = true, bold = true}; + }} + + command('set laststatus=0') + screen:expect{grid=[[ + {1:│} {1:│} {1:│}^ | + {2:~ }{1:│}{2:~ }{1:│}{2:~}{1:│}{2:~ }| + {2:~ }{1:│}{2:~ }{1:│}{2:~}{1:│}{2:~ }| + {2:~ }{1:│}{2:~ }{1:│}{2:~}{1:│}{2:~ }| + {2:~ }{1:│< Name] 0,0-1 │}{2:~}{1:│}{2:~ }| + {2:~ }{1:│} {1:│}{2:~}{1:│}{2:~ }| + {2:~ }{1:│}{2:~ }{1:│}{2:~}{1:│}{2:~ }| + {2:~ }{1:│}{2:~ }{1:│}{2:~}{1:│}{3:<No Name] 0,0-1 All}| + {2:~ }{1:│}{2:~ }{1:│}{2:~}{1:│} | + {1:<No Name] 0,0-1 All < Name] 0,0-1 <│}{2:~ }| + {1:│}{2:~ }| + {2:~ }{1:│}{2:~ }| + {2:~ }{1:│}{2:~ }| + {2:~ }{1:│}{2:~ }| + {2:~ }{1:│}{2:~ }| + 0,0-1 All | + ]], attr_ids={ + [1] = {reverse = true}; + [2] = {foreground = Screen.colors.Blue, bold = true}; + [3] = {reverse = true, bold = true}; + }} + + command('set laststatus=3') + screen:expect{grid=[[ + {1:│} {1:│} {1:│}^ | + {2:~ }{1:│}{2:~ }{1:│}{2:~}{1:│}{2:~ }| + {2:~ }{1:│}{2:~ }{1:│}{2:~}{1:│}{2:~ }| + {2:~ }{1:│}{2:~ }{1:│}{2:~}{1:│}{2:~ }| + {2:~ }{1:├────────────────┤}{2:~}{1:│}{2:~ }| + {2:~ }{1:│} {1:│}{2:~}{1:│}{2:~ }| + {2:~ }{1:│}{2:~ }{1:│}{2:~}{1:│}{2:~ }| + {2:~ }{1:│}{2:~ }{1:│}{2:~}{1:├────────────────────}| + {2:~ }{1:│}{2:~ }{1:│}{2:~}{1:│} | + {1:────────────────────┴────────────────┴─┤}{2:~ }| + {1:│}{2:~ }| + {2:~ }{1:│}{2:~ }| + {2:~ }{1:│}{2:~ }| + {2:~ }{1:│}{2:~ }| + {3:[No Name] 0,0-1 All}| + | + ]], attr_ids={ + [1] = {reverse = true}; + [2] = {foreground = Screen.colors.Blue, bold = true}; + [3] = {reverse = true, bold = true}; + }} + end) + + it('win_move_statusline() can reduce cmdheight to 1', function() + eq(1, meths.get_option('cmdheight')) + funcs.win_move_statusline(0, -1) + eq(2, meths.get_option('cmdheight')) + funcs.win_move_statusline(0, -1) + eq(3, meths.get_option('cmdheight')) + funcs.win_move_statusline(0, 1) + eq(2, meths.get_option('cmdheight')) + funcs.win_move_statusline(0, 1) + eq(1, meths.get_option('cmdheight')) + end) + + it('mouse dragging can reduce cmdheight to 1', function() + command('set mouse=a') + meths.input_mouse('left', 'press', '', 0, 14, 10) + eq(1, meths.get_option('cmdheight')) + meths.input_mouse('left', 'drag', '', 0, 13, 10) + eq(2, meths.get_option('cmdheight')) + meths.input_mouse('left', 'drag', '', 0, 12, 10) + eq(3, meths.get_option('cmdheight')) + meths.input_mouse('left', 'drag', '', 0, 13, 10) + eq(2, meths.get_option('cmdheight')) + meths.input_mouse('left', 'drag', '', 0, 14, 10) + eq(1, meths.get_option('cmdheight')) + end) +end) diff --git a/test/functional/ui/highlight_spec.lua b/test/functional/ui/highlight_spec.lua index c00d30fe32..8afc69a649 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 @@ -276,6 +276,24 @@ describe('highlight defaults', function() ]], {[0] = {bold=true, foreground=Screen.colors.Blue}}) end) + it('linking updates window highlight immediately #16552', function() + screen:try_resize(53, 4) + screen:expect([[ + ^ | + {0:~ }| + {0:~ }| + | + ]], {[0] = {bold=true, foreground=Screen.colors.Blue}}) + feed_command("hi NonTextAlt guifg=Red") + feed_command("hi! link NonText NonTextAlt") + screen:expect([[ + ^ | + {0:~ }| + {0:~ }| + :hi! link NonText NonTextAlt | + ]], {[0] = {foreground=Screen.colors.Red}}) + end) + it('Cursor after `:hi clear|syntax reset` #6508', function() command('highlight clear|syntax reset') eq('guifg=bg guibg=fg', eval([[matchstr(execute('hi Cursor'), '\v(gui|cterm).*$')]])) @@ -822,7 +840,7 @@ describe("'listchars' highlight", function() end) end) -describe('CursorLine highlight', function() +describe('CursorLine and CursorLineNr highlights', function() before_each(clear) it('overridden by Error, ColorColumn if fg not set', function() @@ -919,17 +937,18 @@ describe('CursorLine highlight', function() [2] = {foreground = Screen.colors.Yellow}; [3] = {foreground = Screen.colors.Red, background = Screen.colors.Green}; [4] = {foreground = Screen.colors.Green, background = Screen.colors.Red}; + [5] = {bold = true}, -- ModeMsg }) screen:attach() - feed_command('set wrap cursorline cursorlineopt=screenline') - feed_command('set showbreak=>>>') - feed_command('highlight clear NonText') - feed_command('highlight clear CursorLine') - feed_command('highlight NonText guifg=Yellow gui=NONE') - feed_command('highlight LineNr guifg=Red guibg=Green gui=NONE') - feed_command('highlight CursorLine guifg=Black guibg=White gui=NONE') - feed_command('highlight CursorLineNr guifg=Green guibg=Red gui=NONE') + command('set wrap cursorline cursorlineopt=screenline') + command('set showbreak=>>>') + command('highlight clear NonText') + command('highlight clear CursorLine') + command('highlight NonText guifg=Yellow gui=NONE') + command('highlight LineNr guifg=Red guibg=Green gui=NONE') + command('highlight CursorLine guifg=Black guibg=White gui=NONE') + command('highlight CursorLineNr guifg=Green guibg=Red gui=NONE') feed('30iø<esc>o<esc>30ia<esc>') @@ -959,7 +978,7 @@ describe('CursorLine highlight', function() ]]) -- CursorLineNr should not apply to line number when 'cursorlineopt' does not contain "number" - feed_command('set relativenumber numberwidth=2') + command('set relativenumber numberwidth=2') screen:expect([[ {3:0 }{1:øøøøøøøøøøøø^øøøøøø}| {3: }{2:>>>}øøøøøøøøøøøø | @@ -969,7 +988,7 @@ describe('CursorLine highlight', function() ]]) -- CursorLineNr should apply to line number when 'cursorlineopt' contains "number" - feed_command('set cursorlineopt+=number') + command('set cursorlineopt+=number') screen:expect([[ {4:0 }{1:øøøøøøøøøøøø^øøøøøø}| {3: }{2:>>>}øøøøøøøøøøøø | @@ -1001,6 +1020,44 @@ describe('CursorLine highlight', function() {3: }{2:>>>}{1:aaaaaaaaa^aaa }| | ]]) + + -- updated in Insert mode + feed('I') + screen:expect([[ + {3:1 }øøøøøøøøøøøøøøøøøø| + {3: }{2:>>>}øøøøøøøøøøøø | + {4:0 }{1:^aaaaaaaaaaaaaaaaaa}| + {3: }{2:>>>}aaaaaaaaaaaa | + {5:-- INSERT --} | + ]]) + + feed('<Esc>gg') + screen:expect([[ + {4:0 }{1:^øøøøøøøøøøøøøøøøøø}| + {3: }{2:>>>}øøøøøøøøøøøø | + {3:1 }aaaaaaaaaaaaaaaaaa| + {3: }{2:>>>}aaaaaaaaaaaa | + | + ]]) + + command('inoremap <F2> <Cmd>call cursor(1, 1)<CR>') + feed('A') + screen:expect([[ + {4:0 }øøøøøøøøøøøøøøøøøø| + {3: }{2:>>>}{1:øøøøøøøøøøøø^ }| + {3:1 }aaaaaaaaaaaaaaaaaa| + {3: }{2:>>>}aaaaaaaaaaaa | + {5:-- INSERT --} | + ]]) + + feed('<F2>') + screen:expect([[ + {4:0 }{1:^øøøøøøøøøøøøøøøøøø}| + {3: }{2:>>>}øøøøøøøøøøøø | + {3:1 }aaaaaaaaaaaaaaaaaa| + {3: }{2:>>>}aaaaaaaaaaaa | + {5:-- INSERT --} | + ]]) end) it('always updated. vim-patch:8.1.0849', function() @@ -1063,7 +1120,47 @@ describe('CursorLine highlight', function() ]]) end) - it('with split-windows in diff-mode', function() + it('is updated if cursor is moved up from timer vim-patch:8.2.4591', function() + local screen = Screen.new(50, 8) + screen:set_default_attr_ids({ + [1] = {background = Screen.colors.Gray90}, -- CursorLine + [2] = {bold = true, foreground = Screen.colors.Blue1}, -- NonText + }) + screen:attach() + exec([[ + call setline(1, ['aaaaa', 'bbbbb', 'ccccc', 'ddddd']) + set cursorline + call cursor(4, 1) + + func Func(timer) + call cursor(2, 1) + endfunc + + call timer_start(300, 'Func') + ]]) + screen:expect({grid = [[ + aaaaa | + bbbbb | + ccccc | + {1:^ddddd }| + {2:~ }| + {2:~ }| + {2:~ }| + | + ]], timeout = 100}) + screen:expect({grid = [[ + aaaaa | + {1:^bbbbb }| + ccccc | + ddddd | + {2:~ }| + {2:~ }| + {2:~ }| + | + ]]}) + end) + + it('with split windows in diff mode', function() local screen = Screen.new(50,12) screen:set_default_attr_ids({ [1] = {foreground = Screen.colors.DarkBlue, background = Screen.colors.WebGray}, @@ -1075,7 +1172,6 @@ describe('CursorLine highlight', function() [7] = {background = Screen.colors.Red, foreground = Screen.colors.White}, [8] = {bold = true, foreground = Screen.colors.Blue1}, [9] = {bold = true, reverse = true}, - [10] = {bold = true}, }) screen:attach() @@ -1152,8 +1248,261 @@ describe('CursorLine highlight', function() background = Screen.colors.Red}, }) end) + + it('CursorLineNr shows correctly just below filler lines', function() + local screen = Screen.new(50,12) + screen:set_default_attr_ids({ + [1] = {foreground = Screen.colors.DarkBlue, background = Screen.colors.WebGray}, + [2] = {background = Screen.colors.LightCyan1, bold = true, foreground = Screen.colors.Blue1}, + [3] = {reverse = true}, + [4] = {background = Screen.colors.LightBlue}, + [5] = {background = Screen.colors.Red, foreground = Screen.colors.White}, + [6] = {background = Screen.colors.White, bold = true, foreground = Screen.colors.Black}, + [7] = {bold = true, foreground = Screen.colors.Blue1}, + [8] = {bold = true, reverse = true}, + [9] = {foreground = Screen.colors.Brown}, + }) + screen:attach() + + command('hi CursorLine guibg=red guifg=white') + command('hi CursorLineNr guibg=white guifg=black gui=bold') + command('set cursorline number') + command('call setline(1, ["baz", "foo", "foo", "bar"])') + feed('2gg0') + command('vnew') + command('call setline(1, ["foo", "foo", "bar"])') + command('windo diffthis') + command('1wincmd w') + screen:expect([[ + {1: }{9: }{2:-------------------}{3:│}{1: }{9: 1 }{4:baz }| + {1: }{6: 1 }{5:^foo }{3:│}{1: }{6: 2 }{5:foo }| + {1: }{9: 2 }foo {3:│}{1: }{9: 3 }foo | + {1: }{9: 3 }bar {3:│}{1: }{9: 4 }bar | + {7:~ }{3:│}{7:~ }| + {7:~ }{3:│}{7:~ }| + {7:~ }{3:│}{7:~ }| + {7:~ }{3:│}{7:~ }| + {7:~ }{3:│}{7:~ }| + {7:~ }{3:│}{7:~ }| + {8:[No Name] [+] }{3:[No Name] [+] }| + | + ]]) + command('set cursorlineopt=number') + screen:expect([[ + {1: }{9: }{2:-------------------}{3:│}{1: }{9: 1 }{4:baz }| + {1: }{6: 1 }^foo {3:│}{1: }{6: 2 }{5:foo }| + {1: }{9: 2 }foo {3:│}{1: }{9: 3 }foo | + {1: }{9: 3 }bar {3:│}{1: }{9: 4 }bar | + {7:~ }{3:│}{7:~ }| + {7:~ }{3:│}{7:~ }| + {7:~ }{3:│}{7:~ }| + {7:~ }{3:│}{7:~ }| + {7:~ }{3:│}{7:~ }| + {7:~ }{3:│}{7:~ }| + {8:[No Name] [+] }{3:[No Name] [+] }| + | + ]]) + end) end) +describe('CursorColumn highlight', function() + local screen + before_each(function() + clear() + screen = Screen.new(50, 8) + screen:set_default_attr_ids({ + [1] = {background = Screen.colors.Gray90}, -- CursorColumn + [2] = {bold = true, foreground = Screen.colors.Blue1}, -- NonText + [3] = {bold = true}, -- ModeMsg + }) + screen:attach() + end) + + it('is updated when pressing "i" on a TAB character', function() + exec([[ + call setline(1, ['123456789', "a\tb"]) + set cursorcolumn + call cursor(2, 2) + ]]) + screen:expect([[ + 1234567{1:8}9 | + a ^ b | + {2:~ }| + {2:~ }| + {2:~ }| + {2:~ }| + {2:~ }| + | + ]]) + feed('i') + screen:expect([[ + 1{1:2}3456789 | + a^ b | + {2:~ }| + {2:~ }| + {2:~ }| + {2:~ }| + {2:~ }| + {3:-- INSERT --} | + ]]) + feed('<C-O>') + screen:expect([[ + 1234567{1:8}9 | + a ^ b | + {2:~ }| + {2:~ }| + {2:~ }| + {2:~ }| + {2:~ }| + {3:-- (insert) --} | + ]]) + feed('i') + screen:expect([[ + 1{1:2}3456789 | + a^ b | + {2:~ }| + {2:~ }| + {2:~ }| + {2:~ }| + {2:~ }| + {3:-- INSERT --} | + ]]) + end) + + it('is updated if cursor is moved from timer', function() + exec([[ + call setline(1, ['aaaaa', 'bbbbb', 'ccccc', 'ddddd']) + set cursorcolumn + call cursor(4, 5) + + func Func(timer) + call cursor(1, 1) + endfunc + + call timer_start(300, 'Func') + ]]) + screen:expect({grid = [[ + aaaa{1:a} | + bbbb{1:b} | + cccc{1:c} | + dddd^d | + {2:~ }| + {2:~ }| + {2:~ }| + | + ]], timeout = 100}) + screen:expect({grid = [[ + ^aaaaa | + {1:b}bbbb | + {1:c}cccc | + {1:d}dddd | + {2:~ }| + {2:~ }| + {2:~ }| + | + ]]}) + 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 @@ -1359,6 +1708,46 @@ describe("'number' and 'relativenumber' highlight", function() | ]]) end) + + it('relative number highlight is updated if cursor is moved from timer', function() + local screen = Screen.new(50, 8) + screen:set_default_attr_ids({ + [1] = {foreground = Screen.colors.Brown}, -- LineNr + [2] = {bold = true, foreground = Screen.colors.Blue1}, -- NonText + }) + screen:attach() + exec([[ + call setline(1, ['aaaaa', 'bbbbb', 'ccccc', 'ddddd']) + set relativenumber + call cursor(4, 1) + + func Func(timer) + call cursor(1, 1) + endfunc + + call timer_start(300, 'Func') + ]]) + screen:expect({grid = [[ + {1: 3 }aaaaa | + {1: 2 }bbbbb | + {1: 1 }ccccc | + {1: 0 }^ddddd | + {2:~ }| + {2:~ }| + {2:~ }| + | + ]], timeout = 100}) + screen:expect({grid = [[ + {1: 0 }^aaaaa | + {1: 1 }bbbbb | + {1: 2 }ccccc | + {1: 3 }ddddd | + {2:~ }| + {2:~ }| + {2:~ }| + | + ]]}) + end) end) describe("'winhighlight' highlight", function() diff --git a/test/functional/ui/hlstate_spec.lua b/test/functional/ui/hlstate_spec.lua index 2a567b28ee..dcea2c76dd 100644 --- a/test/functional/ui/hlstate_spec.lua +++ b/test/functional/ui/hlstate_spec.lua @@ -59,7 +59,7 @@ describe('ext_hlstate detailed highlights', function() it('work with cleared UI highlights', function() screen:set_default_attr_ids({ - [1] = {{}, {{hi_name = "VertSplit", ui_name = "VertSplit", kind = "ui"}}}, + [1] = {{}, {{hi_name = "VertSplit", ui_name = "WinSeparator", kind = "ui"}}}, [2] = {{bold = true, foreground = Screen.colors.Blue1}, {{hi_name = "NonText", ui_name = "EndOfBuffer", kind = "ui"}}}, [3] = {{bold = true, reverse = true}, @@ -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..8925dda730 100644 --- a/test/functional/ui/input_spec.lua +++ b/test/functional/ui/input_spec.lua @@ -3,9 +3,11 @@ local clear, feed_command = helpers.clear, helpers.feed_command local feed, next_msg, eq = helpers.feed, helpers.next_msg, helpers.eq local command = helpers.command local expect = helpers.expect +local curbuf_contents = helpers.curbuf_contents local meths = helpers.meths local exec_lua = helpers.exec_lua local write_file = helpers.write_file +local funcs = helpers.funcs local Screen = require('test.functional.ui.screen') before_each(clear) @@ -50,6 +52,8 @@ describe('mappings', function() add_mapping('<kenter>','<kenter>') add_mapping('<kcomma>','<kcomma>') add_mapping('<kequal>','<kequal>') + add_mapping('<f38>','<f38>') + add_mapping('<f63>','<f63>') end) it('ok', function() @@ -106,6 +110,8 @@ describe('mappings', function() check_mapping('<KPComma>','<kcomma>') check_mapping('<kequal>','<kequal>') check_mapping('<KPEquals>','<kequal>') + check_mapping('<f38>','<f38>') + check_mapping('<f63>','<f63>') end) it('support meta + multibyte char mapping', function() @@ -114,11 +120,100 @@ 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 pairs', function() + describe('<tab> / <c-i>', function() + it('ok', function() + feed('i<tab><c-i><esc>') + eq('\t\t', curbuf_contents()) + end) + + it('can be mapped', function() + command('inoremap <tab> TAB!') + command('inoremap <c-i> CTRL-I!') + feed('i<tab><c-i><esc>') + eq('TAB!CTRL-I!', curbuf_contents()) + end) + end) + + describe('<cr> / <c-m>', function() + it('ok', function() + feed('iunos<c-m>dos<cr>tres<esc>') + eq('unos\ndos\ntres', curbuf_contents()) + end) + + it('can be mapped', function() + command('inoremap <c-m> SNIPPET!') + command('inoremap <cr> , and then<cr>') + feed('iunos<c-m>dos<cr>tres<esc>') + eq('unosSNIPPET!dos, and then\ntres', curbuf_contents()) + end) + end) + + describe('<esc> / <c-[>', function() + it('ok', function() + feed('2adouble<c-[>asingle<esc>') + eq('doubledoublesingle', curbuf_contents()) + end) + + it('can be mapped', function() + command('inoremap <c-[> HALLOJ!') + command('inoremap <esc> ,<esc>') + feed('2adubbel<c-[>upp<esc>') + eq('dubbelHALLOJ!upp,dubbelHALLOJ!upp,', curbuf_contents()) + end) + end) +end) + +it('Ctrl-6 is Ctrl-^ vim-patch:8.1.2333', function() + command('split aaa') + command('edit bbb') + feed('<C-6>') + eq('aaa', funcs.bufname()) end) describe('input non-printable chars', function() @@ -146,7 +241,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. @@ -237,3 +332,47 @@ describe("event processing and input", function() eq({'notification', 'stop', {}}, next_msg()) end) end) + +describe('display is updated', function() + local screen + before_each(function() + screen = Screen.new(60, 8) + screen:set_default_attr_ids({ + [1] = {bold = true, foreground = Screen.colors.Blue1}, -- NonText + [2] = {bold = true}, -- ModeMsg + }) + screen:attach() + end) + + it('in Insert mode after <Nop> mapping #17911', function() + command('imap <Plug>test <Nop>') + command('imap <F2> abc<CR><Plug>test') + feed('i<F2>') + screen:expect([[ + abc | + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {2:-- INSERT --} | + ]]) + end) + + it('in Insert mode after empty string <expr> mapping #17911', function() + command('imap <expr> <Plug>test ""') + command('imap <F2> abc<CR><Plug>test') + feed('i<F2>') + screen:expect([[ + abc | + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {2:-- INSERT --} | + ]]) + end) +end) 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/mouse_spec.lua b/test/functional/ui/mouse_spec.lua index baacef358f..8d3c312def 100644 --- a/test/functional/ui/mouse_spec.lua +++ b/test/functional/ui/mouse_spec.lua @@ -1571,4 +1571,23 @@ describe('ui/mouse/input', function() meths.set_option('winwidth', winwidth) meths.input_mouse('left', 'release', '', 0, 0, 0) end) + + it('scroll keys are not translated into multiclicks #6211 #6989', function() + meths.set_var('mouse_up', 0) + meths.set_var('mouse_up2', 0) + meths.set_var('mouse_up3', 0) + meths.set_var('mouse_up4', 0) + command('nnoremap <ScrollWheelUp> <Cmd>let g:mouse_up += 1<CR>') + command('nnoremap <2-ScrollWheelUp> <Cmd>let g:mouse_up2 += 1<CR>') + command('nnoremap <3-ScrollWheelUp> <Cmd>let g:mouse_up3 += 1<CR>') + command('nnoremap <4-ScrollWheelUp> <Cmd>let g:mouse_up4 += 1<CR>') + meths.input_mouse('wheel', 'up', '', 0, 0, 0) + meths.input_mouse('wheel', 'up', '', 0, 0, 0) + meths.input_mouse('wheel', 'up', '', 0, 0, 0) + meths.input_mouse('wheel', 'up', '', 0, 0, 0) + eq(4, meths.get_var('mouse_up')) + eq(0, meths.get_var('mouse_up2')) + eq(0, meths.get_var('mouse_up3')) + eq(0, meths.get_var('mouse_up4')) + end) end) 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 aeba049557..07c6c5b046 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() @@ -2213,4 +2320,134 @@ describe('builtin popupmenu', function() feed('<c-y>') assert_alive() end) + + it('truncates double-width character correctly when there is no scrollbar', function() + screen:try_resize(32,8) + command('set completeopt+=menuone,noselect') + feed('i' .. string.rep(' ', 13)) + funcs.complete(14, {'哦哦哦哦哦哦哦哦哦哦'}) + screen:expect([[ + ^ | + {1:~ }{n: 哦哦哦哦哦哦哦哦哦>}| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {2:-- INSERT --} | + ]]) + end) + + it('truncates double-width character correctly when there is scrollbar', function() + screen:try_resize(32,8) + command('set completeopt+=noselect') + command('set pumheight=4') + feed('i' .. string.rep(' ', 12)) + local items = {} + for _ = 1, 8 do + table.insert(items, {word = '哦哦哦哦哦哦哦哦哦哦', equal = 1, dup = 1}) + end + funcs.complete(13, items) + screen:expect([[ + ^ | + {1:~ }{n: 哦哦哦哦哦哦哦哦哦>}{c: }| + {1:~ }{n: 哦哦哦哦哦哦哦哦哦>}{c: }| + {1:~ }{n: 哦哦哦哦哦哦哦哦哦>}{s: }| + {1:~ }{n: 哦哦哦哦哦哦哦哦哦>}{s: }| + {1:~ }| + {1:~ }| + {2:-- INSERT --} | + ]]) + end) +end) + +describe('builtin popupmenu with ui/ext_multigrid', function() + local screen + before_each(function() + clear() + screen = Screen.new(32, 20) + screen:attach({ext_multigrid=true}) + screen:set_default_attr_ids({ + -- popup selected item / scrollbar track + ['s'] = {background = Screen.colors.WebGray}, + -- popup non-selected item + ['n'] = {background = Screen.colors.LightMagenta}, + -- popup scrollbar knob + ['c'] = {background = Screen.colors.Grey0}, + [1] = {bold = true, foreground = Screen.colors.Blue}, + [2] = {bold = true}, + [3] = {reverse = true}, + [4] = {bold = true, reverse = true}, + [5] = {bold = true, foreground = Screen.colors.SeaGreen}, + [6] = {foreground = Screen.colors.Grey100, background = Screen.colors.Red}, + }) + end) + + it('truncates double-width character correctly when there is no scrollbar', function() + screen:try_resize(32,8) + command('set completeopt+=menuone,noselect') + feed('i' .. string.rep(' ', 13)) + funcs.complete(14, {'哦哦哦哦哦哦哦哦哦哦'}) + screen:expect({grid=[[ + ## grid 1 + [2:--------------------------------]| + [2:--------------------------------]| + [2:--------------------------------]| + [2:--------------------------------]| + [2:--------------------------------]| + [2:--------------------------------]| + [2:--------------------------------]| + [3:--------------------------------]| + ## grid 2 + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + ## grid 3 + {2:-- INSERT --} | + ## grid 4 + {n: 哦哦哦哦哦哦哦哦哦>}| + ]], float_pos={[4] = {{id = -1}, 'NW', 2, 1, 12, false, 100}}}) + end) + + it('truncates double-width character correctly when there is scrollbar', function() + screen:try_resize(32,8) + command('set completeopt+=noselect') + command('set pumheight=4') + feed('i' .. string.rep(' ', 12)) + local items = {} + for _ = 1, 8 do + table.insert(items, {word = '哦哦哦哦哦哦哦哦哦哦', equal = 1, dup = 1}) + end + funcs.complete(13, items) + screen:expect({grid=[[ + ## grid 1 + [2:--------------------------------]| + [2:--------------------------------]| + [2:--------------------------------]| + [2:--------------------------------]| + [2:--------------------------------]| + [2:--------------------------------]| + [2:--------------------------------]| + [3:--------------------------------]| + ## grid 2 + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + ## grid 3 + {2:-- INSERT --} | + ## grid 4 + {n: 哦哦哦哦哦哦哦哦哦>}{c: }| + {n: 哦哦哦哦哦哦哦哦哦>}{c: }| + {n: 哦哦哦哦哦哦哦哦哦>}{s: }| + {n: 哦哦哦哦哦哦哦哦哦>}{s: }| + ]], float_pos={[4] = {{id = -1}, 'NW', 2, 1, 11, false, 100}}}) + end) end) diff --git a/test/functional/ui/screen.lua b/test/functional/ui/screen.lua index 61f19c3794..3e94fdbf44 100644 --- a/test/functional/ui/screen.lua +++ b/test/functional/ui/screen.lua @@ -576,16 +576,16 @@ to the test if they make sense. print([[ warning: Screen changes were received after the expected state. This indicates -indeterminism in the test. Try adding screen:expect(...) (or wait()) between -asynchronous (feed(), nvim_input()) and synchronous API calls. +indeterminism in the test. Try adding screen:expect(...) (or poke_eventloop()) +between asynchronous (feed(), nvim_input()) and synchronous API calls. - Use screen:redraw_debug() to investigate; it may find relevant intermediate states that should be added to the test to make it more robust. - If the purpose of the test is to assert state after some user input sent with feed(), adding screen:expect() before the feed() will help to ensure the input is sent when Nvim is in a predictable state. This is preferable - to wait(), for being closer to real user interaction. - - wait() can trigger redraws and consequently generate more indeterminism. - Try removing wait(). + to poke_eventloop(), for being closer to real user interaction. + - poke_eventloop() can trigger redraws and thus generate more indeterminism. + Try removing poke_eventloop(). ]]) did_warn = true end @@ -1559,10 +1559,11 @@ end function Screen:_equal_attrs(a, b) return a.bold == b.bold and a.standout == b.standout and - a.underline == b.underline and a.undercurl == b.undercurl and - a.italic == b.italic and a.reverse == b.reverse and - a.foreground == b.foreground and a.background == b.background and - a.special == b.special and a.blend == b.blend and + a.underline == b.underline and a.underlineline == b.underlineline and + a.undercurl == b.undercurl and a.underdot == b.underdot and + a.underdash == b.underdash and a.italic == b.italic and + a.reverse == b.reverse and a.foreground == b.foreground and + a.background == b.background and a.special == b.special and a.blend == b.blend and a.strikethrough == b.strikethrough and a.fg_indexed == b.fg_indexed and a.bg_indexed == b.bg_indexed end 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 741b93043d..dbc92ca222 100644 --- a/test/functional/ui/sign_spec.lua +++ b/test/functional/ui/sign_spec.lua @@ -157,6 +157,99 @@ describe('Signs', function() ]]) end) + it('higlights the cursorline sign with culhl', function() + feed('ia<cr>b<cr>c<esc>') + command('sign define piet text=>> texthl=Search culhl=ErrorMsg') + command('sign place 1 line=1 name=piet buffer=1') + command('sign place 2 line=2 name=piet buffer=1') + command('sign place 3 line=3 name=piet buffer=1') + command('set cursorline') + screen:expect([[ + {1:>>}a | + {1:>>}b | + {8:>>}{3:^c }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + | + ]]) + feed('k') + screen:expect([[ + {1:>>}a | + {8:>>}{3:^b }| + {1:>>}c | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + | + ]]) + command('set nocursorline') + screen:expect([[ + {1:>>}a | + {1:>>}^b | + {1:>>}c | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + | + ]]) + command('set cursorline cursorlineopt=line') + screen:expect([[ + {1:>>}a | + {1:>>}{3:^b }| + {1:>>}c | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + | + ]]) + command('set cursorlineopt=number') + screen:expect([[ + {1:>>}a | + {8:>>}^b | + {1:>>}c | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + | + ]]) + end) + it('multiple signs #9295', function() feed('ia<cr>b<cr>c<cr><esc>') command('set number') @@ -353,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:~ }| @@ -375,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:~ }| @@ -389,7 +482,7 @@ describe('Signs', function() ]]) end) - it('ignores signs with no icon and text when calculting the signcolumn width', function() + it('ignores signs with no icon and text when calculating the signcolumn width', function() feed('ia<cr>b<cr>c<cr><esc>') command('set number') command('set signcolumn=auto:2') @@ -419,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/ui/syntax_conceal_spec.lua b/test/functional/ui/syntax_conceal_spec.lua index 4e1852162f..9d10c55cb6 100644 --- a/test/functional/ui/syntax_conceal_spec.lua +++ b/test/functional/ui/syntax_conceal_spec.lua @@ -3,6 +3,7 @@ local Screen = require('test.functional.ui.screen') local clear, feed, command = helpers.clear, helpers.feed, helpers.command local eq = helpers.eq local insert = helpers.insert +local poke_eventloop = helpers.poke_eventloop describe('Screen', function() local screen @@ -255,6 +256,40 @@ describe('Screen', function() ]]) end) end) -- a region of text (implicit concealing) + + it("cursor position is correct when entering Insert mode with cocu=ni #13916", function() + insert([[foobarfoobarfoobar]]) + -- move to end of line + feed("$") + command("set concealcursor=ni") + command("syn match Foo /foobar/ conceal cchar=&") + screen:expect([[ + {1:&&&}^ | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + | + ]]) + feed("i") + -- cursor should stay in place, not jump to column 16 + screen:expect([[ + {1:&&&}^ | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {4:-- INSERT --} | + ]]) + end) end) -- match and conceal describe("let the conceal level be", function() @@ -911,7 +946,57 @@ describe('Screen', function() {0:~ }| | ]]} - eq(grid_lines, {{2, 0, {{'c', 0, 3}}}}) + eq({{2, 0, {{'c', 0, 3}}}}, grid_lines) + end) + + it('K_EVENT should not cause extra redraws with concealcursor #13196', function() + command('set conceallevel=1') + command('set concealcursor=nv') + command('set redrawdebug+=nodelta') + + insert([[ + aaa + bbb + ccc + ]]) + screen:expect{grid=[[ + aaa | + bbb | + ccc | + ^ | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + | + ]]} + + -- XXX: hack to get notifications, and check only a single line is + -- updated. Could use next_msg() also. + local orig_handle_grid_line = screen._handle_grid_line + local grid_lines = {} + function screen._handle_grid_line(self, grid, row, col, items) + table.insert(grid_lines, {row, col, items}) + orig_handle_grid_line(self, grid, row, col, items) + end + feed('k') + screen:expect{grid=[[ + aaa | + bbb | + ^ccc | + | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + | + ]]} + eq({{2, 0, {{'c', 0, 3}}}}, grid_lines) + poke_eventloop() -- causes K_EVENT key + screen:expect_unchanged() + eq({{2, 0, {{'c', 0, 3}}}}, grid_lines) end) -- Copy of Test_cursor_column_in_concealed_line_after_window_scroll in 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/lang_spec.lua b/test/functional/vimscript/lang_spec.lua index d5254986ab..90437f2ee1 100644 --- a/test/functional/vimscript/lang_spec.lua +++ b/test/functional/vimscript/lang_spec.lua @@ -11,6 +11,7 @@ describe('vimscript', function() return end source([[ + let s:foo = 1 func! <sid>_dummy_function() echo 1 endfunc diff --git a/test/functional/vimscript/screenpos_spec.lua b/test/functional/vimscript/screenpos_spec.lua new file mode 100644 index 0000000000..75e5c02298 --- /dev/null +++ b/test/functional/vimscript/screenpos_spec.lua @@ -0,0 +1,51 @@ +local helpers = require('test.functional.helpers')(after_each) +local clear, eq, meths = helpers.clear, helpers.eq, helpers.meths +local command, funcs = helpers.command, helpers.funcs + +before_each(clear) + +describe('screenpos() function', function() + it('works in floating window with border', function() + local bufnr = meths.create_buf(false, true) + local opts = { + relative='editor', + height=8, + width=12, + row=6, + col=8, + anchor='NW', + style='minimal', + border='none', + focusable=1 + } + local float = meths.open_win(bufnr, false, opts) + command('redraw') + local pos = funcs.screenpos(bufnr, 1, 1) + eq(7, pos.row) + eq(9, pos.col) + + -- only left border + opts.border = {'', '', '', '', '', '', '', '|'} + meths.win_set_config(float, opts) + command('redraw') + pos = funcs.screenpos(bufnr, 1, 1) + eq(7, pos.row) + eq(10, pos.col) + + -- only top border + opts.border = {'', '_', '', '', '', '', '', ''} + meths.win_set_config(float, opts) + command('redraw') + pos = funcs.screenpos(bufnr, 1, 1) + eq(8, pos.row) + eq(9, pos.col) + + -- both left and top border + opts.border = 'single' + meths.win_set_config(float, opts) + command('redraw') + pos = funcs.screenpos(bufnr, 1, 1) + eq(8, pos.row) + eq(10, pos.col) + end) +end) diff --git a/test/functional/vimscript/timer_spec.lua b/test/functional/vimscript/timer_spec.lua index 9ee0735e40..20f2afb20f 100644 --- a/test/functional/vimscript/timer_spec.lua +++ b/test/functional/vimscript/timer_spec.lua @@ -3,6 +3,7 @@ local Screen = require('test.functional.ui.screen') local feed, eq, eval, ok = helpers.feed, helpers.eq, helpers.eval, helpers.ok local source, nvim_async, run = helpers.source, helpers.nvim_async, helpers.run local clear, command, funcs = helpers.clear, helpers.command, helpers.funcs +local exc_exec = helpers.exc_exec local curbufmeths = helpers.curbufmeths local load_adjust = helpers.load_adjust local retry = helpers.retry @@ -262,4 +263,21 @@ describe('timers', function() eq(2, eval('g:val')) end) + + it("timer_start can't be used in the sandbox", function() + source [[ + function! Scary(timer) abort + call execute('echo ''execute() should be disallowed''', '') + endfunction + ]] + 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 #17257', 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 09b113c01d..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 @@ -741,9 +741,20 @@ function module.read_file_list(filename, start) if not file then return nil end + + -- There is no need to read more than the last 2MB of the log file, so seek + -- to that. + local file_size = file:seek("end") + local offset = file_size - 2000000 + if offset < 0 then + offset = 0 + end + file:seek("set", offset) + local lines = {} local i = 1 - for line in file:lines() do + local line = file:read("*l") + while line ~= nil do if i >= start then table.insert(lines, line) if #lines > maxlines then @@ -751,6 +762,7 @@ function module.read_file_list(filename, start) end end i = i + 1 + line = file:read("*l") end file:close() return lines @@ -789,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 @@ -803,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..833b21b00b 100644 --- a/test/unit/buffer_spec.lua +++ b/test/unit/buffer_spec.lua @@ -17,8 +17,8 @@ describe('buffer functions', function() return buffer.buflist_new(c_file, c_file, 1, flags) end - local close_buffer = function(win, buf, action, abort_if_last) - return buffer.close_buffer(win, buf, action, abort_if_last) + local close_buffer = function(win, buf, action, abort_if_last, ignore_abort) + return buffer.close_buffer(win, buf, action, abort_if_last, ignore_abort) end local path1 = 'test_file_path' @@ -53,7 +53,7 @@ describe('buffer functions', function() itp('should view a closed and hidden buffer as valid', function() local buf = buflist_new(path1, buffer.BLN_LISTED) - close_buffer(NULL, buf, 0, 0) + close_buffer(NULL, buf, 0, 0, 0) eq(true, buffer.buf_valid(buf)) end) @@ -61,7 +61,7 @@ describe('buffer functions', function() itp('should view a closed and unloaded buffer as valid', function() local buf = buflist_new(path1, buffer.BLN_LISTED) - close_buffer(NULL, buf, buffer.DOBUF_UNLOAD, 0) + close_buffer(NULL, buf, buffer.DOBUF_UNLOAD, 0, 0) eq(true, buffer.buf_valid(buf)) end) @@ -69,7 +69,7 @@ describe('buffer functions', function() itp('should view a closed and wiped buffer as invalid', function() local buf = buflist_new(path1, buffer.BLN_LISTED) - close_buffer(NULL, buf, buffer.DOBUF_WIPE, 0) + close_buffer(NULL, buf, buffer.DOBUF_WIPE, 0, 0) eq(false, buffer.buf_valid(buf)) end) @@ -90,7 +90,7 @@ describe('buffer functions', function() eq(buf.handle, buflist_findpat(path1, ONLY_LISTED)) - close_buffer(NULL, buf, buffer.DOBUF_WIPE, 0) + close_buffer(NULL, buf, buffer.DOBUF_WIPE, 0, 0) end) itp('should prefer to match the start of a file path', function() @@ -102,9 +102,9 @@ describe('buffer functions', function() eq(buf2.handle, buflist_findpat("file", ONLY_LISTED)) eq(buf3.handle, buflist_findpat("path", ONLY_LISTED)) - close_buffer(NULL, buf1, buffer.DOBUF_WIPE, 0) - close_buffer(NULL, buf2, buffer.DOBUF_WIPE, 0) - close_buffer(NULL, buf3, buffer.DOBUF_WIPE, 0) + close_buffer(NULL, buf1, buffer.DOBUF_WIPE, 0, 0) + close_buffer(NULL, buf2, buffer.DOBUF_WIPE, 0, 0) + close_buffer(NULL, buf3, buffer.DOBUF_WIPE, 0, 0) end) itp('should prefer to match the end of a file over the middle', function() @@ -118,7 +118,7 @@ describe('buffer functions', function() --} --{ When: We close buf2 - close_buffer(NULL, buf2, buffer.DOBUF_WIPE, 0) + close_buffer(NULL, buf2, buffer.DOBUF_WIPE, 0, 0) -- And: Open buf1, which has 'file' in the middle of its name local buf1 = buflist_new(path1, buffer.BLN_LISTED) @@ -127,8 +127,8 @@ describe('buffer functions', function() eq(buf3.handle, buflist_findpat("file", ONLY_LISTED)) --} - close_buffer(NULL, buf1, buffer.DOBUF_WIPE, 0) - close_buffer(NULL, buf3, buffer.DOBUF_WIPE, 0) + close_buffer(NULL, buf1, buffer.DOBUF_WIPE, 0, 0) + close_buffer(NULL, buf3, buffer.DOBUF_WIPE, 0, 0) end) itp('should match a unique fragment of a file path', function() @@ -138,9 +138,9 @@ describe('buffer functions', function() eq(buf3.handle, buflist_findpat("_test_", ONLY_LISTED)) - close_buffer(NULL, buf1, buffer.DOBUF_WIPE, 0) - close_buffer(NULL, buf2, buffer.DOBUF_WIPE, 0) - close_buffer(NULL, buf3, buffer.DOBUF_WIPE, 0) + close_buffer(NULL, buf1, buffer.DOBUF_WIPE, 0, 0) + close_buffer(NULL, buf2, buffer.DOBUF_WIPE, 0, 0) + close_buffer(NULL, buf3, buffer.DOBUF_WIPE, 0, 0) end) itp('should include / ignore unlisted buffers based on the flag.', function() @@ -152,7 +152,7 @@ describe('buffer functions', function() --} --{ When: We unlist the buffer - close_buffer(NULL, buf3, buffer.DOBUF_DEL, 0) + close_buffer(NULL, buf3, buffer.DOBUF_DEL, 0, 0) -- Then: It should not find the buffer when searching only listed buffers eq(-1, buflist_findpat("_test_", ONLY_LISTED)) @@ -162,7 +162,7 @@ describe('buffer functions', function() --} --{ When: We wipe the buffer - close_buffer(NULL, buf3, buffer.DOBUF_WIPE, 0) + close_buffer(NULL, buf3, buffer.DOBUF_WIPE, 0, 0) -- Then: It should not find the buffer at all eq(-1, buflist_findpat("_test_", ONLY_LISTED)) @@ -180,7 +180,7 @@ describe('buffer functions', function() --} --{ When: The first buffer is unlisted - close_buffer(NULL, buf1, buffer.DOBUF_DEL, 0) + close_buffer(NULL, buf1, buffer.DOBUF_DEL, 0, 0) -- Then: The second buffer is preferred because -- unlisted buffers are not allowed @@ -194,7 +194,7 @@ describe('buffer functions', function() --} --{ When: We unlist the second buffer - close_buffer(NULL, buf2, buffer.DOBUF_DEL, 0) + close_buffer(NULL, buf2, buffer.DOBUF_DEL, 0, 0) -- Then: The first buffer is preferred again -- because buf1 matches better which takes precedence @@ -205,8 +205,8 @@ describe('buffer functions', function() eq(-1, buflist_findpat("test", ONLY_LISTED)) --} - close_buffer(NULL, buf1, buffer.DOBUF_WIPE, 0) - close_buffer(NULL, buf2, buffer.DOBUF_WIPE, 0) + close_buffer(NULL, buf1, buffer.DOBUF_WIPE, 0, 0) + close_buffer(NULL, buf2, buffer.DOBUF_WIPE, 0, 0) end) end) @@ -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/os/shell_spec.lua b/test/unit/os/shell_spec.lua index a73fc8e47e..29a2b78491 100644 --- a/test/unit/os/shell_spec.lua +++ b/test/unit/os/shell_spec.lua @@ -4,7 +4,6 @@ local cimported = helpers.cimport( './src/nvim/os/shell.h', './src/nvim/option_defs.h', './src/nvim/main.h', - './src/nvim/misc1.h', './src/nvim/memory.h' ) local ffi, eq = helpers.ffi, helpers.eq diff --git a/test/unit/path_spec.lua b/test/unit/path_spec.lua index 41954de9be..fb476397e6 100644 --- a/test/unit/path_spec.lua +++ b/test/unit/path_spec.lua @@ -54,15 +54,21 @@ describe('path.c', function() eq(lfs.currentdir(), (ffi.string(buffer))) end) - itp('fails if the given directory does not exist', function() - eq(FAIL, path_full_dir_name('does_not_exist', buffer, length)) - end) - itp('works with a normal relative dir', function() local result = path_full_dir_name('unit-test-directory', buffer, length) eq(lfs.currentdir() .. '/unit-test-directory', (ffi.string(buffer))) eq(OK, result) end) + + itp('works with a non-existing relative dir', function() + local result = path_full_dir_name('does-not-exist', buffer, length) + eq(lfs.currentdir() .. '/does-not-exist', (ffi.string(buffer))) + eq(OK, result) + end) + + itp('fails with a non-existing absolute dir', function() + eq(FAIL, path_full_dir_name('/does_not_exist', buffer, length)) + end) end) describe('path_full_compare', function() @@ -620,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/test/unit/strings_spec.lua b/test/unit/strings_spec.lua index e54c82b26a..e085ac749d 100644 --- a/test/unit/strings_spec.lua +++ b/test/unit/strings_spec.lua @@ -138,3 +138,15 @@ describe('vim_strchr()', function() eq(nil, vim_strchr('«\237\175\191\237\188\128»', 0x10FF00)) end) end) + +describe('strcase_save()' , function() + local strcase_save = function(input_string, upper) + local res = strings.strcase_save(to_cstr(input_string), upper) + return ffi.string(res) + end + + itp('decodes overlong encoded characters.', function() + eq("A", strcase_save("\xc1\x81", true)) + eq("a", strcase_save("\xc1\x81", false)) + end) +end) diff --git a/test/unit/viml/expressions/parser_spec.lua b/test/unit/viml/expressions/parser_spec.lua index 8342044b5e..51a703b593 100644 --- a/test/unit/viml/expressions/parser_spec.lua +++ b/test/unit/viml/expressions/parser_spec.lua @@ -48,6 +48,7 @@ local predefined_hl_defs = { TermCursor=true, VertSplit=true, WildMenu=true, + WinSeparator=true, EndOfBuffer=true, QuickFixLine=true, Substitute=true, diff --git a/third-party/CMakeLists.txt b/third-party/CMakeLists.txt index 1bdfa7a6dd..c06e31a328 100644 --- a/third-party/CMakeLists.txt +++ b/third-party/CMakeLists.txt @@ -97,7 +97,7 @@ endif() if(MINGW AND CMAKE_GENERATOR MATCHES "Ninja") find_program(MAKE_PRG NAMES mingw32-make) if(NOT MAKE_PRG) - message(FATAL_ERROR "GNU Make for mingw32 is required to build the dependecies.") + message(FATAL_ERROR "GNU Make for mingw32 is required to build the dependencies.") else() message(STATUS "Found GNU Make for mingw32: ${MAKE_PRG}") endif() @@ -144,15 +144,15 @@ 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/v1.44.1.tar.gz) +set(LIBUV_SHA256 e91614e6dc2dd0bfdd140ceace49438882206b7a6fb00b8750914e67a9ed6d6b) 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) # https://github.com/LuaJIT/LuaJIT/tree/v2.1 -set(LUAJIT_URL https://github.com/LuaJIT/LuaJIT/archive/b4b2dce9fc3ffaaaede39b36d06415311e2aa516.tar.gz) -set(LUAJIT_SHA256 6c9e46877db2df16ca0fa76db4043ed30a1ae60c89d9ba2c3e4d35eb2360cd4d) +set(LUAJIT_URL https://github.com/LuaJIT/LuaJIT/archive/5e3c45c43bb0e0f1f2917d432e9d2dba12c42a6e.tar.gz) +set(LUAJIT_SHA256 72294770c73ff2ed03deb9c81a38253c45fd634917583c6ae39f5143c9adc1e1) set(LUA_URL https://www.lua.org/ftp/lua-5.1.5.tar.gz) set(LUA_SHA256 2640fc56a795f29d28ef15e13c34a47e223960b0240e8cb0a82d9b0738695333) @@ -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) @@ -200,11 +200,11 @@ set(GETTEXT_SHA256 66415634c6e8c3fa8b71362879ec7575e27da43da562c798a8a2f223e6e47 set(LIBICONV_URL https://ftp.gnu.org/pub/gnu/libiconv/libiconv-1.15.tar.gz) set(LIBICONV_SHA256 ccf536620a45458d26ba83887a983b96827001e92a13847b45e4925cc8913178) -set(TREESITTER_C_URL https://github.com/tree-sitter/tree-sitter-c/archive/5aa0bbb.tar.gz) -set(TREESITTER_C_SHA256 a5dcb37460d83002dfae7f9a208170ddbc9a047f231b9d6b75da7d36d707db2f) +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/7890a29db0b186b7b21a0a95d99fa6c562b8316b.tar.gz) -set(TREESITTER_SHA256 634006b0336a5eef1b07d2f80a4d4f8ac1522bf15759ec3e5dda0032a734fb19) +set(TREESITTER_URL https://github.com/tree-sitter/tree-sitter/archive/v0.20.6.tar.gz) +set(TREESITTER_SHA256 4d37eaef8a402a385998ff9aca3e1043b4a3bba899bceeff27a7178e1165b9de) 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/BuildLuajit.cmake b/third-party/cmake/BuildLuajit.cmake index c4e5112dce..e02d7fe609 100644 --- a/third-party/cmake/BuildLuajit.cmake +++ b/third-party/cmake/BuildLuajit.cmake @@ -124,7 +124,9 @@ elseif(MINGW) COMMAND ${CMAKE_COMMAND} -E copy ${DEPS_BUILD_DIR}/src/luajit/src/libluajit.a ${DEPS_INSTALL_DIR}/lib COMMAND ${CMAKE_COMMAND} -E make_directory ${DEPS_INSTALL_DIR}/include/luajit-2.1 COMMAND ${CMAKE_COMMAND} -DFROM_GLOB=${DEPS_BUILD_DIR}/src/luajit/src/*.h -DTO=${DEPS_INSTALL_DIR}/include/luajit-2.1 -P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/CopyFilesGlob.cmake - ) + COMMAND ${CMAKE_COMMAND} -E make_directory ${DEPS_INSTALL_DIR}/bin/lua/jit + COMMAND ${CMAKE_COMMAND} -E copy_directory ${DEPS_BUILD_DIR}/src/luajit/src/jit ${DEPS_INSTALL_DIR}/bin/lua/jit + ) elseif(MSVC) BuildLuaJit( @@ -138,8 +140,10 @@ elseif(MSVC) # Luv searches for luajit.lib COMMAND ${CMAKE_COMMAND} -E copy ${DEPS_BUILD_DIR}/src/luajit/src/lua51.lib ${DEPS_INSTALL_DIR}/lib/luajit.lib COMMAND ${CMAKE_COMMAND} -E make_directory ${DEPS_INSTALL_DIR}/include/luajit-2.1 - COMMAND ${CMAKE_COMMAND} -DFROM_GLOB=${DEPS_BUILD_DIR}/src/luajit/src/*.h -DTO=${DEPS_INSTALL_DIR}/include/luajit-2.1 -P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/CopyFilesGlob.cmake) - + COMMAND ${CMAKE_COMMAND} -DFROM_GLOB=${DEPS_BUILD_DIR}/src/luajit/src/*.h -DTO=${DEPS_INSTALL_DIR}/include/luajit-2.1 -P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/CopyFilesGlob.cmake + COMMAND ${CMAKE_COMMAND} -E make_directory ${DEPS_INSTALL_DIR}/bin/lua/jit + COMMAND ${CMAKE_COMMAND} -E copy_directory ${DEPS_BUILD_DIR}/src/luajit/src/jit ${DEPS_INSTALL_DIR}/bin/lua/jit + ) else() message(FATAL_ERROR "Trying to build luajit in an unsupported system ${CMAKE_SYSTEM_NAME}/${CMAKE_C_COMPILER_ID}") endif() 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..4ceb402455 100644 --- a/third-party/cmake/BuildTreesitterParsers.cmake +++ b/third-party/cmake/BuildTreesitterParsers.cmake @@ -15,8 +15,8 @@ PATCH_COMMAND ${CMAKE_COMMAND} -E copy ${DEPS_BUILD_DIR}/src/treesitter-c/CMakeLists.txt 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> |